mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-10-31 17:50:11 +00:00 
			
		
		
		
	Add routes to retrieve sent mails
This commit is contained in:
		| @@ -6,17 +6,13 @@ import cats.implicits._ | ||||
| import cats.data.OptionT | ||||
| import emil._ | ||||
| import emil.javamail.syntax._ | ||||
| import bitpeace.{FileMeta, RangeDef} | ||||
|  | ||||
| import docspell.common._ | ||||
| import docspell.store._ | ||||
| import docspell.store.records.RUserEmail | ||||
| import OMail.{ItemMail, SmtpSettings} | ||||
| import docspell.store.records.RAttachment | ||||
| import bitpeace.FileMeta | ||||
| import bitpeace.RangeDef | ||||
| import docspell.store.records.RItem | ||||
| import docspell.store.records.RSentMail | ||||
| import docspell.store.records.RSentMailItem | ||||
| import docspell.store.records._ | ||||
| import docspell.store.queries.QMails | ||||
| import OMail.{ItemMail, Sent, SmtpSettings} | ||||
|  | ||||
| trait OMail[F[_]] { | ||||
|  | ||||
| @@ -31,10 +27,32 @@ trait OMail[F[_]] { | ||||
|   def deleteSettings(accId: AccountId, name: Ident): F[Int] | ||||
|  | ||||
|   def sendMail(accId: AccountId, name: Ident, m: ItemMail): F[SendResult] | ||||
|  | ||||
|   def getSentMailsForItem(accId: AccountId, itemId: Ident): F[Vector[Sent]] | ||||
|  | ||||
|   def getSentMail(accId: AccountId, mailId: Ident): OptionT[F, Sent] | ||||
|  | ||||
|   def deleteSentMail(accId: AccountId, mailId: Ident): F[Int] | ||||
| } | ||||
|  | ||||
| object OMail { | ||||
|  | ||||
|   case class Sent( | ||||
|       id: Ident, | ||||
|       senderLogin: Ident, | ||||
|       connectionName: Ident, | ||||
|       recipients: List[MailAddress], | ||||
|       subject: String, | ||||
|       body: String, | ||||
|       created: Timestamp | ||||
|   ) | ||||
|  | ||||
|   object Sent { | ||||
|  | ||||
|     def create(r: RSentMail, login: Ident): Sent = | ||||
|       Sent(r.id, login, r.connName, r.recipients, r.subject, r.body, r.created) | ||||
|   } | ||||
|  | ||||
|   case class ItemMail( | ||||
|       item: Ident, | ||||
|       subject: String, | ||||
| @@ -155,6 +173,7 @@ object OMail { | ||||
|               accId, | ||||
|               msgId, | ||||
|               cfg.mailFrom, | ||||
|               name, | ||||
|               m.subject, | ||||
|               m.recipients, | ||||
|               m.body | ||||
| @@ -180,5 +199,17 @@ object OMail { | ||||
|         } yield conv).getOrElse(SendResult.NotFound) | ||||
|       } | ||||
|  | ||||
|       def getSentMailsForItem(accId: AccountId, itemId: Ident): F[Vector[Sent]] = | ||||
|         store | ||||
|           .transact(QMails.findMails(accId.collective, itemId)) | ||||
|           .map(_.map(t => Sent.create(t._1, t._2))) | ||||
|  | ||||
|       def getSentMail(accId: AccountId, mailId: Ident): OptionT[F, Sent] = | ||||
|         OptionT(store.transact(QMails.findMail(accId.collective, mailId))).map(t => | ||||
|           Sent.create(t._1, t._2) | ||||
|         ) | ||||
|  | ||||
|       def deleteSentMail(accId: AccountId, mailId: Ident): F[Int] = | ||||
|         store.transact(QMails.delete(accId.collective, mailId)) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -125,6 +125,8 @@ paths: | ||||
|  | ||||
|         The result shows all items that contains a file with the given | ||||
|         checksum. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/checksum" | ||||
|       responses: | ||||
| @@ -159,6 +161,8 @@ paths: | ||||
|         * application/pdf | ||||
|  | ||||
|         Support for more types might be added. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       requestBody: | ||||
|         content: | ||||
|           multipart/form-data: | ||||
| @@ -1188,6 +1192,8 @@ paths: | ||||
|         Get the current state of the job qeue. The job qeue contains | ||||
|         all processing tasks and other long-running operations. All | ||||
|         users/collectives share processing resources. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
| @@ -1203,6 +1209,8 @@ paths: | ||||
|         Tries to cancel a job and remove it from the queue. If the job | ||||
|         is running, a cancel request is send to the corresponding joex | ||||
|         instance. Otherwise the job is removed from the queue. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/id" | ||||
|       responses: | ||||
| @@ -1224,6 +1232,8 @@ paths: | ||||
|         Multiple e-mail settings can be specified, they are | ||||
|         distinguished by their `name`. The query `q` parameter does a | ||||
|         simple substring search in the connection name. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/q" | ||||
|       responses: | ||||
| @@ -1238,6 +1248,8 @@ paths: | ||||
|       summary: Create new email settings | ||||
|       description: | | ||||
|         Create new e-mail settings. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
| @@ -1259,6 +1271,8 @@ paths: | ||||
|       description: | | ||||
|         Return the stored e-mail settings for the given connection | ||||
|         name. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
| @@ -1271,6 +1285,8 @@ paths: | ||||
|       summary: Change specific email settings. | ||||
|       description: | | ||||
|         Changes all settings for the connection with the given `name`. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
| @@ -1288,6 +1304,8 @@ paths: | ||||
|       summary: Delete e-mail settings. | ||||
|       description: | | ||||
|         Deletes the e-mail settings with the specified `name`. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
| @@ -1301,10 +1319,11 @@ paths: | ||||
|       tags: [ E-Mail ] | ||||
|       summary: Send an email. | ||||
|       description: | | ||||
|         Sends an email as specified with all attachments of the item | ||||
|         with `id` as mail attachments. If the item has no attachments, | ||||
|         then the mail is sent without any. If the item's attachments | ||||
|         exceed a specific size, the mail will not be sent. | ||||
|         Sends an email as specified in the body of the request. | ||||
|  | ||||
|         The item's attachment are added to the mail if requested. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/name" | ||||
|         - $ref: "#/components/parameters/id" | ||||
| @@ -1320,9 +1339,100 @@ paths: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/BasicResult" | ||||
|   /sec/email/sent/item/{id}: | ||||
|     get: | ||||
|       tags: [ E-Mail ] | ||||
|       summary: Get sent mail related to an item | ||||
|       description: | | ||||
|         Return all mails that have been sent related to the item with | ||||
|         id `id`. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       parameters: | ||||
|         - $ref: "#/components/parameters/id" | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/SentMails" | ||||
|   /sec/email/sent/mail/{mailId}: | ||||
|     parameters: | ||||
|       - $ref: "#/components/parameters/mailId" | ||||
|     get: | ||||
|       tags: [ E-Mail ] | ||||
|       summary: Get sent single mail related to an item | ||||
|       description: | | ||||
|         Return one mail with the given id. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/SentMail" | ||||
|     delete: | ||||
|       tags: [ E-Mail ] | ||||
|       summary: Delete a sent mail. | ||||
|       description: | | ||||
|         Delete a sent mail. | ||||
|       security: | ||||
|         - authTokenHeader: [] | ||||
|       responses: | ||||
|         200: | ||||
|           description: Ok | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: "#/components/schemas/BasicResult" | ||||
|  | ||||
| components: | ||||
|   schemas: | ||||
|     SentMails: | ||||
|       description: | | ||||
|         A list of sent mails. | ||||
|       required: | ||||
|         - items | ||||
|       properties: | ||||
|         items: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: "#/components/schemas/SentMail" | ||||
|     SentMail: | ||||
|       description: | | ||||
|         A mail that has been sent previously related to an item. | ||||
|       required: | ||||
|         - id | ||||
|         - sender | ||||
|         - connection | ||||
|         - recipients | ||||
|         - subject | ||||
|         - body | ||||
|         - created | ||||
|       properties: | ||||
|         id: | ||||
|           type: string | ||||
|           format: ident | ||||
|         sender: | ||||
|           type: string | ||||
|           format: ident | ||||
|         connection: | ||||
|           type: string | ||||
|           format: ident | ||||
|         recipients: | ||||
|           type: array | ||||
|           items: | ||||
|             type: string | ||||
|         subject: | ||||
|           type: string | ||||
|         body: | ||||
|           type: string | ||||
|         created: | ||||
|           type: integer | ||||
|           format: date-time | ||||
|     SimpleMail: | ||||
|       description: | | ||||
|         A simple e-mail related to an item. | ||||
| @@ -1330,7 +1440,8 @@ components: | ||||
|         The mail may contain the item attachments as mail attachments. | ||||
|         If all item attachments should be send, set | ||||
|         `addAllAttachments` to `true`. Otherwise set it to `false` and | ||||
|         specify a list of file-ids that you want to include. | ||||
|         specify a list of file-ids that you want to include. This list | ||||
|         is ignored, if `addAllAttachments` is set to `true`. | ||||
|       required: | ||||
|         - recipients | ||||
|         - subject | ||||
| @@ -2369,3 +2480,10 @@ components: | ||||
|       required: true | ||||
|       schema: | ||||
|         type: string | ||||
|     mailId: | ||||
|       name: mailId | ||||
|       in: path | ||||
|       description: The id of a sent mail. | ||||
|       required: true | ||||
|       schema: | ||||
|         type: string | ||||
|   | ||||
| @@ -71,7 +71,8 @@ object RestServer { | ||||
|       "upload"         -> UploadRoutes.secured(restApp.backend, cfg, token), | ||||
|       "checkfile"      -> CheckFileRoutes.secured(restApp.backend, token), | ||||
|       "email/send"     -> MailSendRoutes(restApp.backend, token), | ||||
|       "email/settings" -> MailSettingsRoutes(restApp.backend, token) | ||||
|       "email/settings" -> MailSettingsRoutes(restApp.backend, token), | ||||
|       "email/sent"     -> SentMailRoutes(restApp.backend, token) | ||||
|     ) | ||||
|  | ||||
|   def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] = | ||||
|   | ||||
| @@ -0,0 +1,54 @@ | ||||
| package docspell.restserver.routes | ||||
|  | ||||
| import cats.effect._ | ||||
| import cats.implicits._ | ||||
| import cats.data.OptionT | ||||
| import org.http4s._ | ||||
| import org.http4s.dsl.Http4sDsl | ||||
| import org.http4s.circe.CirceEntityEncoder._ | ||||
|  | ||||
| import docspell.backend.BackendApp | ||||
| import docspell.backend.auth.AuthToken | ||||
| import docspell.backend.ops.OMail.Sent | ||||
| import docspell.common._ | ||||
| import docspell.restapi.model._ | ||||
| import docspell.store.EmilUtil | ||||
|  | ||||
| object SentMailRoutes { | ||||
|  | ||||
|   def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = { | ||||
|     val dsl = new Http4sDsl[F] {} | ||||
|     import dsl._ | ||||
|  | ||||
|     HttpRoutes.of { | ||||
|       case GET -> Root / "item" / Ident(id) => | ||||
|         for { | ||||
|           all <- backend.mail.getSentMailsForItem(user.account, id) | ||||
|           resp <- Ok(SentMails(all.map(convert).toList)) | ||||
|         } yield resp | ||||
|  | ||||
|       case GET -> Root / "mail" / Ident(mailId) => | ||||
|         (for { | ||||
|           mail <- backend.mail.getSentMail(user.account, mailId) | ||||
|           resp <- OptionT.liftF(Ok(convert(mail))) | ||||
|         } yield resp).getOrElseF(NotFound()) | ||||
|  | ||||
|       case DELETE -> Root / "mail" / Ident(mailId) => | ||||
|         for { | ||||
|           n <- backend.mail.deleteSentMail(user.account, mailId) | ||||
|           resp <- Ok(BasicResult(n > 0, s"Mails deleted: $n")) | ||||
|         } yield resp | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def convert(s: Sent): SentMail = | ||||
|     SentMail( | ||||
|       s.id, | ||||
|       s.senderLogin, | ||||
|       s.connectionName, | ||||
|       s.recipients.map(EmilUtil.mailAddressString), | ||||
|       s.subject, | ||||
|       s.body, | ||||
|       s.created | ||||
|     ) | ||||
| } | ||||
| @@ -20,6 +20,7 @@ CREATE TABLE "sentmail" ( | ||||
|   "uid" varchar(254) not null, | ||||
|   "message_id" varchar(254) not null, | ||||
|   "sender" varchar(254) not null, | ||||
|   "conn_name" varchar(254) not null, | ||||
|   "subject" varchar(254) not null, | ||||
|   "recipients" varchar(254) not null, | ||||
|   "body" text not null, | ||||
|   | ||||
| @@ -65,12 +65,6 @@ trait DoobieSyntax { | ||||
|     Fragment.const("SELECT DISTINCT(") ++ commas(cols.map(_.f)) ++ | ||||
|       Fragment.const(") FROM ") ++ table ++ this.where(where) | ||||
|  | ||||
| //  def selectJoinCollective(cols: Seq[Column], fkCid: Column, table: Fragment, wh: Fragment): Fragment = | ||||
| //    selectSimple(cols.map(_.prefix("a")) | ||||
| //      , table ++ fr"a," ++ RCollective.table ++ fr"b" | ||||
| //      , if (isEmpty(wh)) fkCid.prefix("a") is RCollective.Columns.id.prefix("b") | ||||
| //        else and(wh, fkCid.prefix("a") is RCollective.Columns.id.prefix("b"))) | ||||
|  | ||||
|   def selectCount(col: Column, table: Fragment, where: Fragment): Fragment = | ||||
|     Fragment.const("SELECT COUNT(") ++ col.f ++ Fragment.const(") FROM ") ++ table ++ this.where( | ||||
|       where | ||||
|   | ||||
| @@ -0,0 +1,64 @@ | ||||
| package docspell.store.queries | ||||
|  | ||||
| import cats.data.OptionT | ||||
| import doobie._ | ||||
| import doobie.implicits._ | ||||
|  | ||||
| import docspell.common._ | ||||
| import docspell.store.impl.Column | ||||
| import docspell.store.impl.Implicits._ | ||||
| import docspell.store.records.{RItem, RSentMail, RSentMailItem, RUser} | ||||
|  | ||||
| object QMails { | ||||
|  | ||||
|   def delete(coll: Ident, mailId: Ident): ConnectionIO[Int] = | ||||
|     (for { | ||||
|       m <- OptionT(findMail(coll, mailId)) | ||||
|       k <- OptionT.liftF(RSentMailItem.deleteMail(mailId)) | ||||
|       n <- OptionT.liftF(RSentMail.delete(m._1.id)) | ||||
|     } yield k + n).getOrElse(0) | ||||
|  | ||||
|   def findMail(coll: Ident, mailId: Ident): ConnectionIO[Option[(RSentMail, Ident)]] = { | ||||
|     val iColl = RItem.Columns.cid.prefix("i") | ||||
|     val mId   = RSentMail.Columns.id.prefix("m") | ||||
|  | ||||
|     val (cols, from) = partialFind | ||||
|  | ||||
|     val cond = Seq(mId.is(mailId), iColl.is(coll)) | ||||
|  | ||||
|     selectSimple(cols, from, and(cond)).query[(RSentMail, Ident)].option | ||||
|   } | ||||
|  | ||||
|   def findMails(coll: Ident, itemId: Ident): ConnectionIO[Vector[(RSentMail, Ident)]] = { | ||||
|     val iColl    = RItem.Columns.cid.prefix("i") | ||||
|     val tItem    = RSentMailItem.Columns.itemId.prefix("t") | ||||
|     val mCreated = RSentMail.Columns.created.prefix("m") | ||||
|  | ||||
|     val (cols, from) = partialFind | ||||
|  | ||||
|     val cond = Seq(tItem.is(itemId), iColl.is(coll)) | ||||
|  | ||||
|     (selectSimple(cols, from, and(cond)) ++ orderBy(mCreated.f) ++ fr"DESC") | ||||
|       .query[(RSentMail, Ident)] | ||||
|       .to[Vector] | ||||
|   } | ||||
|  | ||||
|   private def partialFind: (Seq[Column], Fragment) = { | ||||
|     val iId    = RItem.Columns.id.prefix("i") | ||||
|     val tItem  = RSentMailItem.Columns.itemId.prefix("t") | ||||
|     val tMail  = RSentMailItem.Columns.sentMailId.prefix("t") | ||||
|     val mId    = RSentMail.Columns.id.prefix("m") | ||||
|     val mUser  = RSentMail.Columns.uid.prefix("m") | ||||
|     val uId    = RUser.Columns.uid.prefix("u") | ||||
|     val uLogin = RUser.Columns.login.prefix("u") | ||||
|  | ||||
|     val cols = RSentMail.Columns.all.map(_.prefix("m")) :+ uLogin | ||||
|     val from = RSentMail.table ++ fr"m INNER JOIN" ++ | ||||
|       RSentMailItem.table ++ fr"t ON" ++ tMail.is(mId) ++ | ||||
|       fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ tItem.is(iId) ++ | ||||
|       fr"INNER JOIN" ++ RUser.table ++ fr"u ON" ++ uId.is(mUser) | ||||
|  | ||||
|     (cols, from) | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -16,6 +16,7 @@ case class RSentMail( | ||||
|     uid: Ident, | ||||
|     messageId: String, | ||||
|     sender: MailAddress, | ||||
|     connName: Ident, | ||||
|     subject: String, | ||||
|     recipients: List[MailAddress], | ||||
|     body: String, | ||||
| @@ -28,6 +29,7 @@ object RSentMail { | ||||
|       uid: Ident, | ||||
|       messageId: String, | ||||
|       sender: MailAddress, | ||||
|       connName: Ident, | ||||
|       subject: String, | ||||
|       recipients: List[MailAddress], | ||||
|       body: String | ||||
| @@ -35,24 +37,26 @@ object RSentMail { | ||||
|     for { | ||||
|       id  <- Ident.randomId[F] | ||||
|       now <- Timestamp.current[F] | ||||
|     } yield RSentMail(id, uid, messageId, sender, subject, recipients, body, now) | ||||
|     } yield RSentMail(id, uid, messageId, sender, connName, subject, recipients, body, now) | ||||
|  | ||||
|   def forItem( | ||||
|       itemId: Ident, | ||||
|       accId: AccountId, | ||||
|       messageId: String, | ||||
|       sender: MailAddress, | ||||
|       connName: Ident, | ||||
|       subject: String, | ||||
|       recipients: List[MailAddress], | ||||
|       body: String | ||||
|   ): OptionT[ConnectionIO, (RSentMail, RSentMailItem)] = | ||||
|     for { | ||||
|       user <- OptionT(RUser.findByAccount(accId)) | ||||
|       sm <- OptionT.liftF(RSentMail[ConnectionIO](user.uid, messageId, sender, subject, recipients, body)) | ||||
|       sm <- OptionT.liftF( | ||||
|         RSentMail[ConnectionIO](user.uid, messageId, sender, connName, subject, recipients, body) | ||||
|       ) | ||||
|       si <- OptionT.liftF(RSentMailItem[ConnectionIO](itemId, sm.id, Some(sm.created))) | ||||
|     } yield (sm, si) | ||||
|  | ||||
|  | ||||
|   val table = fr"sentmail" | ||||
|  | ||||
|   object Columns { | ||||
| @@ -60,6 +64,7 @@ object RSentMail { | ||||
|     val uid        = Column("uid") | ||||
|     val messageId  = Column("message_id") | ||||
|     val sender     = Column("sender") | ||||
|     val connName   = Column("conn_name") | ||||
|     val subject    = Column("subject") | ||||
|     val recipients = Column("recipients") | ||||
|     val body       = Column("body") | ||||
| @@ -70,6 +75,7 @@ object RSentMail { | ||||
|       uid, | ||||
|       messageId, | ||||
|       sender, | ||||
|       connName, | ||||
|       subject, | ||||
|       recipients, | ||||
|       body, | ||||
| @@ -83,10 +89,12 @@ object RSentMail { | ||||
|     insertRow( | ||||
|       table, | ||||
|       all, | ||||
|       sql"${v.id},${v.uid},${v.messageId},${v.sender},${v.subject},${v.recipients},${v.body},${v.created}" | ||||
|       sql"${v.id},${v.uid},${v.messageId},${v.sender},${v.connName},${v.subject},${v.recipients},${v.body},${v.created}" | ||||
|     ).update.run | ||||
|  | ||||
|   def findByUser(userId: Ident): Stream[ConnectionIO, RSentMail] = | ||||
|     selectSimple(all, table, uid.is(userId)).query[RSentMail].stream | ||||
|  | ||||
|   def delete(mailId: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, id.is(mailId)).update.run | ||||
| } | ||||
|   | ||||
| @@ -52,4 +52,6 @@ object RSentMailItem { | ||||
|       sql"${v.id},${v.itemId},${v.sentMailId},${v.created}" | ||||
|     ).update.run | ||||
|  | ||||
|   def deleteMail(mailId: Ident): ConnectionIO[Int] = | ||||
|     deleteFrom(table, sentMailId.is(mailId)).update.run | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user