mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-02 13:32:51 +00:00
Add imap settings
This commit is contained in:
parent
2a5ffd391e
commit
c9de74fd91
@ -12,19 +12,29 @@ import docspell.common._
|
||||
import docspell.store._
|
||||
import docspell.store.records._
|
||||
import docspell.store.queries.QMails
|
||||
import OMail.{ItemMail, Sent, SmtpSettings}
|
||||
import OMail.{ImapSettings, ItemMail, Sent, SmtpSettings}
|
||||
|
||||
trait OMail[F[_]] {
|
||||
|
||||
def getSettings(accId: AccountId, nameQ: Option[String]): F[Vector[RUserEmail]]
|
||||
def getSmtpSettings(accId: AccountId, nameQ: Option[String]): F[Vector[RUserEmail]]
|
||||
|
||||
def findSettings(accId: AccountId, name: Ident): OptionT[F, RUserEmail]
|
||||
def findSmtpSettings(accId: AccountId, name: Ident): OptionT[F, RUserEmail]
|
||||
|
||||
def createSettings(accId: AccountId, data: SmtpSettings): F[AddResult]
|
||||
def createSmtpSettings(accId: AccountId, data: SmtpSettings): F[AddResult]
|
||||
|
||||
def updateSettings(accId: AccountId, name: Ident, data: OMail.SmtpSettings): F[Int]
|
||||
def updateSmtpSettings(accId: AccountId, name: Ident, data: OMail.SmtpSettings): F[Int]
|
||||
|
||||
def deleteSettings(accId: AccountId, name: Ident): F[Int]
|
||||
def deleteSmtpSettings(accId: AccountId, name: Ident): F[Int]
|
||||
|
||||
def getImapSettings(accId: AccountId, nameQ: Option[String]): F[Vector[RUserImap]]
|
||||
|
||||
def findImapSettings(accId: AccountId, name: Ident): OptionT[F, RUserImap]
|
||||
|
||||
def createImapSettings(accId: AccountId, data: ImapSettings): F[AddResult]
|
||||
|
||||
def updateImapSettings(accId: AccountId, name: Ident, data: OMail.ImapSettings): F[Int]
|
||||
|
||||
def deleteImapSettings(accId: AccountId, name: Ident): F[Int]
|
||||
|
||||
def sendMail(accId: AccountId, name: Ident, m: ItemMail): F[SendResult]
|
||||
|
||||
@ -103,15 +113,41 @@ object OMail {
|
||||
)
|
||||
}
|
||||
|
||||
case class ImapSettings(
|
||||
name: Ident,
|
||||
imapHost: String,
|
||||
imapPort: Option[Int],
|
||||
imapUser: Option[String],
|
||||
imapPassword: Option[Password],
|
||||
imapSsl: SSLType,
|
||||
imapCertCheck: Boolean
|
||||
) {
|
||||
|
||||
def toRecord(accId: AccountId) =
|
||||
RUserImap.fromAccount(
|
||||
accId,
|
||||
name,
|
||||
imapHost,
|
||||
imapPort,
|
||||
imapUser,
|
||||
imapPassword,
|
||||
imapSsl,
|
||||
imapCertCheck
|
||||
)
|
||||
}
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F], emil: Emil[F]): Resource[F, OMail[F]] =
|
||||
Resource.pure[F, OMail[F]](new OMail[F] {
|
||||
def getSettings(accId: AccountId, nameQ: Option[String]): F[Vector[RUserEmail]] =
|
||||
def getSmtpSettings(
|
||||
accId: AccountId,
|
||||
nameQ: Option[String]
|
||||
): F[Vector[RUserEmail]] =
|
||||
store.transact(RUserEmail.findByAccount(accId, nameQ))
|
||||
|
||||
def findSettings(accId: AccountId, name: Ident): OptionT[F, RUserEmail] =
|
||||
def findSmtpSettings(accId: AccountId, name: Ident): OptionT[F, RUserEmail] =
|
||||
OptionT(store.transact(RUserEmail.getByName(accId, name)))
|
||||
|
||||
def createSettings(accId: AccountId, s: SmtpSettings): F[AddResult] =
|
||||
def createSmtpSettings(accId: AccountId, s: SmtpSettings): F[AddResult] =
|
||||
(for {
|
||||
ru <- OptionT(store.transact(s.toRecord(accId).value))
|
||||
ins = RUserEmail.insert(ru)
|
||||
@ -119,7 +155,11 @@ object OMail {
|
||||
res <- OptionT.liftF(store.add(ins, exists))
|
||||
} yield res).getOrElse(AddResult.Failure(new Exception("User not found")))
|
||||
|
||||
def updateSettings(accId: AccountId, name: Ident, data: SmtpSettings): F[Int] = {
|
||||
def updateSmtpSettings(
|
||||
accId: AccountId,
|
||||
name: Ident,
|
||||
data: SmtpSettings
|
||||
): F[Int] = {
|
||||
val op = for {
|
||||
um <- OptionT(RUserEmail.getByName(accId, name))
|
||||
ru <- data.toRecord(accId)
|
||||
@ -129,12 +169,43 @@ object OMail {
|
||||
store.transact(op.value).map(_.getOrElse(0))
|
||||
}
|
||||
|
||||
def deleteSettings(accId: AccountId, name: Ident): F[Int] =
|
||||
def deleteSmtpSettings(accId: AccountId, name: Ident): F[Int] =
|
||||
store.transact(RUserEmail.delete(accId, name))
|
||||
|
||||
def getImapSettings(accId: AccountId, nameQ: Option[String]): F[Vector[RUserImap]] =
|
||||
store.transact(RUserImap.findByAccount(accId, nameQ))
|
||||
|
||||
def findImapSettings(accId: AccountId, name: Ident): OptionT[F, RUserImap] =
|
||||
OptionT(store.transact(RUserImap.getByName(accId, name)))
|
||||
|
||||
def createImapSettings(accId: AccountId, data: ImapSettings): F[AddResult] =
|
||||
(for {
|
||||
ru <- OptionT(store.transact(data.toRecord(accId).value))
|
||||
ins = RUserImap.insert(ru)
|
||||
exists = RUserImap.exists(ru.uid, ru.name)
|
||||
res <- OptionT.liftF(store.add(ins, exists))
|
||||
} yield res).getOrElse(AddResult.Failure(new Exception("User not found")))
|
||||
|
||||
def updateImapSettings(
|
||||
accId: AccountId,
|
||||
name: Ident,
|
||||
data: OMail.ImapSettings
|
||||
): F[Int] = {
|
||||
val op = for {
|
||||
um <- OptionT(RUserImap.getByName(accId, name))
|
||||
ru <- data.toRecord(accId)
|
||||
n <- OptionT.liftF(RUserImap.update(um.id, ru))
|
||||
} yield n
|
||||
|
||||
store.transact(op.value).map(_.getOrElse(0))
|
||||
}
|
||||
|
||||
def deleteImapSettings(accId: AccountId, name: Ident): F[Int] =
|
||||
store.transact(RUserImap.delete(accId, name))
|
||||
|
||||
def sendMail(accId: AccountId, name: Ident, m: ItemMail): F[SendResult] = {
|
||||
|
||||
val getSettings: OptionT[F, RUserEmail] =
|
||||
val getSmtpSettings: OptionT[F, RUserEmail] =
|
||||
OptionT(store.transact(RUserEmail.getByName(accId, name)))
|
||||
|
||||
def createMail(sett: RUserEmail): OptionT[F, Mail[F]] = {
|
||||
@ -198,7 +269,7 @@ object OMail {
|
||||
}
|
||||
|
||||
(for {
|
||||
mailCfg <- getSettings
|
||||
mailCfg <- getSmtpSettings
|
||||
mail <- createMail(mailCfg)
|
||||
mid <- OptionT.liftF(sendMail(mailCfg.toMailConfig, mail))
|
||||
res <- mid.traverse(id => OptionT.liftF(storeMail(id, mailCfg)))
|
||||
|
@ -1414,7 +1414,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/email/settings:
|
||||
/sec/email/settings/smtp:
|
||||
get:
|
||||
tags: [ E-Mail ]
|
||||
summary: List email settings for current user.
|
||||
@ -1456,7 +1456,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/email/settings/{name}:
|
||||
/sec/email/settings/smtp/{name}:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/name"
|
||||
get:
|
||||
@ -1507,6 +1507,99 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/email/settings/imap:
|
||||
get:
|
||||
tags: [ E-Mail ]
|
||||
summary: List email settings for current user.
|
||||
description: |
|
||||
List all available e-mail settings for the current user.
|
||||
E-Mail settings specify imap connections that can be used to
|
||||
retrieve e-mails.
|
||||
|
||||
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:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ImapSettingsList"
|
||||
post:
|
||||
tags: [ E-Mail ]
|
||||
summary: Create new email settings
|
||||
description: |
|
||||
Create new e-mail settings.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ImapSettings"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/email/settings/imap/{name}:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/name"
|
||||
get:
|
||||
tags: [ E-Mail ]
|
||||
summary: Return specific email settings by name.
|
||||
description: |
|
||||
Return the stored e-mail settings for the given connection
|
||||
name.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ImapSettings"
|
||||
put:
|
||||
tags: [ E-Mail ]
|
||||
summary: Change specific email settings.
|
||||
description: |
|
||||
Changes all settings for the connection with the given `name`.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ImapSettings"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
delete:
|
||||
tags: [ E-Mail ]
|
||||
summary: Delete e-mail settings.
|
||||
description: |
|
||||
Deletes the e-mail settings with the specified `name`.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
|
||||
/sec/email/send/{name}/{id}:
|
||||
post:
|
||||
@ -1664,6 +1757,43 @@ paths:
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ImapSettingsList:
|
||||
description: |
|
||||
A list of user email settings.
|
||||
required:
|
||||
- items
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ImapSettings"
|
||||
ImapSettings:
|
||||
description: |
|
||||
IMAP settings for sending mail.
|
||||
required:
|
||||
- name
|
||||
- imapHost
|
||||
- from
|
||||
- sslType
|
||||
- ignoreCertificates
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
format: ident
|
||||
imapHost:
|
||||
type: string
|
||||
imapPort:
|
||||
type: integer
|
||||
format: int32
|
||||
imapUser:
|
||||
type: string
|
||||
imapPassword:
|
||||
type: string
|
||||
format: password
|
||||
sslType:
|
||||
type: string
|
||||
ignoreCertificates:
|
||||
type: boolean
|
||||
CalEventCheckResult:
|
||||
description: |
|
||||
The result of checking a calendar event string.
|
||||
|
@ -15,7 +15,7 @@ import docspell.backend.auth.AuthToken
|
||||
import docspell.backend.ops.OMail
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
import docspell.store.records.RUserEmail
|
||||
import docspell.store.records.{RUserEmail, RUserImap}
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.restserver.http4s.QueryParam
|
||||
|
||||
@ -26,25 +26,38 @@ object MailSettingsRoutes {
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? QueryParam.QueryOpt(q) =>
|
||||
case GET -> Root / "smtp" :? QueryParam.QueryOpt(q) =>
|
||||
for {
|
||||
list <- backend.mail.getSettings(user.account, q.map(_.q))
|
||||
list <- backend.mail.getSmtpSettings(user.account, q.map(_.q))
|
||||
res = list.map(convert)
|
||||
resp <- Ok(EmailSettingsList(res.toList))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(name) =>
|
||||
case GET -> Root / "imap" :? QueryParam.QueryOpt(q) =>
|
||||
for {
|
||||
list <- backend.mail.getImapSettings(user.account, q.map(_.q))
|
||||
res = list.map(convert)
|
||||
resp <- Ok(ImapSettingsList(res.toList))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / "smtp" / Ident(name) =>
|
||||
(for {
|
||||
ems <- backend.mail.findSettings(user.account, name)
|
||||
ems <- backend.mail.findSmtpSettings(user.account, name)
|
||||
resp <- OptionT.liftF(Ok(convert(ems)))
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ POST -> Root =>
|
||||
case GET -> Root / "imap" / Ident(name) =>
|
||||
(for {
|
||||
ems <- backend.mail.findImapSettings(user.account, name)
|
||||
resp <- OptionT.liftF(Ok(convert(ems)))
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ POST -> Root / "smtp" =>
|
||||
(for {
|
||||
in <- OptionT.liftF(req.as[EmailSettings])
|
||||
ru = makeSettings(in)
|
||||
ru = makeSmtpSettings(in)
|
||||
up <- OptionT.liftF(
|
||||
ru.traverse(r => backend.mail.createSettings(user.account, r))
|
||||
ru.traverse(r => backend.mail.createSmtpSettings(user.account, r))
|
||||
)
|
||||
resp <- OptionT.liftF(
|
||||
Ok(
|
||||
@ -56,12 +69,29 @@ object MailSettingsRoutes {
|
||||
)
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ PUT -> Root / Ident(name) =>
|
||||
case req @ POST -> Root / "imap" =>
|
||||
(for {
|
||||
in <- OptionT.liftF(req.as[ImapSettings])
|
||||
ru = makeImapSettings(in)
|
||||
up <- OptionT.liftF(
|
||||
ru.traverse(r => backend.mail.createImapSettings(user.account, r))
|
||||
)
|
||||
resp <- OptionT.liftF(
|
||||
Ok(
|
||||
up.fold(
|
||||
err => BasicResult(false, err),
|
||||
ar => Conversions.basicResult(ar, "Mail settings stored.")
|
||||
)
|
||||
)
|
||||
)
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ PUT -> Root / "smtp" / Ident(name) =>
|
||||
(for {
|
||||
in <- OptionT.liftF(req.as[EmailSettings])
|
||||
ru = makeSettings(in)
|
||||
ru = makeSmtpSettings(in)
|
||||
up <- OptionT.liftF(
|
||||
ru.traverse(r => backend.mail.updateSettings(user.account, name, r))
|
||||
ru.traverse(r => backend.mail.updateSmtpSettings(user.account, name, r))
|
||||
)
|
||||
resp <- OptionT.liftF(
|
||||
Ok(
|
||||
@ -75,16 +105,43 @@ object MailSettingsRoutes {
|
||||
)
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case DELETE -> Root / Ident(name) =>
|
||||
case req @ PUT -> Root / "imap" / Ident(name) =>
|
||||
(for {
|
||||
in <- OptionT.liftF(req.as[ImapSettings])
|
||||
ru = makeImapSettings(in)
|
||||
up <- OptionT.liftF(
|
||||
ru.traverse(r => backend.mail.updateImapSettings(user.account, name, r))
|
||||
)
|
||||
resp <- OptionT.liftF(
|
||||
Ok(
|
||||
up.fold(
|
||||
err => BasicResult(false, err),
|
||||
n =>
|
||||
if (n > 0) BasicResult(true, "Mail settings stored.")
|
||||
else BasicResult(false, "Mail settings could not be saved")
|
||||
)
|
||||
)
|
||||
)
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case DELETE -> Root / "smtp" / Ident(name) =>
|
||||
for {
|
||||
n <- backend.mail.deleteSettings(user.account, name)
|
||||
n <- backend.mail.deleteSmtpSettings(user.account, name)
|
||||
resp <- Ok(
|
||||
if (n > 0) BasicResult(true, "Mail settings removed")
|
||||
else BasicResult(false, "Mail settings could not be removed")
|
||||
)
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / "imap" / Ident(name) =>
|
||||
for {
|
||||
n <- backend.mail.deleteImapSettings(user.account, name)
|
||||
resp <- Ok(
|
||||
if (n > 0) BasicResult(true, "Mail settings removed")
|
||||
else BasicResult(false, "Mail settings could not be removed")
|
||||
)
|
||||
} yield resp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def convert(ru: RUserEmail): EmailSettings =
|
||||
@ -100,7 +157,18 @@ object MailSettingsRoutes {
|
||||
!ru.smtpCertCheck
|
||||
)
|
||||
|
||||
def makeSettings(ems: EmailSettings): Either[String, OMail.SmtpSettings] = {
|
||||
def convert(ru: RUserImap): ImapSettings =
|
||||
ImapSettings(
|
||||
ru.name,
|
||||
ru.imapHost,
|
||||
ru.imapPort,
|
||||
ru.imapUser,
|
||||
ru.imapPassword,
|
||||
ru.imapSsl.name,
|
||||
!ru.imapCertCheck
|
||||
)
|
||||
|
||||
def makeSmtpSettings(ems: EmailSettings): Either[String, OMail.SmtpSettings] = {
|
||||
def readMail(str: String): Either[String, MailAddress] =
|
||||
MailAddress.parse(str).left.map(err => s"E-Mail address '$str' invalid: $err")
|
||||
|
||||
@ -122,6 +190,18 @@ object MailSettingsRoutes {
|
||||
from,
|
||||
repl
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
def makeImapSettings(ims: ImapSettings): Either[String, OMail.ImapSettings] =
|
||||
for {
|
||||
sslt <- SSLType.fromString(ims.sslType)
|
||||
} yield OMail.ImapSettings(
|
||||
ims.name,
|
||||
ims.imapHost,
|
||||
ims.imapPort,
|
||||
ims.imapUser,
|
||||
ims.imapPassword,
|
||||
sslt,
|
||||
!ims.ignoreCertificates
|
||||
)
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ object NotifyDueItemsRoutes {
|
||||
texc <- backend.tag.loadAll(task.args.tagsExclude)
|
||||
conn <-
|
||||
backend.mail
|
||||
.getSettings(account, None)
|
||||
.getSmtpSettings(account, None)
|
||||
.map(
|
||||
_.find(_.name == task.args.smtpConnection)
|
||||
.map(_.name)
|
||||
|
@ -0,0 +1,14 @@
|
||||
CREATE TABLE `userimap` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`name` varchar(254) not null,
|
||||
`imap_host` varchar(254) not null,
|
||||
`imap_port` int,
|
||||
`imap_user` varchar(254),
|
||||
`imap_password` varchar(254),
|
||||
`imap_ssl` varchar(254) not null,
|
||||
`imap_certcheck` boolean not null,
|
||||
`created` timestamp not null,
|
||||
unique (`uid`, `name`),
|
||||
foreign key (`uid`) references `user_`(`uid`)
|
||||
);
|
@ -0,0 +1,14 @@
|
||||
CREATE TABLE "userimap" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"name" varchar(254) not null,
|
||||
"imap_host" varchar(254) not null,
|
||||
"imap_port" int,
|
||||
"imap_user" varchar(254),
|
||||
"imap_password" varchar(254),
|
||||
"imap_ssl" varchar(254) not null,
|
||||
"imap_certcheck" boolean not null,
|
||||
"created" timestamp not null,
|
||||
unique ("uid", "name"),
|
||||
foreign key ("uid") references "user_"("uid")
|
||||
);
|
@ -0,0 +1,201 @@
|
||||
package docspell.store.records
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
import cats.data.OptionT
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
import emil.{MailConfig, SSLType}
|
||||
|
||||
case class RUserImap(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
name: Ident,
|
||||
imapHost: String,
|
||||
imapPort: Option[Int],
|
||||
imapUser: Option[String],
|
||||
imapPassword: Option[Password],
|
||||
imapSsl: SSLType,
|
||||
imapCertCheck: Boolean,
|
||||
created: Timestamp
|
||||
) {
|
||||
|
||||
def toMailConfig: MailConfig = {
|
||||
val port = imapPort.map(p => s":$p").getOrElse("")
|
||||
MailConfig(
|
||||
s"imap://$imapHost$port",
|
||||
imapUser.getOrElse(""),
|
||||
imapPassword.map(_.pass).getOrElse(""),
|
||||
imapSsl,
|
||||
!imapCertCheck
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object RUserImap {
|
||||
|
||||
def apply[F[_]: Sync](
|
||||
uid: Ident,
|
||||
name: Ident,
|
||||
imapHost: String,
|
||||
imapPort: Option[Int],
|
||||
imapUser: Option[String],
|
||||
imapPassword: Option[Password],
|
||||
imapSsl: SSLType,
|
||||
imapCertCheck: Boolean
|
||||
): F[RUserImap] =
|
||||
for {
|
||||
now <- Timestamp.current[F]
|
||||
id <- Ident.randomId[F]
|
||||
} yield RUserImap(
|
||||
id,
|
||||
uid,
|
||||
name,
|
||||
imapHost,
|
||||
imapPort,
|
||||
imapUser,
|
||||
imapPassword,
|
||||
imapSsl,
|
||||
imapCertCheck,
|
||||
now
|
||||
)
|
||||
|
||||
def fromAccount(
|
||||
accId: AccountId,
|
||||
name: Ident,
|
||||
imapHost: String,
|
||||
imapPort: Option[Int],
|
||||
imapUser: Option[String],
|
||||
imapPassword: Option[Password],
|
||||
imapSsl: SSLType,
|
||||
imapCertCheck: Boolean
|
||||
): OptionT[ConnectionIO, RUserImap] =
|
||||
for {
|
||||
now <- OptionT.liftF(Timestamp.current[ConnectionIO])
|
||||
id <- OptionT.liftF(Ident.randomId[ConnectionIO])
|
||||
user <- OptionT(RUser.findByAccount(accId))
|
||||
} yield RUserImap(
|
||||
id,
|
||||
user.uid,
|
||||
name,
|
||||
imapHost,
|
||||
imapPort,
|
||||
imapUser,
|
||||
imapPassword,
|
||||
imapSsl,
|
||||
imapCertCheck,
|
||||
now
|
||||
)
|
||||
|
||||
val table = fr"userimap"
|
||||
|
||||
object Columns {
|
||||
val id = Column("id")
|
||||
val uid = Column("uid")
|
||||
val name = Column("name")
|
||||
val imapHost = Column("imap_host")
|
||||
val imapPort = Column("imap_port")
|
||||
val imapUser = Column("imap_user")
|
||||
val imapPass = Column("imap_password")
|
||||
val imapSsl = Column("imap_ssl")
|
||||
val imapCertCheck = Column("imap_certcheck")
|
||||
val created = Column("created")
|
||||
|
||||
val all = List(
|
||||
id,
|
||||
uid,
|
||||
name,
|
||||
imapHost,
|
||||
imapPort,
|
||||
imapUser,
|
||||
imapPass,
|
||||
imapSsl,
|
||||
imapCertCheck,
|
||||
created
|
||||
)
|
||||
}
|
||||
|
||||
import Columns._
|
||||
|
||||
def insert(v: RUserImap): ConnectionIO[Int] =
|
||||
insertRow(
|
||||
table,
|
||||
all,
|
||||
sql"${v.id},${v.uid},${v.name},${v.imapHost},${v.imapPort},${v.imapUser},${v.imapPassword},${v.imapSsl},${v.imapCertCheck},${v.created}"
|
||||
).update.run
|
||||
|
||||
def update(eId: Ident, v: RUserImap): ConnectionIO[Int] =
|
||||
updateRow(
|
||||
table,
|
||||
id.is(eId),
|
||||
commas(
|
||||
name.setTo(v.name),
|
||||
imapHost.setTo(v.imapHost),
|
||||
imapPort.setTo(v.imapPort),
|
||||
imapUser.setTo(v.imapUser),
|
||||
imapPass.setTo(v.imapPassword),
|
||||
imapSsl.setTo(v.imapSsl),
|
||||
imapCertCheck.setTo(v.imapCertCheck)
|
||||
)
|
||||
).update.run
|
||||
|
||||
def findByUser(userId: Ident): ConnectionIO[Vector[RUserImap]] =
|
||||
selectSimple(all, table, uid.is(userId)).query[RUserImap].to[Vector]
|
||||
|
||||
private def findByAccount0(
|
||||
accId: AccountId,
|
||||
nameQ: Option[String],
|
||||
exact: Boolean
|
||||
): Query0[RUserImap] = {
|
||||
val mUid = uid.prefix("m")
|
||||
val mName = name.prefix("m")
|
||||
val uId = RUser.Columns.uid.prefix("u")
|
||||
val uColl = RUser.Columns.cid.prefix("u")
|
||||
val uLogin = RUser.Columns.login.prefix("u")
|
||||
val from = table ++ fr"m INNER JOIN" ++ RUser.table ++ fr"u ON" ++ mUid.is(uId)
|
||||
val cond = Seq(uColl.is(accId.collective), uLogin.is(accId.user)) ++ (nameQ match {
|
||||
case Some(str) if exact => Seq(mName.is(str))
|
||||
case Some(str) if !exact => Seq(mName.lowerLike(s"%${str.toLowerCase}%"))
|
||||
case None => Seq.empty
|
||||
})
|
||||
|
||||
(selectSimple(all.map(_.prefix("m")), from, and(cond)) ++ orderBy(mName.f))
|
||||
.query[RUserImap]
|
||||
}
|
||||
|
||||
def findByAccount(
|
||||
accId: AccountId,
|
||||
nameQ: Option[String]
|
||||
): ConnectionIO[Vector[RUserImap]] =
|
||||
findByAccount0(accId, nameQ, false).to[Vector]
|
||||
|
||||
def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserImap]] =
|
||||
findByAccount0(accId, Some(name.id), true).option
|
||||
|
||||
def delete(accId: AccountId, connName: Ident): ConnectionIO[Int] = {
|
||||
val uId = RUser.Columns.uid
|
||||
val uColl = RUser.Columns.cid
|
||||
val uLogin = RUser.Columns.login
|
||||
val cond = Seq(uColl.is(accId.collective), uLogin.is(accId.user))
|
||||
|
||||
deleteFrom(
|
||||
table,
|
||||
fr"uid in (" ++ selectSimple(Seq(uId), RUser.table, and(cond)) ++ fr") AND" ++ name
|
||||
.is(
|
||||
connName
|
||||
)
|
||||
).update.run
|
||||
}
|
||||
|
||||
def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] =
|
||||
getByName(accId, name).map(_.isDefined)
|
||||
|
||||
def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] =
|
||||
selectCount(id, table, and(uid.is(userId), name.is(connName)))
|
||||
.query[Int]
|
||||
.unique
|
||||
.map(_ > 0)
|
||||
}
|
@ -2,9 +2,11 @@ module Api exposing
|
||||
( cancelJob
|
||||
, changePassword
|
||||
, checkCalEvent
|
||||
, createImapSettings
|
||||
, createMailSettings
|
||||
, deleteAttachment
|
||||
, deleteEquip
|
||||
, deleteImapSettings
|
||||
, deleteItem
|
||||
, deleteMailSettings
|
||||
, deleteOrg
|
||||
@ -17,6 +19,7 @@ module Api exposing
|
||||
, getCollectiveSettings
|
||||
, getContacts
|
||||
, getEquipments
|
||||
, getImapSettings
|
||||
, getInsights
|
||||
, getItemProposals
|
||||
, getJobQueueState
|
||||
@ -81,6 +84,8 @@ import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.Equipment exposing (Equipment)
|
||||
import Api.Model.EquipmentList exposing (EquipmentList)
|
||||
import Api.Model.GenInvite exposing (GenInvite)
|
||||
import Api.Model.ImapSettings exposing (ImapSettings)
|
||||
import Api.Model.ImapSettingsList exposing (ImapSettingsList)
|
||||
import Api.Model.InviteResult exposing (InviteResult)
|
||||
import Api.Model.ItemDetail exposing (ItemDetail)
|
||||
import Api.Model.ItemInsights exposing (ItemInsights)
|
||||
@ -259,7 +264,16 @@ sendMail flags opts receive =
|
||||
deleteMailSettings : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
deleteMailSettings flags name receive =
|
||||
Http2.authDelete
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/" ++ name
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/smtp/" ++ name
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
deleteImapSettings : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
deleteImapSettings flags name receive =
|
||||
Http2.authDelete
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/imap/" ++ name
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
@ -268,12 +282,21 @@ deleteMailSettings flags name receive =
|
||||
getMailSettings : Flags -> String -> (Result Http.Error EmailSettingsList -> msg) -> Cmd msg
|
||||
getMailSettings flags query receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings?q=" ++ Url.percentEncode query
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/smtp?q=" ++ Url.percentEncode query
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.EmailSettingsList.decoder
|
||||
}
|
||||
|
||||
|
||||
getImapSettings : Flags -> String -> (Result Http.Error ImapSettingsList -> msg) -> Cmd msg
|
||||
getImapSettings flags query receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/imap?q=" ++ Url.percentEncode query
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.ImapSettingsList.decoder
|
||||
}
|
||||
|
||||
|
||||
createMailSettings :
|
||||
Flags
|
||||
-> Maybe String
|
||||
@ -284,7 +307,7 @@ createMailSettings flags mname ems receive =
|
||||
case mname of
|
||||
Just en ->
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/" ++ en
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/smtp/" ++ en
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.EmailSettings.encode ems)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
@ -292,13 +315,38 @@ createMailSettings flags mname ems receive =
|
||||
|
||||
Nothing ->
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings"
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/smtp"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.EmailSettings.encode ems)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
createImapSettings :
|
||||
Flags
|
||||
-> Maybe String
|
||||
-> ImapSettings
|
||||
-> (Result Http.Error BasicResult -> msg)
|
||||
-> Cmd msg
|
||||
createImapSettings flags mname ems receive =
|
||||
case mname of
|
||||
Just en ->
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/imap/" ++ en
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.ImapSettings.encode ems)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/email/settings/imap"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.ImapSettings.encode ems)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Upload
|
||||
|
||||
|
226
modules/webapp/src/main/elm/Comp/ImapSettingsForm.elm
Normal file
226
modules/webapp/src/main/elm/Comp/ImapSettingsForm.elm
Normal file
@ -0,0 +1,226 @@
|
||||
module Comp.ImapSettingsForm exposing
|
||||
( Model
|
||||
, Msg
|
||||
, emptyModel
|
||||
, getSettings
|
||||
, init
|
||||
, isValid
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.ImapSettings exposing (ImapSettings)
|
||||
import Comp.Dropdown
|
||||
import Comp.IntField
|
||||
import Comp.PasswordInput
|
||||
import Data.SSLType exposing (SSLType)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onCheck, onInput)
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ settings : ImapSettings
|
||||
, name : String
|
||||
, host : String
|
||||
, portField : Comp.IntField.Model
|
||||
, portNum : Maybe Int
|
||||
, user : Maybe String
|
||||
, passField : Comp.PasswordInput.Model
|
||||
, password : Maybe String
|
||||
, sslType : Comp.Dropdown.Model SSLType
|
||||
, ignoreCertificates : Bool
|
||||
}
|
||||
|
||||
|
||||
emptyModel : Model
|
||||
emptyModel =
|
||||
{ settings = Api.Model.ImapSettings.empty
|
||||
, name = ""
|
||||
, host = ""
|
||||
, portField = Comp.IntField.init (Just 0) Nothing True "IMAP Port"
|
||||
, portNum = Nothing
|
||||
, user = Nothing
|
||||
, passField = Comp.PasswordInput.init
|
||||
, password = Nothing
|
||||
, sslType =
|
||||
Comp.Dropdown.makeSingleList
|
||||
{ makeOption = \s -> { value = Data.SSLType.toString s, text = Data.SSLType.label s }
|
||||
, placeholder = ""
|
||||
, options = Data.SSLType.all
|
||||
, selected = Just Data.SSLType.None
|
||||
}
|
||||
, ignoreCertificates = False
|
||||
}
|
||||
|
||||
|
||||
init : ImapSettings -> Model
|
||||
init ems =
|
||||
{ settings = ems
|
||||
, name = ems.name
|
||||
, host = ems.imapHost
|
||||
, portField = Comp.IntField.init (Just 0) Nothing True "IMAP Port"
|
||||
, portNum = ems.imapPort
|
||||
, user = ems.imapUser
|
||||
, passField = Comp.PasswordInput.init
|
||||
, password = ems.imapPassword
|
||||
, sslType =
|
||||
Comp.Dropdown.makeSingleList
|
||||
{ makeOption = \s -> { value = Data.SSLType.toString s, text = Data.SSLType.label s }
|
||||
, placeholder = ""
|
||||
, options = Data.SSLType.all
|
||||
, selected =
|
||||
Data.SSLType.fromString ems.sslType
|
||||
|> Maybe.withDefault Data.SSLType.None
|
||||
|> Just
|
||||
}
|
||||
, ignoreCertificates = ems.ignoreCertificates
|
||||
}
|
||||
|
||||
|
||||
getSettings : Model -> ( Maybe String, ImapSettings )
|
||||
getSettings model =
|
||||
( Util.Maybe.fromString model.settings.name
|
||||
, { name = model.name
|
||||
, imapHost = model.host
|
||||
, imapUser = model.user
|
||||
, imapPort = model.portNum
|
||||
, imapPassword = model.password
|
||||
, sslType =
|
||||
Comp.Dropdown.getSelected model.sslType
|
||||
|> List.head
|
||||
|> Maybe.withDefault Data.SSLType.None
|
||||
|> Data.SSLType.toString
|
||||
, ignoreCertificates = model.ignoreCertificates
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
type Msg
|
||||
= SetName String
|
||||
| SetHost String
|
||||
| PortMsg Comp.IntField.Msg
|
||||
| SetUser String
|
||||
| PassMsg Comp.PasswordInput.Msg
|
||||
| SSLTypeMsg (Comp.Dropdown.Msg SSLType)
|
||||
| ToggleCheckCert
|
||||
|
||||
|
||||
isValid : Model -> Bool
|
||||
isValid model =
|
||||
model.host /= "" && model.name /= ""
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
SetName str ->
|
||||
( { model | name = str }, Cmd.none )
|
||||
|
||||
SetHost str ->
|
||||
( { model | host = str }, Cmd.none )
|
||||
|
||||
PortMsg m ->
|
||||
let
|
||||
( pm, val ) =
|
||||
Comp.IntField.update m model.portField
|
||||
in
|
||||
( { model | portField = pm, portNum = val }, Cmd.none )
|
||||
|
||||
SetUser str ->
|
||||
( { model | user = Util.Maybe.fromString str }, Cmd.none )
|
||||
|
||||
PassMsg m ->
|
||||
let
|
||||
( pm, val ) =
|
||||
Comp.PasswordInput.update m model.passField
|
||||
in
|
||||
( { model | passField = pm, password = val }, Cmd.none )
|
||||
|
||||
SSLTypeMsg m ->
|
||||
let
|
||||
( sm, sc ) =
|
||||
Comp.Dropdown.update m model.sslType
|
||||
in
|
||||
( { model | sslType = sm }, Cmd.map SSLTypeMsg sc )
|
||||
|
||||
ToggleCheckCert ->
|
||||
( { model | ignoreCertificates = not model.ignoreCertificates }, Cmd.none )
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div
|
||||
[ classList
|
||||
[ ( "ui form", True )
|
||||
, ( "error", not (isValid model) )
|
||||
, ( "success", isValid model )
|
||||
]
|
||||
]
|
||||
[ div [ class "required field" ]
|
||||
[ label [] [ text "Name" ]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, value model.name
|
||||
, onInput SetName
|
||||
, placeholder "Connection name, e.g. 'gmail.com'"
|
||||
]
|
||||
[]
|
||||
, div [ class "ui info message" ]
|
||||
[ text "The connection name must not contain whitespace or special characters."
|
||||
]
|
||||
]
|
||||
, div [ class "fields" ]
|
||||
[ div [ class "thirteen wide required field" ]
|
||||
[ label [] [ text "IMAP Host" ]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, placeholder "IMAP host name, e.g. 'mail.gmail.com'"
|
||||
, value model.host
|
||||
, onInput SetHost
|
||||
]
|
||||
[]
|
||||
]
|
||||
, Html.map PortMsg
|
||||
(Comp.IntField.view model.portNum
|
||||
"three wide field"
|
||||
model.portField
|
||||
)
|
||||
]
|
||||
, div [ class "two fields" ]
|
||||
[ div [ class "field" ]
|
||||
[ label [] [ text "IMAP User" ]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, placeholder "IMAP Username, e.g. 'your.name@gmail.com'"
|
||||
, Maybe.withDefault "" model.user |> value
|
||||
, onInput SetUser
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div [ class "field" ]
|
||||
[ label [] [ text "IMAP Password" ]
|
||||
, Html.map PassMsg (Comp.PasswordInput.view model.password model.passField)
|
||||
]
|
||||
]
|
||||
, div [ class "two fields" ]
|
||||
[ div [ class "inline field" ]
|
||||
[ div [ class "ui checkbox" ]
|
||||
[ input
|
||||
[ type_ "checkbox"
|
||||
, checked model.ignoreCertificates
|
||||
, onCheck (\_ -> ToggleCheckCert)
|
||||
]
|
||||
[]
|
||||
, label [] [ text "Ignore certificate check" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "two fields" ]
|
||||
[ div [ class "field" ]
|
||||
[ label [] [ text "SSL" ]
|
||||
, Html.map SSLTypeMsg (Comp.Dropdown.view model.sslType)
|
||||
]
|
||||
]
|
||||
]
|
288
modules/webapp/src/main/elm/Comp/ImapSettingsManage.elm
Normal file
288
modules/webapp/src/main/elm/Comp/ImapSettingsManage.elm
Normal file
@ -0,0 +1,288 @@
|
||||
module Comp.ImapSettingsManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, emptyModel
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.ImapSettings
|
||||
import Api.Model.ImapSettingsList exposing (ImapSettingsList)
|
||||
import Comp.ImapSettingsForm
|
||||
import Comp.ImapSettingsTable
|
||||
import Comp.YesNoDimmer
|
||||
import Data.Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Http
|
||||
import Util.Http
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ tableModel : Comp.ImapSettingsTable.Model
|
||||
, formModel : Comp.ImapSettingsForm.Model
|
||||
, viewMode : ViewMode
|
||||
, formError : Maybe String
|
||||
, loading : Bool
|
||||
, query : String
|
||||
, deleteConfirm : Comp.YesNoDimmer.Model
|
||||
}
|
||||
|
||||
|
||||
emptyModel : Model
|
||||
emptyModel =
|
||||
{ tableModel = Comp.ImapSettingsTable.emptyModel
|
||||
, formModel = Comp.ImapSettingsForm.emptyModel
|
||||
, viewMode = Table
|
||||
, formError = Nothing
|
||||
, loading = False
|
||||
, query = ""
|
||||
, deleteConfirm = Comp.YesNoDimmer.emptyModel
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( emptyModel, Api.getImapSettings flags "" MailSettingsResp )
|
||||
|
||||
|
||||
type ViewMode
|
||||
= Table
|
||||
| Form
|
||||
|
||||
|
||||
type Msg
|
||||
= TableMsg Comp.ImapSettingsTable.Msg
|
||||
| FormMsg Comp.ImapSettingsForm.Msg
|
||||
| SetQuery String
|
||||
| InitNew
|
||||
| YesNoMsg Comp.YesNoDimmer.Msg
|
||||
| RequestDelete
|
||||
| SetViewMode ViewMode
|
||||
| Submit
|
||||
| SubmitResp (Result Http.Error BasicResult)
|
||||
| LoadSettings
|
||||
| MailSettingsResp (Result Http.Error ImapSettingsList)
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
InitNew ->
|
||||
let
|
||||
ems =
|
||||
Api.Model.ImapSettings.empty
|
||||
|
||||
nm =
|
||||
{ model
|
||||
| viewMode = Form
|
||||
, formError = Nothing
|
||||
, formModel = Comp.ImapSettingsForm.init ems
|
||||
}
|
||||
in
|
||||
( nm, Cmd.none )
|
||||
|
||||
TableMsg m ->
|
||||
let
|
||||
( tm, tc ) =
|
||||
Comp.ImapSettingsTable.update m model.tableModel
|
||||
|
||||
m2 =
|
||||
{ model
|
||||
| tableModel = tm
|
||||
, viewMode = Maybe.map (\_ -> Form) tm.selected |> Maybe.withDefault Table
|
||||
, formError =
|
||||
if tm.selected /= Nothing then
|
||||
Nothing
|
||||
|
||||
else
|
||||
model.formError
|
||||
, formModel =
|
||||
case tm.selected of
|
||||
Just ems ->
|
||||
Comp.ImapSettingsForm.init ems
|
||||
|
||||
Nothing ->
|
||||
model.formModel
|
||||
}
|
||||
in
|
||||
( m2, Cmd.map TableMsg tc )
|
||||
|
||||
FormMsg m ->
|
||||
let
|
||||
( fm, fc ) =
|
||||
Comp.ImapSettingsForm.update m model.formModel
|
||||
in
|
||||
( { model | formModel = fm }, Cmd.map FormMsg fc )
|
||||
|
||||
SetQuery str ->
|
||||
let
|
||||
m =
|
||||
{ model | query = str }
|
||||
in
|
||||
( m, Api.getImapSettings flags str MailSettingsResp )
|
||||
|
||||
YesNoMsg m ->
|
||||
let
|
||||
( dm, flag ) =
|
||||
Comp.YesNoDimmer.update m model.deleteConfirm
|
||||
|
||||
( mid, _ ) =
|
||||
Comp.ImapSettingsForm.getSettings model.formModel
|
||||
|
||||
cmd =
|
||||
case ( flag, mid ) of
|
||||
( True, Just name ) ->
|
||||
Api.deleteImapSettings flags name SubmitResp
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
||||
in
|
||||
( { model | deleteConfirm = dm }, cmd )
|
||||
|
||||
RequestDelete ->
|
||||
update flags (YesNoMsg Comp.YesNoDimmer.activate) model
|
||||
|
||||
SetViewMode m ->
|
||||
( { model | viewMode = m }, Cmd.none )
|
||||
|
||||
Submit ->
|
||||
let
|
||||
( mid, ems ) =
|
||||
Comp.ImapSettingsForm.getSettings model.formModel
|
||||
|
||||
valid =
|
||||
Comp.ImapSettingsForm.isValid model.formModel
|
||||
in
|
||||
if valid then
|
||||
( { model | loading = True }, Api.createImapSettings flags mid ems SubmitResp )
|
||||
|
||||
else
|
||||
( { model | formError = Just "Please fill required fields." }, Cmd.none )
|
||||
|
||||
LoadSettings ->
|
||||
( { model | loading = True }, Api.getImapSettings flags model.query MailSettingsResp )
|
||||
|
||||
SubmitResp (Ok res) ->
|
||||
if res.success then
|
||||
let
|
||||
( m2, c2 ) =
|
||||
update flags (SetViewMode Table) model
|
||||
|
||||
( m3, c3 ) =
|
||||
update flags LoadSettings m2
|
||||
in
|
||||
( { m3 | loading = False }, Cmd.batch [ c2, c3 ] )
|
||||
|
||||
else
|
||||
( { model | formError = Just res.message, loading = False }, Cmd.none )
|
||||
|
||||
SubmitResp (Err err) ->
|
||||
( { model | formError = Just (Util.Http.errorToString err), loading = False }, Cmd.none )
|
||||
|
||||
MailSettingsResp (Ok ems) ->
|
||||
let
|
||||
m2 =
|
||||
{ model
|
||||
| viewMode = Table
|
||||
, loading = False
|
||||
, tableModel = Comp.ImapSettingsTable.init ems.items
|
||||
}
|
||||
in
|
||||
( m2, Cmd.none )
|
||||
|
||||
MailSettingsResp (Err _) ->
|
||||
( { model | loading = False }, Cmd.none )
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
case model.viewMode of
|
||||
Table ->
|
||||
viewTable model
|
||||
|
||||
Form ->
|
||||
viewForm model
|
||||
|
||||
|
||||
viewTable : Model -> Html Msg
|
||||
viewTable model =
|
||||
div []
|
||||
[ div [ class "ui secondary menu" ]
|
||||
[ div [ class "horizontally fitted item" ]
|
||||
[ div [ class "ui icon input" ]
|
||||
[ input
|
||||
[ type_ "text"
|
||||
, onInput SetQuery
|
||||
, value model.query
|
||||
, placeholder "Search…"
|
||||
]
|
||||
[]
|
||||
, i [ class "ui search icon" ]
|
||||
[]
|
||||
]
|
||||
]
|
||||
, div [ class "right menu" ]
|
||||
[ div [ class "item" ]
|
||||
[ a
|
||||
[ class "ui primary button"
|
||||
, href "#"
|
||||
, onClick InitNew
|
||||
]
|
||||
[ i [ class "plus icon" ] []
|
||||
, text "New Settings"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, Html.map TableMsg (Comp.ImapSettingsTable.view model.tableModel)
|
||||
]
|
||||
|
||||
|
||||
viewForm : Model -> Html Msg
|
||||
viewForm model =
|
||||
div [ class "ui segment" ]
|
||||
[ Html.map YesNoMsg (Comp.YesNoDimmer.view model.deleteConfirm)
|
||||
, Html.map FormMsg (Comp.ImapSettingsForm.view model.formModel)
|
||||
, div
|
||||
[ classList
|
||||
[ ( "ui error message", True )
|
||||
, ( "invisible", model.formError == Nothing )
|
||||
]
|
||||
]
|
||||
[ Maybe.withDefault "" model.formError |> text
|
||||
]
|
||||
, div [ class "ui divider" ] []
|
||||
, button
|
||||
[ class "ui primary button"
|
||||
, onClick Submit
|
||||
, href "#"
|
||||
]
|
||||
[ text "Submit"
|
||||
]
|
||||
, a
|
||||
[ class "ui secondary button"
|
||||
, onClick (SetViewMode Table)
|
||||
, href ""
|
||||
]
|
||||
[ text "Cancel"
|
||||
]
|
||||
, if model.formModel.settings.name /= "" then
|
||||
a [ class "ui right floated red button", href "", onClick RequestDelete ]
|
||||
[ text "Delete" ]
|
||||
|
||||
else
|
||||
span [] []
|
||||
, div
|
||||
[ classList
|
||||
[ ( "ui dimmer", True )
|
||||
, ( "active", model.loading )
|
||||
]
|
||||
]
|
||||
[ div [ class "ui loader" ] []
|
||||
]
|
||||
]
|
74
modules/webapp/src/main/elm/Comp/ImapSettingsTable.elm
Normal file
74
modules/webapp/src/main/elm/Comp/ImapSettingsTable.elm
Normal file
@ -0,0 +1,74 @@
|
||||
module Comp.ImapSettingsTable exposing
|
||||
( Model
|
||||
, Msg
|
||||
, emptyModel
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.ImapSettings exposing (ImapSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ emailSettings : List ImapSettings
|
||||
, selected : Maybe ImapSettings
|
||||
}
|
||||
|
||||
|
||||
emptyModel : Model
|
||||
emptyModel =
|
||||
init []
|
||||
|
||||
|
||||
init : List ImapSettings -> Model
|
||||
init ems =
|
||||
{ emailSettings = ems
|
||||
, selected = Nothing
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Select ImapSettings
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Select ems ->
|
||||
( { model | selected = Just ems }, Cmd.none )
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
table [ class "ui selectable pointer table" ]
|
||||
[ thead []
|
||||
[ th [ class "collapsible" ] [ text "Name" ]
|
||||
, th [] [ text "Host/Port" ]
|
||||
]
|
||||
, tbody []
|
||||
(List.map (renderLine model) model.emailSettings)
|
||||
]
|
||||
|
||||
|
||||
renderLine : Model -> ImapSettings -> Html Msg
|
||||
renderLine model ems =
|
||||
let
|
||||
hostport =
|
||||
case ems.imapPort of
|
||||
Just p ->
|
||||
ems.imapHost ++ ":" ++ String.fromInt p
|
||||
|
||||
Nothing ->
|
||||
ems.imapHost
|
||||
in
|
||||
tr
|
||||
[ classList [ ( "active", model.selected == Just ems ) ]
|
||||
, onClick (Select ems)
|
||||
]
|
||||
[ td [ class "collapsible" ] [ text ems.name ]
|
||||
, td [] [ text hostport ]
|
||||
]
|
@ -7,6 +7,7 @@ module Page.UserSettings.Data exposing
|
||||
|
||||
import Comp.ChangePasswordForm
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationForm
|
||||
import Data.Flags exposing (Flags)
|
||||
|
||||
@ -15,6 +16,7 @@ type alias Model =
|
||||
{ currentTab : Maybe Tab
|
||||
, changePassModel : Comp.ChangePasswordForm.Model
|
||||
, emailSettingsModel : Comp.EmailSettingsManage.Model
|
||||
, imapSettingsModel : Comp.ImapSettingsManage.Model
|
||||
, notificationModel : Comp.NotificationForm.Model
|
||||
}
|
||||
|
||||
@ -24,6 +26,7 @@ emptyModel flags =
|
||||
{ currentTab = Nothing
|
||||
, changePassModel = Comp.ChangePasswordForm.emptyModel
|
||||
, emailSettingsModel = Comp.EmailSettingsManage.emptyModel
|
||||
, imapSettingsModel = Comp.ImapSettingsManage.emptyModel
|
||||
, notificationModel = Tuple.first (Comp.NotificationForm.init flags)
|
||||
}
|
||||
|
||||
@ -31,6 +34,7 @@ emptyModel flags =
|
||||
type Tab
|
||||
= ChangePassTab
|
||||
| EmailSettingsTab
|
||||
| ImapSettingsTab
|
||||
| NotificationTab
|
||||
|
||||
|
||||
@ -39,3 +43,4 @@ type Msg
|
||||
| ChangePassMsg Comp.ChangePasswordForm.Msg
|
||||
| EmailSettingsMsg Comp.EmailSettingsManage.Msg
|
||||
| NotificationMsg Comp.NotificationForm.Msg
|
||||
| ImapSettingsMsg Comp.ImapSettingsManage.Msg
|
||||
|
@ -2,6 +2,7 @@ module Page.UserSettings.Update exposing (update)
|
||||
|
||||
import Comp.ChangePasswordForm
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationForm
|
||||
import Data.Flags exposing (Flags)
|
||||
import Page.UserSettings.Data exposing (..)
|
||||
@ -24,6 +25,13 @@ update flags msg model =
|
||||
in
|
||||
( { m | emailSettingsModel = em }, Cmd.map EmailSettingsMsg c )
|
||||
|
||||
ImapSettingsTab ->
|
||||
let
|
||||
( em, c ) =
|
||||
Comp.ImapSettingsManage.init flags
|
||||
in
|
||||
( { m | imapSettingsModel = em }, Cmd.map ImapSettingsMsg c )
|
||||
|
||||
ChangePassTab ->
|
||||
( m, Cmd.none )
|
||||
|
||||
@ -51,6 +59,13 @@ update flags msg model =
|
||||
in
|
||||
( { model | emailSettingsModel = m2 }, Cmd.map EmailSettingsMsg c2 )
|
||||
|
||||
ImapSettingsMsg m ->
|
||||
let
|
||||
( m2, c2 ) =
|
||||
Comp.ImapSettingsManage.update flags m model.imapSettingsModel
|
||||
in
|
||||
( { model | imapSettingsModel = m2 }, Cmd.map ImapSettingsMsg c2 )
|
||||
|
||||
NotificationMsg lm ->
|
||||
let
|
||||
( m2, c2 ) =
|
||||
|
@ -2,6 +2,7 @@ module Page.UserSettings.View exposing (view)
|
||||
|
||||
import Comp.ChangePasswordForm
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationForm
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -20,7 +21,8 @@ view model =
|
||||
, div [ class "ui attached fluid segment" ]
|
||||
[ div [ class "ui fluid vertical secondary menu" ]
|
||||
[ makeTab model ChangePassTab "Change Password" "user secret icon"
|
||||
, makeTab model EmailSettingsTab "E-Mail Settings" "mail icon"
|
||||
, makeTab model EmailSettingsTab "E-Mail Settings (SMTP)" "mail icon"
|
||||
, makeTab model ImapSettingsTab "E-Mail Settings (IMAP)" "mail icon"
|
||||
, makeTab model NotificationTab "Notifications" "bullhorn icon"
|
||||
]
|
||||
]
|
||||
@ -37,6 +39,9 @@ view model =
|
||||
Just NotificationTab ->
|
||||
viewNotificationForm model
|
||||
|
||||
Just ImapSettingsTab ->
|
||||
viewImapSettings model
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
)
|
||||
@ -61,13 +66,25 @@ viewEmailSettings model =
|
||||
[ h2 [ class "ui header" ]
|
||||
[ i [ class "mail icon" ] []
|
||||
, div [ class "content" ]
|
||||
[ text "E-Mail Settings"
|
||||
[ text "E-Mail Settings (Smtp)"
|
||||
]
|
||||
]
|
||||
, Html.map EmailSettingsMsg (Comp.EmailSettingsManage.view model.emailSettingsModel)
|
||||
]
|
||||
|
||||
|
||||
viewImapSettings : Model -> List (Html Msg)
|
||||
viewImapSettings model =
|
||||
[ h2 [ class "ui header" ]
|
||||
[ i [ class "mail icon" ] []
|
||||
, div [ class "content" ]
|
||||
[ text "E-Mail Settings (Imap)"
|
||||
]
|
||||
]
|
||||
, Html.map ImapSettingsMsg (Comp.ImapSettingsManage.view model.imapSettingsModel)
|
||||
]
|
||||
|
||||
|
||||
viewChangePassword : Model -> List (Html Msg)
|
||||
viewChangePassword model =
|
||||
[ h2 [ class "ui header" ]
|
||||
|
Loading…
x
Reference in New Issue
Block a user