mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-03 18:00: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)
 | 
					      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.")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user