mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 10:59:33 +00:00
Add route to send mail for a share
This commit is contained in:
parent
09242fddb2
commit
337293128d
@ -86,7 +86,9 @@ object BackendApp {
|
|||||||
customFieldsImpl <- OCustomFields(store)
|
customFieldsImpl <- OCustomFields(store)
|
||||||
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
||||||
clientSettingsImpl <- OClientSettings(store)
|
clientSettingsImpl <- OClientSettings(store)
|
||||||
shareImpl <- Resource.pure(OShare(store, itemSearchImpl, simpleSearchImpl))
|
shareImpl <- Resource.pure(
|
||||||
|
OShare(store, itemSearchImpl, simpleSearchImpl, javaEmil)
|
||||||
|
)
|
||||||
} yield new BackendApp[F] {
|
} yield new BackendApp[F] {
|
||||||
val login = loginImpl
|
val login = loginImpl
|
||||||
val signup = signupImpl
|
val signup = signupImpl
|
||||||
|
@ -51,6 +51,22 @@ trait OMail[F[_]] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object OMail {
|
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(
|
case class Sent(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
|
@ -13,7 +13,7 @@ import cats.implicits._
|
|||||||
import docspell.backend.PasswordCrypt
|
import docspell.backend.PasswordCrypt
|
||||||
import docspell.backend.auth.ShareToken
|
import docspell.backend.auth.ShareToken
|
||||||
import docspell.backend.ops.OItemSearch._
|
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.backend.ops.OSimpleSearch.StringSearchResult
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.query.ItemQuery
|
import docspell.query.ItemQuery
|
||||||
@ -21,8 +21,9 @@ import docspell.query.ItemQuery.Expr
|
|||||||
import docspell.query.ItemQuery.Expr.AttachId
|
import docspell.query.ItemQuery.Expr.AttachId
|
||||||
import docspell.store.Store
|
import docspell.store.Store
|
||||||
import docspell.store.queries.SearchSummary
|
import docspell.store.queries.SearchSummary
|
||||||
import docspell.store.records.RShare
|
import docspell.store.records.{RShare, RUserEmail}
|
||||||
|
|
||||||
|
import emil._
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
trait OShare[F[_]] {
|
trait OShare[F[_]] {
|
||||||
@ -63,9 +64,33 @@ trait OShare[F[_]] {
|
|||||||
def searchSummary(
|
def searchSummary(
|
||||||
settings: OSimpleSearch.StatsSettings
|
settings: OSimpleSearch.StatsSettings
|
||||||
)(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]]
|
)(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]]
|
||||||
|
|
||||||
|
def sendMail(account: AccountId, connection: Ident, mail: ShareMail): F[SendResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OShare {
|
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) {
|
final case class ShareQuery(id: Ident, cid: Ident, query: ItemQuery) {
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
@ -116,7 +141,8 @@ object OShare {
|
|||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
store: Store[F],
|
store: Store[F],
|
||||||
itemSearch: OItemSearch[F],
|
itemSearch: OItemSearch[F],
|
||||||
simpleSearch: OSimpleSearch[F]
|
simpleSearch: OSimpleSearch[F],
|
||||||
|
emil: Emil[F]
|
||||||
): OShare[F] =
|
): OShare[F] =
|
||||||
new OShare[F] {
|
new OShare[F] {
|
||||||
private[this] val logger = Logger.log4s[F](org.log4s.getLogger)
|
private[this] val logger = Logger.log4s[F](org.log4s.getLogger)
|
||||||
@ -293,5 +319,45 @@ object OShare {
|
|||||||
case other => other
|
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:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/IdResult"
|
$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}:
|
/sec/share/{shareId}:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: "#/components/parameters/shareId"
|
- $ref: "#/components/parameters/shareId"
|
||||||
@ -5283,6 +5309,36 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
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:
|
EmailSettingsList:
|
||||||
description: |
|
description: |
|
||||||
A list of user email settings.
|
A list of user email settings.
|
||||||
|
@ -11,8 +11,7 @@ import cats.implicits._
|
|||||||
|
|
||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.backend.ops.OMail.{AttachSelection, ItemMail}
|
import docspell.backend.ops.OMail.{AttachSelection, ItemMail, SendResult}
|
||||||
import docspell.backend.ops.SendResult
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import cats.implicits._
|
|||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.backend.ops.OShare
|
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.common.{Ident, Timestamp}
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
@ -21,6 +21,8 @@ import docspell.restserver.auth.ShareCookieData
|
|||||||
import docspell.restserver.http4s.{ClientRequestInfo, ResponseGenerator}
|
import docspell.restserver.http4s.{ClientRequestInfo, ResponseGenerator}
|
||||||
import docspell.store.records.RShare
|
import docspell.store.records.RShare
|
||||||
|
|
||||||
|
import emil.MailAddress
|
||||||
|
import emil.javamail.syntax._
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import org.http4s.circe.CirceEntityDecoder._
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
@ -68,6 +70,17 @@ object ShareRoutes {
|
|||||||
del <- backend.share.delete(id, user.account.collective)
|
del <- backend.share.delete(id, user.account.collective)
|
||||||
resp <- Ok(BasicResult(del, if (del) "Share deleted." else "Deleting failed."))
|
resp <- Ok(BasicResult(del, if (del) "Share deleted." else "Deleting failed."))
|
||||||
} yield resp
|
} 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
|
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.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user