mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-31 17:50:11 +00:00 
			
		
		
		
	Add route to send mail for a share
This commit is contained in:
		| @@ -86,7 +86,9 @@ object BackendApp { | ||||
|       customFieldsImpl <- OCustomFields(store) | ||||
|       simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl) | ||||
|       clientSettingsImpl <- OClientSettings(store) | ||||
|       shareImpl <- Resource.pure(OShare(store, itemSearchImpl, simpleSearchImpl)) | ||||
|       shareImpl <- Resource.pure( | ||||
|         OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil) | ||||
|       ) | ||||
|     } yield new BackendApp[F] { | ||||
|       val login = loginImpl | ||||
|       val signup = signupImpl | ||||
|   | ||||
| @@ -51,6 +51,22 @@ trait OMail[F[_]] { | ||||
| } | ||||
|  | ||||
| object OMail { | ||||
|   sealed trait SendResult | ||||
|  | ||||
|   object SendResult { | ||||
|  | ||||
|     /** Mail was successfully sent and stored to db. */ | ||||
|     case class Success(id: Ident) extends SendResult | ||||
|  | ||||
|     /** There was a failure sending the mail. The mail is then not saved to db. */ | ||||
|     case class SendFailure(ex: Throwable) extends SendResult | ||||
|  | ||||
|     /** The mail was successfully sent, but storing to db failed. */ | ||||
|     case class StoreFailure(ex: Throwable) extends SendResult | ||||
|  | ||||
|     /** Something could not be found required for sending (mail configs, items etc). */ | ||||
|     case object NotFound extends SendResult | ||||
|   } | ||||
|  | ||||
|   case class Sent( | ||||
|       id: Ident, | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import cats.implicits._ | ||||
| import docspell.backend.PasswordCrypt | ||||
| import docspell.backend.auth.ShareToken | ||||
| import docspell.backend.ops.OItemSearch._ | ||||
| import docspell.backend.ops.OShare.{ShareQuery, VerifyResult} | ||||
| import docspell.backend.ops.OShare._ | ||||
| import docspell.backend.ops.OSimpleSearch.StringSearchResult | ||||
| import docspell.common._ | ||||
| import docspell.query.ItemQuery | ||||
| @@ -21,8 +21,9 @@ import docspell.query.ItemQuery.Expr | ||||
| import docspell.query.ItemQuery.Expr.AttachId | ||||
| import docspell.store.Store | ||||
| import docspell.store.queries.SearchSummary | ||||
| import docspell.store.records.RShare | ||||
| import docspell.store.records.{RShare, RUserEmail} | ||||
|  | ||||
| import emil._ | ||||
| import scodec.bits.ByteVector | ||||
|  | ||||
| trait OShare[F[_]] { | ||||
| @@ -63,9 +64,33 @@ trait OShare[F[_]] { | ||||
|   def searchSummary( | ||||
|       settings: OSimpleSearch.StatsSettings | ||||
|   )(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]] | ||||
|  | ||||
|   def sendMail(account: AccountId, connection: Ident, mail: ShareMail): F[SendResult] | ||||
| } | ||||
|  | ||||
| object OShare { | ||||
|   final case class ShareMail( | ||||
|       shareId: Ident, | ||||
|       subject: String, | ||||
|       recipients: List[MailAddress], | ||||
|       cc: List[MailAddress], | ||||
|       bcc: List[MailAddress], | ||||
|       body: String | ||||
|   ) | ||||
|  | ||||
|   sealed trait SendResult | ||||
|   object SendResult { | ||||
|  | ||||
|     /** Mail was successfully sent and stored to db. */ | ||||
|     case class Success(msgId: String) extends SendResult | ||||
|  | ||||
|     /** There was a failure sending the mail. The mail is then not saved to db. */ | ||||
|     case class SendFailure(ex: Throwable) extends SendResult | ||||
|  | ||||
|     /** Something could not be found required for sending (mail configs, items etc). */ | ||||
|     case object NotFound extends SendResult | ||||
|   } | ||||
|  | ||||
|   final case class ShareQuery(id: Ident, cid: Ident, query: ItemQuery) { | ||||
|  | ||||
|     //TODO | ||||
| @@ -116,7 +141,8 @@ object OShare { | ||||
|   def apply[F[_]: Async]( | ||||
|       store: Store[F], | ||||
|       itemSearch: OItemSearch[F], | ||||
|       simpleSearch: OSimpleSearch[F] | ||||
|       simpleSearch: OSimpleSearch[F], | ||||
|       emil: Emil[F] | ||||
|   ): OShare[F] = | ||||
|     new OShare[F] { | ||||
|       private[this] val logger = Logger.log4s[F](org.log4s.getLogger) | ||||
| @@ -293,5 +319,45 @@ object OShare { | ||||
|                 case other => other | ||||
|               } | ||||
|           } | ||||
|  | ||||
|       def sendMail( | ||||
|           account: AccountId, | ||||
|           connection: Ident, | ||||
|           mail: ShareMail | ||||
|       ): F[SendResult] = { | ||||
|         val getSmtpSettings: OptionT[F, RUserEmail] = | ||||
|           OptionT(store.transact(RUserEmail.getByName(account, connection))) | ||||
|  | ||||
|         def createMail(sett: RUserEmail): OptionT[F, Mail[F]] = { | ||||
|           import _root_.emil.builder._ | ||||
|  | ||||
|           OptionT.pure( | ||||
|             MailBuilder.build( | ||||
|               From(sett.mailFrom), | ||||
|               Tos(mail.recipients), | ||||
|               Ccs(mail.cc), | ||||
|               Bccs(mail.bcc), | ||||
|               XMailer.emil, | ||||
|               Subject(mail.subject), | ||||
|               TextBody[F](mail.body) | ||||
|             ) | ||||
|           ) | ||||
|         } | ||||
|  | ||||
|         def sendMail(cfg: MailConfig, mail: Mail[F]): F[Either[SendResult, String]] = | ||||
|           emil(cfg).send(mail).map(_.head).attempt.map(_.left.map(SendResult.SendFailure)) | ||||
|  | ||||
|         (for { | ||||
|           _ <- RShare | ||||
|             .findCurrentActive(mail.shareId) | ||||
|             .filter(_.cid == account.collective) | ||||
|             .mapK(store.transform) | ||||
|           mailCfg <- getSmtpSettings | ||||
|           mail <- createMail(mailCfg) | ||||
|           mid <- OptionT.liftF(sendMail(mailCfg.toMailConfig, mail)) | ||||
|           conv = mid.fold(identity, id => SendResult.Success(id)) | ||||
|         } yield conv).getOrElse(SendResult.NotFound) | ||||
|       } | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,26 +0,0 @@ | ||||
| /* | ||||
|  * Copyright 2020 Eike K. & Contributors | ||||
|  * | ||||
|  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||
|  */ | ||||
|  | ||||
| package docspell.backend.ops | ||||
|  | ||||
| import docspell.common._ | ||||
|  | ||||
| sealed trait SendResult | ||||
|  | ||||
| object SendResult { | ||||
|  | ||||
|   /** Mail was successfully sent and stored to db. */ | ||||
|   case class Success(id: Ident) extends SendResult | ||||
|  | ||||
|   /** There was a failure sending the mail. The mail is then not saved to db. */ | ||||
|   case class SendFailure(ex: Throwable) extends SendResult | ||||
|  | ||||
|   /** The mail was successfully sent, but storing to db failed. */ | ||||
|   case class StoreFailure(ex: Throwable) extends SendResult | ||||
|  | ||||
|   /** Something could not be found required for sending (mail configs, items etc). */ | ||||
|   case object NotFound extends SendResult | ||||
| } | ||||
| @@ -1959,6 +1959,32 @@ paths: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/IdResult" | ||||
|   /sec/share/email/send/{name}: | ||||
|     post: | ||||
|       operationId: "sec-share-email-send" | ||||
|       tags: [ Share, E-Mail ] | ||||
|       summary: Send an email. | ||||
|       description: | | ||||
|         Sends an email as specified in the body of the request. | ||||
|  | ||||
|         An existing shareId must be given with the request, no matter | ||||
|         the content of the mail. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/name" | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: "#/components/schemas/SimpleShareMail" | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/BasicResult" | ||||
|   /sec/share/{shareId}: | ||||
|     parameters: | ||||
|       - $ref: "#/components/parameters/shareId" | ||||
| @@ -5283,6 +5309,36 @@ components: | ||||
|           items: | ||||
|             type: string | ||||
|             format: ident | ||||
|     SimpleShareMail: | ||||
|       description: | | ||||
|         A simple e-mail related to a share. | ||||
|       required: | ||||
|         - shareId | ||||
|         - recipients | ||||
|         - cc | ||||
|         - bcc | ||||
|         - subject | ||||
|         - body | ||||
|       properties: | ||||
|         shareId: | ||||
|           type: string | ||||
|           format: ident | ||||
|         recipients: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         cc: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         bcc: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         subject: | ||||
|           type: string | ||||
|         body: | ||||
|           type: string | ||||
|     EmailSettingsList: | ||||
|       description: | | ||||
|         A list of user email settings. | ||||
|   | ||||
| @@ -11,8 +11,7 @@ import cats.implicits._ | ||||
|  | ||||
| import docspell.backend.BackendApp | ||||
| import docspell.backend.auth.AuthToken | ||||
| import docspell.backend.ops.OMail.{AttachSelection, ItemMail} | ||||
| import docspell.backend.ops.SendResult | ||||
| import docspell.backend.ops.OMail.{AttachSelection, ItemMail, SendResult} | ||||
| import docspell.common._ | ||||
| import docspell.restapi.model._ | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import cats.implicits._ | ||||
| import docspell.backend.BackendApp | ||||
| import docspell.backend.auth.AuthToken | ||||
| import docspell.backend.ops.OShare | ||||
| import docspell.backend.ops.OShare.VerifyResult | ||||
| import docspell.backend.ops.OShare.{SendResult, ShareMail, VerifyResult} | ||||
| import docspell.common.{Ident, Timestamp} | ||||
| import docspell.restapi.model._ | ||||
| import docspell.restserver.Config | ||||
| @@ -21,6 +21,8 @@ import docspell.restserver.auth.ShareCookieData | ||||
| import docspell.restserver.http4s.{ClientRequestInfo, ResponseGenerator} | ||||
| import docspell.store.records.RShare | ||||
|  | ||||
| import emil.MailAddress | ||||
| import emil.javamail.syntax._ | ||||
| import org.http4s.HttpRoutes | ||||
| import org.http4s.circe.CirceEntityDecoder._ | ||||
| import org.http4s.circe.CirceEntityEncoder._ | ||||
| @@ -68,6 +70,17 @@ object ShareRoutes { | ||||
|           del <- backend.share.delete(id, user.account.collective) | ||||
|           resp <- Ok(BasicResult(del, if (del) "Share deleted." else "Deleting failed.")) | ||||
|         } yield resp | ||||
|  | ||||
|       case req @ POST -> Root / "email" / "send" / Ident(name) => | ||||
|         for { | ||||
|           in <- req.as[SimpleShareMail] | ||||
|           mail = convertIn(in) | ||||
|           res <- mail.traverse(m => backend.share.sendMail(user.account, name, m)) | ||||
|           resp <- res.fold( | ||||
|             err => Ok(BasicResult(false, s"Invalid mail data: $err")), | ||||
|             res => Ok(convertOut(res)) | ||||
|           ) | ||||
|         } yield resp | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -134,4 +147,20 @@ object ShareRoutes { | ||||
|       r.lastAccess | ||||
|     ) | ||||
|  | ||||
|   def convertIn(s: SimpleShareMail): Either[String, ShareMail] = | ||||
|     for { | ||||
|       rec <- s.recipients.traverse(MailAddress.parse) | ||||
|       cc <- s.cc.traverse(MailAddress.parse) | ||||
|       bcc <- s.bcc.traverse(MailAddress.parse) | ||||
|     } yield ShareMail(s.shareId, s.subject, rec, cc, bcc, s.body) | ||||
|  | ||||
|   def convertOut(res: SendResult): BasicResult = | ||||
|     res match { | ||||
|       case SendResult.Success(_) => | ||||
|         BasicResult(true, "Mail sent.") | ||||
|       case SendResult.SendFailure(ex) => | ||||
|         BasicResult(false, s"Mail sending failed: ${ex.getMessage}") | ||||
|       case SendResult.NotFound => | ||||
|         BasicResult(false, s"There was no mail-connection or item found.") | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user