mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-28 09:45:07 +00:00
Prepare sending mail
This commit is contained in:
parent
51ce48997c
commit
7a3289c41d
@ -8,6 +8,7 @@ import emil.{MailAddress, SSLType}
|
||||
import docspell.common._
|
||||
import docspell.store._
|
||||
import docspell.store.records.RUserEmail
|
||||
import OMail.{ItemMail, SmtpSettings}
|
||||
|
||||
trait OMail[F[_]] {
|
||||
|
||||
@ -15,15 +16,31 @@ trait OMail[F[_]] {
|
||||
|
||||
def findSettings(accId: AccountId, name: Ident): OptionT[F, RUserEmail]
|
||||
|
||||
def createSettings(accId: AccountId, data: OMail.SmtpSettings): F[AddResult]
|
||||
def createSettings(accId: AccountId, data: SmtpSettings): F[AddResult]
|
||||
|
||||
def updateSettings(accId: AccountId, name: Ident, data: OMail.SmtpSettings): F[Int]
|
||||
|
||||
def deleteSettings(accId: AccountId, name: Ident): F[Int]
|
||||
|
||||
def sendMail(accId: AccountId, name: Ident, m: ItemMail): F[SendResult]
|
||||
}
|
||||
|
||||
object OMail {
|
||||
|
||||
case class ItemMail(
|
||||
item: Ident,
|
||||
subject: String,
|
||||
recipients: List[MailAddress],
|
||||
body: String,
|
||||
attach: AttachSelection
|
||||
)
|
||||
|
||||
sealed trait AttachSelection
|
||||
object AttachSelection {
|
||||
case object All extends AttachSelection
|
||||
case class Selected(ids: List[Ident]) extends AttachSelection
|
||||
}
|
||||
|
||||
case class SmtpSettings(
|
||||
name: Ident,
|
||||
smtpHost: String,
|
||||
@ -79,5 +96,8 @@ object OMail {
|
||||
|
||||
def deleteSettings(accId: AccountId, name: Ident): F[Int] =
|
||||
store.transact(RUserEmail.delete(accId, name))
|
||||
|
||||
def sendMail(accId: AccountId, name: Ident, m: ItemMail): F[SendResult] =
|
||||
Effect[F].pure(SendResult.Failure(new Exception("not implemented")))
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import docspell.common._
|
||||
|
||||
sealed trait SendResult
|
||||
|
||||
object SendResult {
|
||||
|
||||
case class Success(id: Ident) extends SendResult
|
||||
|
||||
case class Failure(ex: Throwable) extends SendResult
|
||||
}
|
@ -1348,7 +1348,7 @@ components:
|
||||
type: string
|
||||
addAllAttachments:
|
||||
type: boolean
|
||||
attachemntIds:
|
||||
attachmentIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
@ -70,6 +70,7 @@ object RestServer {
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"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)
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
import org.http4s._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.backend.ops.OMail.{AttachSelection, ItemMail}
|
||||
import docspell.backend.ops.SendResult
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
import docspell.store.EmilUtil
|
||||
|
||||
object MailSendRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / Ident(name) / Ident(id) =>
|
||||
for {
|
||||
in <- req.as[SimpleMail]
|
||||
mail = convertIn(id, in)
|
||||
res <- mail.traverse(m => backend.mail.sendMail(user.account, name, m))
|
||||
resp <- res.fold(
|
||||
err => Ok(BasicResult(false, s"Invalid mail data: $err")),
|
||||
res => Ok(convertOut(res))
|
||||
)
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
def convertIn(item: Ident, s: SimpleMail): Either[String, ItemMail] =
|
||||
for {
|
||||
rec <- s.recipients.traverse(EmilUtil.readMailAddress)
|
||||
fileIds <- s.attachmentIds.traverse(Ident.fromString)
|
||||
sel = if (s.addAllAttachments) AttachSelection.All else AttachSelection.Selected(fileIds)
|
||||
} yield ItemMail(item, s.subject, rec, s.body, sel)
|
||||
|
||||
def convertOut(res: SendResult): BasicResult =
|
||||
res match {
|
||||
case SendResult.Success(_) =>
|
||||
BasicResult(true, "Mail sent.")
|
||||
case SendResult.Failure(ex) =>
|
||||
BasicResult(false, s"Mail sending failed: ${ex.getMessage}")
|
||||
}
|
||||
}
|
@ -14,3 +14,17 @@ CREATE TABLE "useremail" (
|
||||
unique ("uid", "name"),
|
||||
foreign key ("uid") references "user_"("uid")
|
||||
);
|
||||
|
||||
CREATE TABLE "sentmail" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"item_id" varchar(254) not null,
|
||||
"message_id" varchar(254) not null,
|
||||
"sender" varchar(254) not null,
|
||||
"subject" varchar(254) not null,
|
||||
"recipients" varchar(254) not null,
|
||||
"body" text not null,
|
||||
"created" timestamp not null,
|
||||
foreign key("uid") references "user_"("uid"),
|
||||
foreign key("item_id") references "item"("itemid")
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package docspell.store
|
||||
|
||||
import cats.implicits._
|
||||
import emil._
|
||||
import emil.javamail.syntax._
|
||||
|
||||
@ -29,6 +30,9 @@ object EmilUtil {
|
||||
def unsafeReadMailAddress(str: String): MailAddress =
|
||||
readMailAddress(str).fold(sys.error, identity)
|
||||
|
||||
def readMultipleAddresses(str: String): Either[String, List[MailAddress]] =
|
||||
str.split(',').toList.map(_.trim).traverse(readMailAddress)
|
||||
|
||||
def mailAddressString(ma: MailAddress): String =
|
||||
ma.asUnicodeString
|
||||
}
|
||||
|
@ -93,6 +93,9 @@ trait DoobieMeta {
|
||||
|
||||
implicit val mailAddress: Meta[MailAddress] =
|
||||
Meta[String].imap(EmilUtil.unsafeReadMailAddress)(EmilUtil.mailAddressString)
|
||||
|
||||
implicit def mailAddressList: Meta[List[MailAddress]] =
|
||||
???
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
|
@ -0,0 +1,63 @@
|
||||
package docspell.store.records
|
||||
|
||||
import fs2.Stream
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
import emil.MailAddress
|
||||
|
||||
case class RSentMail(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
itemId: Ident,
|
||||
messageId: String,
|
||||
sender: MailAddress,
|
||||
subject: String,
|
||||
recipients: List[MailAddress],
|
||||
body: String,
|
||||
created: Timestamp
|
||||
) {}
|
||||
|
||||
object RSentMail {
|
||||
|
||||
val table = fr"sentmail"
|
||||
|
||||
object Columns {
|
||||
val id = Column("id")
|
||||
val uid = Column("uid")
|
||||
val itemId = Column("item_id")
|
||||
val messageId = Column("message_id")
|
||||
val sender = Column("sender")
|
||||
val subject = Column("subject")
|
||||
val recipients = Column("recipients")
|
||||
val body = Column("body")
|
||||
val created = Column("created")
|
||||
|
||||
val all = List(
|
||||
id,
|
||||
uid,
|
||||
itemId,
|
||||
messageId,
|
||||
sender,
|
||||
subject,
|
||||
recipients,
|
||||
body,
|
||||
created
|
||||
)
|
||||
}
|
||||
|
||||
import Columns._
|
||||
|
||||
def insert(v: RSentMail): ConnectionIO[Int] =
|
||||
insertRow(
|
||||
table,
|
||||
all,
|
||||
sql"${v.id},${v.uid},${v.itemId},${v.messageId},${v.sender},${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
|
||||
|
||||
}
|
@ -40,6 +40,7 @@ module Api exposing
|
||||
, putUser
|
||||
, refreshSession
|
||||
, register
|
||||
, sendMail
|
||||
, setCollectiveSettings
|
||||
, setConcEquip
|
||||
, setConcPerson
|
||||
@ -86,6 +87,7 @@ import Api.Model.Person exposing (Person)
|
||||
import Api.Model.PersonList exposing (PersonList)
|
||||
import Api.Model.ReferenceList exposing (ReferenceList)
|
||||
import Api.Model.Registration exposing (Registration)
|
||||
import Api.Model.SimpleMail exposing (SimpleMail)
|
||||
import Api.Model.Source exposing (Source)
|
||||
import Api.Model.SourceList exposing (SourceList)
|
||||
import Api.Model.Tag exposing (Tag)
|
||||
@ -105,6 +107,24 @@ import Util.Http as Http2
|
||||
|
||||
|
||||
|
||||
--- Mail Send
|
||||
|
||||
|
||||
sendMail :
|
||||
Flags
|
||||
-> { conn : String, item : String, mail : SimpleMail }
|
||||
-> (Result Http.Error BasicResult -> msg)
|
||||
-> Cmd msg
|
||||
sendMail flags opts receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/send/" ++ opts.conn ++ "/" ++ opts.item
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.SimpleMail.encode opts.mail)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Mail Settings
|
||||
|
||||
|
||||
|
@ -33,6 +33,7 @@ import Html.Events exposing (onClick, onInput)
|
||||
import Http
|
||||
import Markdown
|
||||
import Page exposing (Page(..))
|
||||
import Util.Http
|
||||
import Util.Maybe
|
||||
import Util.Size
|
||||
import Util.String
|
||||
@ -60,6 +61,7 @@ type alias Model =
|
||||
, dueDatePicker : DatePicker
|
||||
, itemMail : Comp.ItemMail.Model
|
||||
, mailOpen : Bool
|
||||
, mailSendResult : Maybe BasicResult
|
||||
}
|
||||
|
||||
|
||||
@ -121,6 +123,7 @@ emptyModel =
|
||||
, dueDatePicker = Comp.DatePicker.emptyModel
|
||||
, itemMail = Comp.ItemMail.emptyModel
|
||||
, mailOpen = False
|
||||
, mailSendResult = Nothing
|
||||
}
|
||||
|
||||
|
||||
@ -165,6 +168,7 @@ type Msg
|
||||
| RemoveDate
|
||||
| ItemMailMsg Comp.ItemMail.Msg
|
||||
| ToggleMail
|
||||
| SendMailResp (Result Http.Error BasicResult)
|
||||
|
||||
|
||||
|
||||
@ -714,16 +718,49 @@ update key flags next msg model =
|
||||
( { model
|
||||
| itemMail = Comp.ItemMail.clear im
|
||||
, mailOpen = False
|
||||
, mailSendResult = Nothing
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Comp.ItemMail.FormSend sm ->
|
||||
Debug.todo "implement send"
|
||||
let
|
||||
mail =
|
||||
{ item = model.item.id
|
||||
, mail = sm.mail
|
||||
, conn = sm.conn
|
||||
}
|
||||
in
|
||||
( model, Api.sendMail flags mail SendMailResp )
|
||||
|
||||
ToggleMail ->
|
||||
( { model | mailOpen = not model.mailOpen }, Cmd.none )
|
||||
|
||||
SendMailResp (Ok br) ->
|
||||
let
|
||||
mm =
|
||||
if br.success then
|
||||
Comp.ItemMail.clear model.itemMail
|
||||
|
||||
else
|
||||
model.itemMail
|
||||
in
|
||||
( { model
|
||||
| itemMail = mm
|
||||
, mailSendResult = Just br
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SendMailResp (Err err) ->
|
||||
let
|
||||
errmsg =
|
||||
Util.Http.errorToString err
|
||||
in
|
||||
( { model | mailSendResult = Just (BasicResult False errmsg) }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- view
|
||||
@ -793,7 +830,7 @@ view inav model =
|
||||
, onClick ToggleMail
|
||||
, href "#"
|
||||
]
|
||||
[ i [ class "mail icon" ] []
|
||||
[ i [ class "mail outline icon" ] []
|
||||
]
|
||||
]
|
||||
, renderMailForm model
|
||||
@ -1258,4 +1295,23 @@ renderMailForm model =
|
||||
[ text "Send this item via E-Mail"
|
||||
]
|
||||
, Html.map ItemMailMsg (Comp.ItemMail.view model.itemMail)
|
||||
, div
|
||||
[ classList
|
||||
[ ( "ui message", True )
|
||||
, ( "error"
|
||||
, Maybe.map .success model.mailSendResult
|
||||
|> Maybe.map not
|
||||
|> Maybe.withDefault False
|
||||
)
|
||||
, ( "success"
|
||||
, Maybe.map .success model.mailSendResult
|
||||
|> Maybe.withDefault False
|
||||
)
|
||||
, ( "invisible hidden", model.mailSendResult == Nothing )
|
||||
]
|
||||
]
|
||||
[ Maybe.map .message model.mailSendResult
|
||||
|> Maybe.withDefault ""
|
||||
|> text
|
||||
]
|
||||
]
|
||||
|
@ -42,8 +42,14 @@ type Msg
|
||||
| Send
|
||||
|
||||
|
||||
type alias MailInfo =
|
||||
{ conn : String
|
||||
, mail : SimpleMail
|
||||
}
|
||||
|
||||
|
||||
type FormAction
|
||||
= FormSend SimpleMail
|
||||
= FormSend MailInfo
|
||||
| FormCancel
|
||||
| FormNone
|
||||
|
||||
@ -132,14 +138,19 @@ update msg model =
|
||||
( model, FormCancel )
|
||||
|
||||
Send ->
|
||||
let
|
||||
rec =
|
||||
String.split "," model.receiver
|
||||
case ( model.formError, Comp.Dropdown.getSelected model.connectionModel ) of
|
||||
( Nothing, conn :: [] ) ->
|
||||
let
|
||||
rec =
|
||||
String.split "," model.receiver
|
||||
|
||||
sm =
|
||||
SimpleMail rec model.subject model.body model.attachAll []
|
||||
in
|
||||
( model, FormSend sm )
|
||||
sm =
|
||||
SimpleMail rec model.subject model.body model.attachAll []
|
||||
in
|
||||
( model, FormSend { conn = conn, mail = sm } )
|
||||
|
||||
_ ->
|
||||
( model, FormNone )
|
||||
|
||||
|
||||
isValid : Model -> Bool
|
||||
|
Loading…
x
Reference in New Issue
Block a user