mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-28 09:45:07 +00:00
Add routes to retrieve sent mails
This commit is contained in:
parent
b795a22992
commit
2ecfb679d9
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user