mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Merge pull request #1301 from eikek/notification-improvements
Notification improvements
This commit is contained in:
@ -11,7 +11,6 @@ import cats.implicits._
|
||||
|
||||
import docspell.backend.MailAddressCodec
|
||||
import docspell.common._
|
||||
import docspell.notification.api.ChannelOrRef._
|
||||
import docspell.notification.api.PeriodicQueryArgs
|
||||
import docspell.store.records.RJob
|
||||
|
||||
@ -25,7 +24,7 @@ object JobFactory extends MailAddressCodec {
|
||||
PeriodicQueryArgs.taskName,
|
||||
submitter.collective,
|
||||
args,
|
||||
s"Running periodic query, notify via ${args.channel.channelType}",
|
||||
s"Running periodic query, notify via ${args.channels.map(_.channelType)}",
|
||||
now,
|
||||
submitter.user,
|
||||
Priority.Low,
|
||||
|
@ -39,7 +39,10 @@ trait ONotification[F[_]] {
|
||||
userId: Ident
|
||||
): F[Vector[NotificationChannel]]
|
||||
|
||||
def findNotificationChannel(ref: ChannelRef): F[Vector[NotificationChannel]]
|
||||
def findNotificationChannel(
|
||||
ref: ChannelRef,
|
||||
account: AccountId
|
||||
): F[Vector[NotificationChannel]]
|
||||
|
||||
def listChannels(account: AccountId): F[Vector[Channel]]
|
||||
|
||||
@ -65,7 +68,7 @@ trait ONotification[F[_]] {
|
||||
|
||||
def sendSampleEvent(
|
||||
evt: EventType,
|
||||
channel: Channel,
|
||||
channel: Nel[ChannelRef],
|
||||
account: AccountId,
|
||||
baseUrl: Option[LenientUri]
|
||||
): F[ONotification.SendTestResult]
|
||||
@ -89,7 +92,7 @@ object ONotification {
|
||||
.getOrElse(UpdateResult.notFound)
|
||||
|
||||
def offerEvents(ev: Iterable[Event]): F[Unit] =
|
||||
ev.toList.traverse(notMod.offer(_)).as(())
|
||||
ev.toList.traverse(notMod.offer).as(())
|
||||
|
||||
def sendMessage(
|
||||
logger: Logger[F],
|
||||
@ -109,33 +112,27 @@ object ONotification {
|
||||
|
||||
def sendSampleEvent(
|
||||
evt: EventType,
|
||||
channel: Channel,
|
||||
channels: Nel[ChannelRef],
|
||||
account: AccountId,
|
||||
baseUrl: Option[LenientUri]
|
||||
): F[SendTestResult] = {
|
||||
def doCreate(userId: Ident) =
|
||||
(for {
|
||||
ev <- sampleEvent(evt, account, baseUrl)
|
||||
logbuf <- Logger.buffer()
|
||||
ch <- mkNotificationChannel(channel, userId)
|
||||
_ <- notMod.send(logbuf._2.andThen(log), ev, ch)
|
||||
logs <- logbuf._1.get
|
||||
res = SendTestResult(true, logs)
|
||||
} yield res).attempt
|
||||
.map {
|
||||
case Right(res) => res
|
||||
case Left(ex) =>
|
||||
val ps = new StringWriter()
|
||||
ex.printStackTrace(new PrintWriter(ps))
|
||||
SendTestResult(false, Vector(s"${ex.getMessage}\n$ps"))
|
||||
}
|
||||
|
||||
OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||
.semiflatMap(doCreate)
|
||||
.getOrElse(
|
||||
SendTestResult(false, Vector(s"No user found in db for: ${account.asString}"))
|
||||
): F[SendTestResult] =
|
||||
(for {
|
||||
ev <- sampleEvent(evt, account, baseUrl)
|
||||
logbuf <- Logger.buffer()
|
||||
ch <- channels.toList.toVector.flatTraverse(
|
||||
findNotificationChannel(_, account)
|
||||
)
|
||||
}
|
||||
_ <- notMod.send(logbuf._2.andThen(log), ev, ch)
|
||||
logs <- logbuf._1.get
|
||||
res = SendTestResult(true, logs)
|
||||
} yield res).attempt
|
||||
.map {
|
||||
case Right(res) => res
|
||||
case Left(ex) =>
|
||||
val ps = new StringWriter()
|
||||
ex.printStackTrace(new PrintWriter(ps))
|
||||
SendTestResult(false, Vector(s"${ex.getMessage}\n$ps"))
|
||||
}
|
||||
|
||||
def listChannels(account: AccountId): F[Vector[Channel]] =
|
||||
store
|
||||
@ -153,7 +150,7 @@ object ONotification {
|
||||
(for {
|
||||
newId <- OptionT.liftF(Ident.randomId[F])
|
||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||
r <- ChannelConv.makeRecord[F](store, log, Right(channel), newId, userId)
|
||||
r <- ChannelConv.makeRecord[F](store, channel, newId, userId)
|
||||
_ <- OptionT.liftF(store.transact(RNotificationChannel.insert(r)))
|
||||
_ <- OptionT.liftF(log.debug(s"Created channel $r for $account"))
|
||||
} yield AddResult.Success)
|
||||
@ -162,7 +159,7 @@ object ONotification {
|
||||
def updateChannel(channel: Channel, account: AccountId): F[UpdateResult] =
|
||||
(for {
|
||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||
r <- ChannelConv.makeRecord[F](store, log, Right(channel), channel.id, userId)
|
||||
r <- ChannelConv.makeRecord[F](store, channel, channel.id, userId)
|
||||
n <- OptionT.liftF(store.transact(RNotificationChannel.update(r)))
|
||||
} yield UpdateResult.fromUpdateRows(n)).getOrElse(UpdateResult.notFound)
|
||||
|
||||
@ -179,16 +176,14 @@ object ONotification {
|
||||
def createHook(hook: Hook, account: AccountId): F[AddResult] =
|
||||
(for {
|
||||
_ <- OptionT.liftF(log.debug(s"Creating new notification hook: $hook"))
|
||||
channelId <- OptionT.liftF(Ident.randomId[F])
|
||||
userId <- OptionT(store.transact(RUser.findIdByAccount(account)))
|
||||
r <- ChannelConv.makeRecord[F](store, log, hook.channel, channelId, userId)
|
||||
hr <- OptionT.liftF(Hook.makeRecord(userId, hook))
|
||||
_ <- OptionT.liftF(
|
||||
if (channelId == r.id) store.transact(RNotificationChannel.insert(r))
|
||||
else ().pure[F]
|
||||
store.transact(
|
||||
RNotificationHook.insert(hr) *> RNotificationHookChannel
|
||||
.updateAll(hr.id, hook.channels.toList)
|
||||
)
|
||||
)
|
||||
_ <- OptionT.liftF(log.debug(s"Created channel $r for $account"))
|
||||
hr <- OptionT.liftF(Hook.makeRecord(r, userId, hook))
|
||||
_ <- OptionT.liftF(store.transact(RNotificationHook.insert(hr)))
|
||||
_ <- OptionT.liftF(
|
||||
store.transact(RNotificationHookEvent.insertAll(hr.id, hook.events))
|
||||
)
|
||||
@ -203,31 +198,25 @@ object ONotification {
|
||||
.getOrElse(UpdateResult.notFound)
|
||||
)
|
||||
|
||||
def withChannel(
|
||||
r: RNotificationHook
|
||||
)(f: RNotificationChannel => F[UpdateResult]): F[UpdateResult] =
|
||||
ChannelConv
|
||||
.makeRecord(store, log, hook.channel, r.channelId, r.uid)
|
||||
.semiflatMap(f)
|
||||
.getOrElse(UpdateResult.notFound)
|
||||
|
||||
def doUpdate(r: RNotificationHook): F[UpdateResult] =
|
||||
withChannel(r) { ch =>
|
||||
UpdateResult.fromUpdate(store.transact(for {
|
||||
nc <- RNotificationChannel.update(ch)
|
||||
ne <- RNotificationHookEvent.updateAll(
|
||||
r.id,
|
||||
if (hook.allEvents) Nil else hook.events
|
||||
UpdateResult.fromUpdate(store.transact(for {
|
||||
ne <- RNotificationHookEvent.updateAll(
|
||||
r.id,
|
||||
if (hook.allEvents) Nil else hook.events
|
||||
)
|
||||
nc <- RNotificationHookChannel.updateAll(
|
||||
r.id,
|
||||
hook.channels.toList
|
||||
)
|
||||
nr <- RNotificationHook.update(
|
||||
r.copy(
|
||||
enabled = hook.enabled,
|
||||
allEvents = hook.allEvents,
|
||||
eventFilter = hook.eventFilter
|
||||
)
|
||||
nr <- RNotificationHook.update(
|
||||
r.copy(
|
||||
enabled = hook.enabled,
|
||||
allEvents = hook.allEvents,
|
||||
eventFilter = hook.eventFilter
|
||||
)
|
||||
)
|
||||
} yield nc + ne + nr))
|
||||
}
|
||||
)
|
||||
|
||||
} yield nc + ne + nr))
|
||||
|
||||
withHook(doUpdate)
|
||||
}
|
||||
@ -238,13 +227,17 @@ object ONotification {
|
||||
): F[Vector[NotificationChannel]] =
|
||||
(for {
|
||||
rec <- ChannelConv
|
||||
.makeRecord(store, log, Right(channel), channel.id, userId)
|
||||
.makeRecord(store, channel, channel.id, userId)
|
||||
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
||||
} yield ch).getOrElse(Vector.empty)
|
||||
|
||||
def findNotificationChannel(ref: ChannelRef): F[Vector[NotificationChannel]] =
|
||||
def findNotificationChannel(
|
||||
ref: ChannelRef,
|
||||
accountId: AccountId
|
||||
): F[Vector[NotificationChannel]] =
|
||||
(for {
|
||||
rec <- OptionT(store.transact(RNotificationChannel.getByRef(ref)))
|
||||
userId <- OptionT(store.transact(RUser.findIdByAccount(accountId)))
|
||||
rec <- OptionT(store.transact(RNotificationChannel.getByRef(ref, userId)))
|
||||
ch <- OptionT.liftF(store.transact(QNotification.readChannel(rec)))
|
||||
} yield ch).getOrElse(Vector.empty)
|
||||
})
|
||||
@ -254,75 +247,40 @@ object ONotification {
|
||||
private[ops] def makeChannel(r: RNotificationChannel): Channel =
|
||||
r.fold(
|
||||
mail =>
|
||||
Channel.Mail(mail.id, mail.connection, Nel.fromListUnsafe(mail.recipients)),
|
||||
gotify => Channel.Gotify(r.id, gotify.url, gotify.appKey, gotify.priority),
|
||||
Channel.Mail(
|
||||
mail.id,
|
||||
mail.name,
|
||||
mail.connection,
|
||||
Nel.fromListUnsafe(mail.recipients)
|
||||
),
|
||||
gotify =>
|
||||
Channel.Gotify(r.id, gotify.name, gotify.url, gotify.appKey, gotify.priority),
|
||||
matrix =>
|
||||
Channel.Matrix(r.id, matrix.homeServer, matrix.roomId, matrix.accessToken),
|
||||
http => Channel.Http(r.id, http.url)
|
||||
Channel
|
||||
.Matrix(
|
||||
r.id,
|
||||
matrix.name,
|
||||
matrix.homeServer,
|
||||
matrix.roomId,
|
||||
matrix.accessToken
|
||||
),
|
||||
http => Channel.Http(r.id, http.name, http.url)
|
||||
)
|
||||
|
||||
private[ops] def makeRecord[F[_]: Sync](
|
||||
private[ops] def makeRecord[F[_]](
|
||||
store: Store[F],
|
||||
logger: Logger[F],
|
||||
channelIn: Either[ChannelRef, Channel],
|
||||
channel: Channel,
|
||||
id: Ident,
|
||||
userId: Ident
|
||||
): OptionT[F, RNotificationChannel] =
|
||||
channelIn match {
|
||||
case Left(ref) =>
|
||||
OptionT.liftF(logger.debug(s"Loading channel for ref: ${ref}")) *>
|
||||
OptionT(store.transact(RNotificationChannel.getByRef(ref)))
|
||||
RNotificationChannel.fromChannel(channel, id, userId).mapK(store.transform)
|
||||
|
||||
case Right(channel) =>
|
||||
for {
|
||||
time <- OptionT.liftF(Timestamp.current[F])
|
||||
r <-
|
||||
channel match {
|
||||
case Channel.Mail(_, conn, recipients) =>
|
||||
for {
|
||||
_ <- OptionT.liftF(
|
||||
logger.debug(
|
||||
s"Looking up user smtp for ${userId.id} and ${conn.id}"
|
||||
)
|
||||
)
|
||||
mailConn <- OptionT(
|
||||
store.transact(RUserEmail.getByUser(userId, conn))
|
||||
)
|
||||
rec = RNotificationChannelMail(
|
||||
id,
|
||||
userId,
|
||||
mailConn.id,
|
||||
recipients.toList,
|
||||
time
|
||||
).vary
|
||||
} yield rec
|
||||
case Channel.Gotify(_, url, appKey, prio) =>
|
||||
OptionT.pure[F](
|
||||
RNotificationChannelGotify(id, userId, url, appKey, prio, time).vary
|
||||
)
|
||||
case Channel.Matrix(_, homeServer, roomId, accessToken) =>
|
||||
OptionT.pure[F](
|
||||
RNotificationChannelMatrix(
|
||||
id,
|
||||
userId,
|
||||
homeServer,
|
||||
roomId,
|
||||
accessToken,
|
||||
"m.text",
|
||||
time
|
||||
).vary
|
||||
)
|
||||
case Channel.Http(_, url) =>
|
||||
OptionT.pure[F](RNotificationChannelHttp(id, userId, url, time).vary)
|
||||
}
|
||||
} yield r
|
||||
}
|
||||
}
|
||||
|
||||
final case class Hook(
|
||||
id: Ident,
|
||||
enabled: Boolean,
|
||||
channel: Either[ChannelRef, Channel],
|
||||
channels: List[ChannelRef],
|
||||
allEvents: Boolean,
|
||||
eventFilter: Option[JsonMiniQuery],
|
||||
events: List[EventType]
|
||||
@ -335,14 +293,12 @@ object ONotification {
|
||||
r: RNotificationHook,
|
||||
events: List[EventType]
|
||||
): ConnectionIO[Hook] =
|
||||
RNotificationChannel
|
||||
.getByHook(r)
|
||||
.map(_.head)
|
||||
.map(ChannelConv.makeChannel)
|
||||
.map(ch => Hook(r.id, r.enabled, Right(ch), r.allEvents, r.eventFilter, events))
|
||||
RNotificationHookChannel
|
||||
.allOfNel(r.id)
|
||||
.flatMap(rhcs => RNotificationHookChannel.resolveRefs(rhcs))
|
||||
.map(refs => Hook(r.id, r.enabled, refs, r.allEvents, r.eventFilter, events))
|
||||
|
||||
private[ops] def makeRecord[F[_]: Sync](
|
||||
ch: RNotificationChannel,
|
||||
userId: Ident,
|
||||
hook: Hook
|
||||
): F[RNotificationHook] =
|
||||
@ -353,10 +309,6 @@ object ONotification {
|
||||
id,
|
||||
userId,
|
||||
hook.enabled,
|
||||
ch.fold(_.id.some, _ => None, _ => None, _ => None),
|
||||
ch.fold(_ => None, _.id.some, _ => None, _ => None),
|
||||
ch.fold(_ => None, _ => None, _.id.some, _ => None),
|
||||
ch.fold(_ => None, _ => None, _ => None, _.id.some),
|
||||
hook.allEvents,
|
||||
hook.eventFilter,
|
||||
time
|
||||
|
@ -11,7 +11,6 @@ import cats.effect._
|
||||
import cats.implicits._
|
||||
import fs2.Stream
|
||||
|
||||
import docspell.backend.MailAddressCodec._
|
||||
import docspell.common._
|
||||
import docspell.notification.api.PeriodicDueItemsArgs
|
||||
import docspell.notification.api.PeriodicQueryArgs
|
||||
|
@ -7,7 +7,6 @@
|
||||
package docspell.joex.notify
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
@ -24,7 +23,6 @@ import docspell.query.ItemQueryDsl._
|
||||
import docspell.store.qb.Batch
|
||||
import docspell.store.queries.ListItem
|
||||
import docspell.store.queries.{QItem, Query}
|
||||
import docspell.store.records.RUser
|
||||
|
||||
object PeriodicDueItemsTask {
|
||||
val taskName = PeriodicDueItemsArgs.taskName
|
||||
@ -51,11 +49,7 @@ object PeriodicDueItemsTask {
|
||||
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
||||
cont: Vector[NotificationChannel] => F[Unit]
|
||||
): F[Unit] =
|
||||
OptionT(ctx.store.transact(RUser.findIdByAccount(ctx.args.account)))
|
||||
.semiflatMap(userId =>
|
||||
TaskOperations.withChannel(ctx.logger, ctx.args.channel, userId, ops)(cont)
|
||||
)
|
||||
.getOrElse(())
|
||||
TaskOperations.withChannel(ctx.logger, ctx.args.channels, ctx.args.account, ops)(cont)
|
||||
|
||||
def withItems[F[_]: Sync](ctx: Context[F, Args], limit: Int, now: Timestamp)(
|
||||
cont: Vector[ListItem] => F[Unit]
|
||||
|
@ -26,7 +26,6 @@ import docspell.store.queries.ListItem
|
||||
import docspell.store.queries.{QItem, Query}
|
||||
import docspell.store.records.RQueryBookmark
|
||||
import docspell.store.records.RShare
|
||||
import docspell.store.records.RUser
|
||||
|
||||
object PeriodicQueryTask {
|
||||
val taskName = PeriodicQueryArgs.taskName
|
||||
@ -53,11 +52,7 @@ object PeriodicQueryTask {
|
||||
def withChannel[F[_]: Sync](ctx: Context[F, Args], ops: ONotification[F])(
|
||||
cont: Vector[NotificationChannel] => F[Unit]
|
||||
): F[Unit] =
|
||||
OptionT(ctx.store.transact(RUser.findIdByAccount(ctx.args.account)))
|
||||
.semiflatMap(userId =>
|
||||
TaskOperations.withChannel(ctx.logger, ctx.args.channel, userId, ops)(cont)
|
||||
)
|
||||
.getOrElse(())
|
||||
TaskOperations.withChannel(ctx.logger, ctx.args.channels, ctx.args.account, ops)(cont)
|
||||
|
||||
private def queryString(q: ItemQuery.Expr) =
|
||||
ItemQueryParser.asString(q)
|
||||
|
@ -12,7 +12,7 @@ import cats.implicits._
|
||||
|
||||
import docspell.backend.ops.ONotification
|
||||
import docspell.common._
|
||||
import docspell.notification.api.ChannelOrRef
|
||||
import docspell.notification.api.ChannelRef
|
||||
import docspell.notification.api.Event
|
||||
import docspell.notification.api.EventContext
|
||||
import docspell.notification.api.NotificationChannel
|
||||
@ -23,19 +23,18 @@ trait TaskOperations {
|
||||
|
||||
def withChannel[F[_]: Sync](
|
||||
logger: Logger[F],
|
||||
channel: ChannelOrRef,
|
||||
userId: Ident,
|
||||
channelsIn: NonEmptyList[ChannelRef],
|
||||
accountId: AccountId,
|
||||
ops: ONotification[F]
|
||||
)(
|
||||
cont: Vector[NotificationChannel] => F[Unit]
|
||||
): F[Unit] = {
|
||||
val channels = channel match {
|
||||
case Right(ch) => ops.mkNotificationChannel(ch, userId)
|
||||
case Left(ref) => ops.findNotificationChannel(ref)
|
||||
}
|
||||
val channels =
|
||||
channelsIn.toList.toVector.flatTraverse(ops.findNotificationChannel(_, accountId))
|
||||
|
||||
channels.flatMap { ch =>
|
||||
if (ch.isEmpty)
|
||||
logger.error(s"No channels found for the given data: ${channel}")
|
||||
logger.error(s"No channels found for the given data: ${channelsIn}")
|
||||
else cont(ch)
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,14 @@ import io.circe.{Decoder, Encoder}
|
||||
sealed trait Channel {
|
||||
def id: Ident
|
||||
def channelType: ChannelType
|
||||
def name: Option[String]
|
||||
def fold[A](
|
||||
f1: Channel.Mail => A,
|
||||
f2: Channel.Gotify => A,
|
||||
f3: Channel.Matrix => A,
|
||||
f4: Channel.Http => A
|
||||
): A
|
||||
def asRef: ChannelRef = ChannelRef(id, channelType)
|
||||
def asRef: ChannelRef = ChannelRef(id, channelType, name)
|
||||
}
|
||||
|
||||
object Channel {
|
||||
@ -33,6 +34,7 @@ object Channel {
|
||||
|
||||
final case class Mail(
|
||||
id: Ident,
|
||||
name: Option[String],
|
||||
connection: Ident,
|
||||
recipients: Nel[MailAddress]
|
||||
) extends Channel {
|
||||
@ -55,6 +57,7 @@ object Channel {
|
||||
|
||||
final case class Gotify(
|
||||
id: Ident,
|
||||
name: Option[String],
|
||||
url: LenientUri,
|
||||
appKey: Password,
|
||||
priority: Option[Int]
|
||||
@ -77,6 +80,7 @@ object Channel {
|
||||
|
||||
final case class Matrix(
|
||||
id: Ident,
|
||||
name: Option[String],
|
||||
homeServer: LenientUri,
|
||||
roomId: String,
|
||||
accessToken: Password
|
||||
@ -95,7 +99,8 @@ object Channel {
|
||||
implicit val jsonEncoder: Encoder[Matrix] = deriveConfiguredEncoder
|
||||
}
|
||||
|
||||
final case class Http(id: Ident, url: LenientUri) extends Channel {
|
||||
final case class Http(id: Ident, name: Option[String], url: LenientUri)
|
||||
extends Channel {
|
||||
val channelType = ChannelType.Http
|
||||
def fold[A](
|
||||
f1: Mail => A,
|
||||
|
@ -12,7 +12,7 @@ import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
||||
|
||||
final case class ChannelRef(id: Ident, channelType: ChannelType)
|
||||
final case class ChannelRef(id: Ident, channelType: ChannelType, name: Option[String])
|
||||
|
||||
object ChannelRef {
|
||||
|
||||
|
@ -6,9 +6,10 @@
|
||||
|
||||
package docspell.notification.api
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
@ -21,7 +22,7 @@ import io.circe.{Decoder, Encoder}
|
||||
*/
|
||||
final case class PeriodicDueItemsArgs(
|
||||
account: AccountId,
|
||||
channel: ChannelOrRef,
|
||||
channels: NonEmptyList[ChannelRef],
|
||||
remindDays: Int,
|
||||
daysBack: Option[Int],
|
||||
tagsInclude: List[Ident],
|
||||
@ -30,19 +31,11 @@ final case class PeriodicDueItemsArgs(
|
||||
)
|
||||
|
||||
object PeriodicDueItemsArgs {
|
||||
val taskName = Ident.unsafe("periodic-due-items-notify")
|
||||
val taskName = Ident.unsafe("periodic-due-items-notify2")
|
||||
|
||||
implicit def jsonDecoder(implicit
|
||||
mc: Decoder[MailAddress]
|
||||
): Decoder[PeriodicDueItemsArgs] = {
|
||||
implicit val x = ChannelOrRef.jsonDecoder
|
||||
implicit val jsonDecoder: Decoder[PeriodicDueItemsArgs] =
|
||||
semiauto.deriveDecoder
|
||||
}
|
||||
|
||||
implicit def jsonEncoder(implicit
|
||||
mc: Encoder[MailAddress]
|
||||
): Encoder[PeriodicDueItemsArgs] = {
|
||||
implicit val x = ChannelOrRef.jsonEncoder
|
||||
implicit val jsonEncoder: Encoder[PeriodicDueItemsArgs] =
|
||||
semiauto.deriveEncoder
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,16 @@
|
||||
|
||||
package docspell.notification.api
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
final case class PeriodicQueryArgs(
|
||||
account: AccountId,
|
||||
channel: ChannelOrRef,
|
||||
channels: NonEmptyList[ChannelRef],
|
||||
query: Option[ItemQueryString],
|
||||
bookmark: Option[String],
|
||||
baseUrl: Option[LenientUri],
|
||||
@ -22,19 +23,11 @@ final case class PeriodicQueryArgs(
|
||||
)
|
||||
|
||||
object PeriodicQueryArgs {
|
||||
val taskName = Ident.unsafe("periodic-query-notify")
|
||||
val taskName = Ident.unsafe("periodic-query-notify2")
|
||||
|
||||
implicit def jsonDecoder(implicit
|
||||
mc: Decoder[MailAddress]
|
||||
): Decoder[PeriodicQueryArgs] = {
|
||||
implicit val x = ChannelOrRef.jsonDecoder
|
||||
implicit val jsonDecoder: Decoder[PeriodicQueryArgs] =
|
||||
semiauto.deriveDecoder
|
||||
}
|
||||
|
||||
implicit def jsonEncoder(implicit
|
||||
mc: Encoder[MailAddress]
|
||||
): Encoder[PeriodicQueryArgs] = {
|
||||
implicit val x = ChannelOrRef.jsonEncoder
|
||||
implicit def jsonEncoder: Encoder[PeriodicQueryArgs] =
|
||||
semiauto.deriveEncoder
|
||||
}
|
||||
}
|
||||
|
@ -1739,7 +1739,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/extraSchemas/NotificationHook"
|
||||
$ref: "#/components/schemas/NotificationHook"
|
||||
post:
|
||||
operationId: "sec-notification-hook-post"
|
||||
tags: [ Notification ]
|
||||
@ -1753,7 +1753,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/NotificationHook"
|
||||
$ref: "#/components/schemas/NotificationHook"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -1775,7 +1775,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/NotificationHook"
|
||||
$ref: "#/components/schemas/NotificationHook"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -1821,7 +1821,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/NotificationHook"
|
||||
$ref: "#/components/schemas/NotificationHook"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -4917,7 +4917,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
||||
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||
post:
|
||||
operationId: "sec-usertask-notify-new"
|
||||
tags: [ User Tasks ]
|
||||
@ -4931,7 +4931,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
||||
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -4954,7 +4954,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
||||
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -4984,7 +4984,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
||||
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||
delete:
|
||||
operationId: "sec-usertask-notify-delete"
|
||||
tags: [ User Tasks ]
|
||||
@ -5018,7 +5018,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicDueItemsSettings"
|
||||
$ref: "#/components/schemas/PeriodicDueItemsSettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -5048,7 +5048,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
||||
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||
post:
|
||||
operationId: "sec-usertask-periodic-query-new"
|
||||
tags: [ User Tasks ]
|
||||
@ -5062,7 +5062,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
||||
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -5085,7 +5085,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
||||
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -5115,7 +5115,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
||||
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||
delete:
|
||||
operationId: "sec-usertask-periodic-query-delete"
|
||||
tags: [ User Tasks ]
|
||||
@ -5149,7 +5149,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/extraSchemas/PeriodicQuerySettings"
|
||||
$ref: "#/components/schemas/PeriodicQuerySettings"
|
||||
responses:
|
||||
422:
|
||||
description: BadRequest
|
||||
@ -5467,7 +5467,10 @@ components:
|
||||
type: string
|
||||
NotificationChannelRef:
|
||||
description: |
|
||||
A reference to a channel.
|
||||
A reference to a channel. The `id` and `channelType` are
|
||||
required to identify a channel. The `name` attribute is as a
|
||||
descriptive name and is returned by the server if it is
|
||||
specified for the corresponding channel.
|
||||
required:
|
||||
- id
|
||||
- channelType
|
||||
@ -5478,6 +5481,8 @@ components:
|
||||
channelType:
|
||||
type: string
|
||||
format: channeltype
|
||||
name:
|
||||
type: string
|
||||
NotificationMatrix:
|
||||
description: |
|
||||
A notification channel for matrix.
|
||||
@ -5491,6 +5496,8 @@ components:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
name:
|
||||
type: string
|
||||
channelType:
|
||||
type: string
|
||||
format: channeltype
|
||||
@ -5514,6 +5521,8 @@ components:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
name:
|
||||
type: string
|
||||
channelType:
|
||||
type: string
|
||||
format: channeltype
|
||||
@ -5539,6 +5548,8 @@ components:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
name:
|
||||
type: string
|
||||
channelType:
|
||||
type: string
|
||||
format: channeltype
|
||||
@ -5557,6 +5568,8 @@ components:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
name:
|
||||
type: string
|
||||
channelType:
|
||||
type: string
|
||||
format: channeltype
|
||||
@ -5568,6 +5581,136 @@ components:
|
||||
items:
|
||||
type: string
|
||||
|
||||
NotificationHook:
|
||||
description: |
|
||||
Describes a notifcation hook. There must be at least one
|
||||
channel specified. When creating hooks, the channels must
|
||||
provide the `ìd` and the `channelType` while their `name`
|
||||
attribute is optional.
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- events
|
||||
- allEvents
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
channels:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NotificationChannelRef"
|
||||
allEvents:
|
||||
type: boolean
|
||||
eventFilter:
|
||||
type: string
|
||||
format: jsonminiq
|
||||
description: |
|
||||
A filter expression that is applied to the event to be able
|
||||
to ignore a subset of them. See its
|
||||
[documentation](https://docspell.org/docs/jsonminiquery/).
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: eventtype
|
||||
enum:
|
||||
- tagsAdded
|
||||
- tagsSet
|
||||
|
||||
PeriodicQuerySettings:
|
||||
description: |
|
||||
Settings for the periodc-query task. At least one of `query`
|
||||
and `bookmark` is required! There must be at least one channel
|
||||
specified when creating settings. A channel must provide its
|
||||
`id` and `channelType`, while its `name` is optional.
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- schedule
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
channels:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NotificationChannelRef"
|
||||
schedule:
|
||||
type: string
|
||||
format: calevent
|
||||
query:
|
||||
type: string
|
||||
format: itemquery
|
||||
bookmark:
|
||||
type: string
|
||||
description: |
|
||||
Name or ID of bookmark to use.
|
||||
contentStart:
|
||||
type: string
|
||||
|
||||
PeriodicDueItemsSettings:
|
||||
description: |
|
||||
Settings for notifying about due items. At least one of
|
||||
`query` and `bookmark` is required! There must be at least one
|
||||
channel specified when creating settings. A channel must
|
||||
provide its `id` and `channelType`, while its `name` is
|
||||
optional.
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- schedule
|
||||
- remindDays
|
||||
- capOverdue
|
||||
- tagsInclude
|
||||
- tagsExclude
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
channels:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/NotificationChannelRef"
|
||||
schedule:
|
||||
type: string
|
||||
format: calevent
|
||||
remindDays:
|
||||
type: integer
|
||||
format: int32
|
||||
description: |
|
||||
Used to restrict items by their due dates. All items with
|
||||
a due date lower than (now + remindDays) are searched.
|
||||
capOverdue:
|
||||
type: boolean
|
||||
description: |
|
||||
If this is true, the search is also restricted to due
|
||||
dates greater than `now - remindDays'. Otherwise, due date
|
||||
are not restricted in that direction (only lower than `now
|
||||
+ remindDays' applies) and it is expected to restrict it
|
||||
more using custom tags.
|
||||
tagsInclude:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Tag"
|
||||
tagsExclude:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Tag"
|
||||
|
||||
ShareSecret:
|
||||
description: |
|
||||
The secret (the share id + optional password) to access a
|
||||
@ -8001,137 +8144,3 @@ components:
|
||||
schema:
|
||||
type: string
|
||||
format: ident
|
||||
|
||||
# sadly no generator support for these.
|
||||
# Changes here requires corresponding changes in:
|
||||
# - NotificationHook.elm
|
||||
# - routes.model.*
|
||||
extraSchemas:
|
||||
NotificationHook:
|
||||
description: |
|
||||
Describes a notifcation hook. There must be exactly one channel
|
||||
specified, so either use a `channelRef` or one `channel`.
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- events
|
||||
- allEvents
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
channel:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/NotificationMail"
|
||||
- $ref: "#/components/schemas/NotificationGotify"
|
||||
- $ref: "#/components/schemas/NotificationMatrix"
|
||||
- $ref: "#/components/schemas/NotificationHttp"
|
||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
||||
allEvents:
|
||||
type: boolean
|
||||
eventFilter:
|
||||
type: string
|
||||
format: jsonminiq
|
||||
description: |
|
||||
A filter expression that is applied to the event to be able
|
||||
to ignore a subset of them. See its
|
||||
[documentation](https://docspell.org/docs/jsonminiquery/).
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: eventtype
|
||||
enum:
|
||||
- tagsAdded
|
||||
- tagsSet
|
||||
|
||||
PeriodicQuerySettings:
|
||||
description: |
|
||||
Settings for the periodc-query task. At least one of `query` and
|
||||
`bookmark` is required!
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- schedule
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
channel:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/NotificationMail"
|
||||
- $ref: "#/components/schemas/NotificationGotify"
|
||||
- $ref: "#/components/schemas/NotificationMatrix"
|
||||
- $ref: "#/components/schemas/NotificationHttp"
|
||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
||||
schedule:
|
||||
type: string
|
||||
format: calevent
|
||||
query:
|
||||
type: string
|
||||
format: itemquery
|
||||
bookmark:
|
||||
type: string
|
||||
description: |
|
||||
Name or ID of bookmark to use.
|
||||
|
||||
PeriodicDueItemsSettings:
|
||||
description: |
|
||||
Settings for notifying about due items.
|
||||
required:
|
||||
- id
|
||||
- enabled
|
||||
- channel
|
||||
- schedule
|
||||
- remindDays
|
||||
- capOverdue
|
||||
- tagsInclude
|
||||
- tagsExclude
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
enabled:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
channel:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/NotificationMail"
|
||||
- $ref: "#/components/schemas/NotificationGotify"
|
||||
- $ref: "#/components/schemas/NotificationMatrix"
|
||||
- $ref: "#/components/schemas/NotificationHttp"
|
||||
- $ref: "#/components/schemas/NotificationChannelRef"
|
||||
schedule:
|
||||
type: string
|
||||
format: calevent
|
||||
remindDays:
|
||||
type: integer
|
||||
format: int32
|
||||
description: |
|
||||
Used to restrict items by their due dates. All items with
|
||||
a due date lower than (now + remindDays) are searched.
|
||||
capOverdue:
|
||||
type: boolean
|
||||
description: |
|
||||
If this is true, the search is also restricted to due
|
||||
dates greater than `now - remindDays'. Otherwise, due date
|
||||
are not restricted in that direction (only lower than `now
|
||||
+ remindDays' applies) and it is expected to restrict it
|
||||
more using custom tags.
|
||||
tagsInclude:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Tag"
|
||||
tagsExclude:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Tag"
|
||||
|
@ -73,15 +73,24 @@ object NotificationChannel {
|
||||
.map(NonEmptyList.fromList)
|
||||
.flatMap(_.toRight("No recipients given!"))
|
||||
.leftMap(new IllegalArgumentException(_))
|
||||
.map(rec => Channel.Mail(mail.id, mail.connection, rec)),
|
||||
.map(rec => Channel.Mail(mail.id, mail.name, mail.connection, rec)),
|
||||
gotify =>
|
||||
Right(Channel.Gotify(gotify.id, gotify.url, gotify.appKey, gotify.priority)),
|
||||
Right(
|
||||
Channel
|
||||
.Gotify(gotify.id, gotify.name, gotify.url, gotify.appKey, gotify.priority)
|
||||
),
|
||||
matrix =>
|
||||
Right(
|
||||
Channel
|
||||
.Matrix(matrix.id, matrix.homeServer, matrix.roomId, matrix.accessToken)
|
||||
.Matrix(
|
||||
matrix.id,
|
||||
matrix.name,
|
||||
matrix.homeServer,
|
||||
matrix.roomId,
|
||||
matrix.accessToken
|
||||
)
|
||||
),
|
||||
http => Right(Channel.Http(http.id, http.url))
|
||||
http => Right(Channel.Http(http.id, http.name, http.url))
|
||||
)
|
||||
|
||||
def convert(c: Channel): NotificationChannel =
|
||||
@ -90,24 +99,35 @@ object NotificationChannel {
|
||||
mail {
|
||||
NotificationMail(
|
||||
m.id,
|
||||
m.name,
|
||||
ChannelType.Mail,
|
||||
m.connection,
|
||||
m.recipients.toList.map(_.displayString)
|
||||
)
|
||||
},
|
||||
g =>
|
||||
gotify(NotificationGotify(g.id, ChannelType.Gotify, g.url, g.appKey, g.priority)),
|
||||
gotify(
|
||||
NotificationGotify(
|
||||
g.id,
|
||||
g.name,
|
||||
ChannelType.Gotify,
|
||||
g.url,
|
||||
g.appKey,
|
||||
g.priority
|
||||
)
|
||||
),
|
||||
m =>
|
||||
matrix(
|
||||
NotificationMatrix(
|
||||
m.id,
|
||||
m.name,
|
||||
ChannelType.Matrix,
|
||||
m.homeServer,
|
||||
m.roomId,
|
||||
m.accessToken
|
||||
)
|
||||
),
|
||||
h => http(NotificationHttp(h.id, ChannelType.Http, h.url))
|
||||
h => http(NotificationHttp(h.id, h.name, ChannelType.Http, h.url))
|
||||
)
|
||||
|
||||
implicit val jsonDecoder: Decoder[NotificationChannel] =
|
||||
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.restapi.model
|
||||
|
||||
import docspell.common._
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
import docspell.notification.api.{ChannelRef, EventType}
|
||||
import docspell.restapi.codec.ChannelEitherCodec
|
||||
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
||||
final case class NotificationHook(
|
||||
id: Ident,
|
||||
enabled: Boolean,
|
||||
channel: Either[ChannelRef, NotificationChannel],
|
||||
allEvents: Boolean,
|
||||
eventFilter: Option[JsonMiniQuery],
|
||||
events: List[EventType]
|
||||
)
|
||||
|
||||
object NotificationHook {
|
||||
import ChannelEitherCodec._
|
||||
|
||||
implicit val jsonDecoder: Decoder[NotificationHook] =
|
||||
io.circe.generic.semiauto.deriveDecoder
|
||||
implicit val jsonEncoder: Encoder[NotificationHook] =
|
||||
io.circe.generic.semiauto.deriveEncoder
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.restapi.model
|
||||
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
|
||||
import com.github.eikek.calev.CalEvent
|
||||
import com.github.eikek.calev.circe.CalevCirceCodec._
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
||||
final case class PeriodicDueItemsSettings(
|
||||
id: Ident,
|
||||
enabled: Boolean,
|
||||
summary: Option[String],
|
||||
channel: NotificationChannel,
|
||||
schedule: CalEvent,
|
||||
remindDays: Int,
|
||||
capOverdue: Boolean,
|
||||
tagsInclude: List[Tag],
|
||||
tagsExclude: List[Tag]
|
||||
)
|
||||
object PeriodicDueItemsSettings {
|
||||
|
||||
implicit val jsonDecoder: Decoder[PeriodicDueItemsSettings] =
|
||||
semiauto.deriveDecoder[PeriodicDueItemsSettings]
|
||||
implicit val jsonEncoder: Encoder[PeriodicDueItemsSettings] =
|
||||
semiauto.deriveEncoder[PeriodicDueItemsSettings]
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.restapi.model
|
||||
|
||||
import docspell.common._
|
||||
import docspell.query.ItemQuery
|
||||
import docspell.restapi.codec.ItemQueryJson._
|
||||
|
||||
import com.github.eikek.calev.CalEvent
|
||||
import com.github.eikek.calev.circe.CalevCirceCodec._
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
// this must comply to the definition in openapi.yml in `extraSchemas`
|
||||
final case class PeriodicQuerySettings(
|
||||
id: Ident,
|
||||
summary: Option[String],
|
||||
enabled: Boolean,
|
||||
channel: NotificationChannel,
|
||||
query: Option[ItemQuery],
|
||||
bookmark: Option[String],
|
||||
contentStart: Option[String],
|
||||
schedule: CalEvent
|
||||
) {}
|
||||
|
||||
object PeriodicQuerySettings {
|
||||
|
||||
implicit val jsonDecoder: Decoder[PeriodicQuerySettings] =
|
||||
semiauto.deriveDecoder
|
||||
|
||||
implicit val jsonEncoder: Encoder[PeriodicQuerySettings] =
|
||||
semiauto.deriveEncoder
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.restapi.model
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api.ChannelRef
|
||||
import docspell.notification.api.ChannelType
|
||||
|
||||
import io.circe.Decoder
|
||||
import io.circe.parser
|
||||
import munit._
|
||||
|
||||
class NotificationCodecTest extends FunSuite {
|
||||
|
||||
def parse[A: Decoder](str: String): A =
|
||||
parser.parse(str).fold(throw _, identity).as[A].fold(throw _, identity)
|
||||
|
||||
def id(str: String): Ident =
|
||||
Ident.unsafe(str)
|
||||
|
||||
test("decode with channelref") {
|
||||
val json = """{"id":"",
|
||||
"enabled": true,
|
||||
"channel": {"id":"abcde", "channelType":"matrix"},
|
||||
"allEvents": false,
|
||||
"events": ["TagsChanged", "SetFieldValue"]
|
||||
}"""
|
||||
|
||||
val hook = parse[NotificationHook](json)
|
||||
assertEquals(hook.enabled, true)
|
||||
assertEquals(hook.channel, Left(ChannelRef(id("abcde"), ChannelType.Matrix)))
|
||||
}
|
||||
|
||||
test("decode with gotify data") {
|
||||
val json = """{"id":"",
|
||||
"enabled": true,
|
||||
"channel": {"id":"", "channelType":"gotify", "url":"http://test.gotify.com", "appKey": "abcde"},
|
||||
"allEvents": false,
|
||||
"eventFilter": null,
|
||||
"events": ["TagsChanged", "SetFieldValue"]
|
||||
}"""
|
||||
val hook = parse[NotificationHook](json)
|
||||
assertEquals(hook.enabled, true)
|
||||
assertEquals(
|
||||
hook.channel,
|
||||
Right(
|
||||
NotificationChannel.Gotify(
|
||||
NotificationGotify(
|
||||
id(""),
|
||||
ChannelType.Gotify,
|
||||
LenientUri.unsafe("http://test.gotify.com"),
|
||||
Password("abcde"),
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
test("decode with gotify data with prio") {
|
||||
val json = """{"id":"",
|
||||
"enabled": true,
|
||||
"channel": {"id":"", "channelType":"gotify", "url":"http://test.gotify.com", "appKey": "abcde", "priority":9},
|
||||
"allEvents": false,
|
||||
"eventFilter": null,
|
||||
"events": ["TagsChanged", "SetFieldValue"]
|
||||
}"""
|
||||
val hook = parse[NotificationHook](json)
|
||||
assertEquals(hook.enabled, true)
|
||||
assertEquals(
|
||||
hook.channel,
|
||||
Right(
|
||||
NotificationChannel.Gotify(
|
||||
NotificationGotify(
|
||||
id(""),
|
||||
ChannelType.Gotify,
|
||||
LenientUri.unsafe("http://test.gotify.com"),
|
||||
Password("abcde"),
|
||||
Some(9)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
@ -14,10 +15,10 @@ import docspell.backend.auth.AuthToken
|
||||
import docspell.common._
|
||||
import docspell.joexapi.model.BasicResult
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
import docspell.notification.api.EventType
|
||||
import docspell.notification.api.{ChannelRef, EventType}
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.Config
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||
import docspell.restserver.http4s.ClientRequestInfo
|
||||
|
||||
import org.http4s._
|
||||
@ -26,7 +27,7 @@ import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.server.Router
|
||||
|
||||
object NotificationRoutes {
|
||||
object NotificationRoutes extends NonEmptyListSupport {
|
||||
|
||||
def apply[F[_]: Async](
|
||||
cfg: Config,
|
||||
@ -126,17 +127,11 @@ object NotificationRoutes {
|
||||
case req @ POST -> Root / "sendTestEvent" =>
|
||||
for {
|
||||
input <- req.as[NotificationHook]
|
||||
ch <- Sync[F]
|
||||
.pure(
|
||||
input.channel.left
|
||||
.map(_ => new Exception(s"ChannelRefs not allowed for testing"))
|
||||
.flatMap(NotificationChannel.convert)
|
||||
)
|
||||
.rethrow
|
||||
ch <- requireNonEmpty(input.channels)
|
||||
baseUrl = ClientRequestInfo.getBaseUrl(cfg, req)
|
||||
res <- backend.notification.sendSampleEvent(
|
||||
input.events.headOption.getOrElse(EventType.all.head),
|
||||
ch,
|
||||
ch.map(r => ChannelRef(r.id, r.channelType, r.name)),
|
||||
user.account,
|
||||
baseUrl.some
|
||||
)
|
||||
@ -179,33 +174,26 @@ object NotificationRoutes {
|
||||
NotificationHook(
|
||||
h.id,
|
||||
h.enabled,
|
||||
h.channel.map(NotificationChannel.convert),
|
||||
h.channels.map(c => NotificationChannelRef(c.id, c.channelType, c.name)).toList,
|
||||
h.allEvents,
|
||||
h.eventFilter,
|
||||
h.events
|
||||
)
|
||||
|
||||
def convertHook(h: NotificationHook): Either[Throwable, ONotification.Hook] =
|
||||
h.channel match {
|
||||
case Left(cref) =>
|
||||
Right(
|
||||
ONotification.Hook(
|
||||
h.id,
|
||||
h.enabled,
|
||||
Left(cref),
|
||||
h.allEvents,
|
||||
h.eventFilter,
|
||||
h.events
|
||||
)
|
||||
NonEmptyList
|
||||
.fromList(h.channels)
|
||||
.toRight(new IllegalArgumentException(s"Empty channels not allowed!"))
|
||||
.map(_ =>
|
||||
ONotification.Hook(
|
||||
h.id,
|
||||
h.enabled,
|
||||
h.channels.map(c => ChannelRef(c.id, c.channelType, c.name)),
|
||||
h.allEvents,
|
||||
h.eventFilter,
|
||||
h.events
|
||||
)
|
||||
case Right(channel) =>
|
||||
NotificationChannel
|
||||
.convert(channel)
|
||||
.map(ch =>
|
||||
ONotification
|
||||
.Hook(h.id, h.enabled, Right(ch), h.allEvents, h.eventFilter, h.events)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@ import docspell.backend.BackendApp
|
||||
import docspell.backend.MailAddressCodec
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.common._
|
||||
import docspell.notification.api.PeriodicDueItemsArgs
|
||||
import docspell.notification.api.{ChannelRef, PeriodicDueItemsArgs}
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.Config
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||
import docspell.restserver.http4s.ClientRequestInfo
|
||||
import docspell.store.usertask._
|
||||
|
||||
@ -26,7 +26,7 @@ import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object NotifyDueItemsRoutes extends MailAddressCodec {
|
||||
object NotifyDueItemsRoutes extends MailAddressCodec with NonEmptyListSupport {
|
||||
|
||||
def apply[F[_]: Async](
|
||||
cfg: Config,
|
||||
@ -113,7 +113,7 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
||||
user: AccountId,
|
||||
settings: PeriodicDueItemsSettings
|
||||
): F[UserTask[PeriodicDueItemsArgs]] =
|
||||
Sync[F].pure(NotificationChannel.convert(settings.channel)).rethrow.map { channel =>
|
||||
requireNonEmpty(settings.channels).map { ch =>
|
||||
UserTask(
|
||||
id,
|
||||
PeriodicDueItemsArgs.taskName,
|
||||
@ -122,7 +122,7 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
||||
settings.summary,
|
||||
PeriodicDueItemsArgs(
|
||||
user,
|
||||
Right(channel),
|
||||
ch.map(c => ChannelRef(c.id, c.channelType, c.name)),
|
||||
settings.remindDays,
|
||||
if (settings.capOverdue) Some(settings.remindDays)
|
||||
else None,
|
||||
@ -140,20 +140,13 @@ object NotifyDueItemsRoutes extends MailAddressCodec {
|
||||
for {
|
||||
tinc <- backend.tag.loadAll(task.args.tagsInclude)
|
||||
texc <- backend.tag.loadAll(task.args.tagsExclude)
|
||||
|
||||
ch <- task.args.channel match {
|
||||
case Right(c) => NotificationChannel.convert(c).pure[F]
|
||||
case Left(ref) =>
|
||||
Sync[F].raiseError(
|
||||
new IllegalStateException(s"ChannelRefs are not supported: $ref")
|
||||
)
|
||||
}
|
||||
|
||||
} yield PeriodicDueItemsSettings(
|
||||
task.id,
|
||||
task.enabled,
|
||||
task.summary,
|
||||
ch,
|
||||
task.args.channels
|
||||
.map(c => NotificationChannelRef(c.id, c.channelType, c.name))
|
||||
.toList,
|
||||
task.timer,
|
||||
task.args.remindDays,
|
||||
task.args.daysBack.isDefined,
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.data.{NonEmptyList, OptionT}
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
@ -14,11 +14,11 @@ import docspell.backend.BackendApp
|
||||
import docspell.backend.MailAddressCodec
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.common._
|
||||
import docspell.notification.api.PeriodicQueryArgs
|
||||
import docspell.notification.api.{ChannelRef, PeriodicQueryArgs}
|
||||
import docspell.query.ItemQueryParser
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.Config
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.restserver.conv.{Conversions, NonEmptyListSupport}
|
||||
import docspell.restserver.http4s.ClientRequestInfo
|
||||
import docspell.store.usertask._
|
||||
|
||||
@ -27,7 +27,7 @@ import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
object PeriodicQueryRoutes extends MailAddressCodec with NonEmptyListSupport {
|
||||
|
||||
def apply[F[_]: Async](
|
||||
cfg: Config,
|
||||
@ -116,7 +116,9 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
): F[UserTask[PeriodicQueryArgs]] =
|
||||
Sync[F]
|
||||
.pure(for {
|
||||
ch <- NotificationChannel.convert(settings.channel)
|
||||
ch <- NonEmptyList
|
||||
.fromList(settings.channels)
|
||||
.toRight(new Exception(s"No channels found for: ${settings.channels}"))
|
||||
qstr <- settings.query match {
|
||||
case Some(q) =>
|
||||
ItemQueryParser
|
||||
@ -132,7 +134,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
else Left(new IllegalArgumentException("No query or bookmark provided"))
|
||||
} yield (ch, qstr.map(ItemQueryString.apply)))
|
||||
.rethrow
|
||||
.map { case (channel, qstr) =>
|
||||
.map { case (channels, qstr) =>
|
||||
UserTask(
|
||||
id,
|
||||
PeriodicQueryArgs.taskName,
|
||||
@ -141,7 +143,7 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
settings.summary,
|
||||
PeriodicQueryArgs(
|
||||
user,
|
||||
Right(channel),
|
||||
channels.map(r => ChannelRef(r.id, r.channelType, r.name)),
|
||||
qstr,
|
||||
settings.bookmark,
|
||||
Some(baseUrl / "app" / "item"),
|
||||
@ -153,22 +155,18 @@ object PeriodicQueryRoutes extends MailAddressCodec {
|
||||
def taskToSettings[F[_]: Sync](
|
||||
task: UserTask[PeriodicQueryArgs]
|
||||
): F[PeriodicQuerySettings] =
|
||||
for {
|
||||
ch <- task.args.channel match {
|
||||
case Right(c) => NotificationChannel.convert(c).pure[F]
|
||||
case Left(ref) =>
|
||||
Sync[F].raiseError(
|
||||
new IllegalStateException(s"ChannelRefs are not supported: $ref")
|
||||
)
|
||||
}
|
||||
} yield PeriodicQuerySettings(
|
||||
task.id,
|
||||
task.summary,
|
||||
task.enabled,
|
||||
ch,
|
||||
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
|
||||
task.args.bookmark,
|
||||
task.args.contentStart,
|
||||
task.timer
|
||||
Sync[F].pure(
|
||||
PeriodicQuerySettings(
|
||||
task.id,
|
||||
task.enabled,
|
||||
task.summary,
|
||||
task.args.channels
|
||||
.map(c => NotificationChannelRef(c.id, c.channelType, c.name))
|
||||
.toList,
|
||||
task.timer,
|
||||
task.args.query.map(_.query).map(ItemQueryParser.parseUnsafe),
|
||||
task.args.bookmark,
|
||||
task.args.contentStart
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
alter table "notification_channel_mail"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_gotify"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_matrix"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_http"
|
||||
add column "name" varchar(254);
|
@ -0,0 +1,33 @@
|
||||
CREATE TABLE "notification_hook_channel" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"hook_id" varchar(254) not null,
|
||||
"channel_mail" varchar(254),
|
||||
"channel_gotify" varchar(254),
|
||||
"channel_matrix" varchar(254),
|
||||
"channel_http" varchar(254),
|
||||
foreign key ("hook_id") references "notification_hook"("id") on delete cascade,
|
||||
foreign key ("channel_mail") references "notification_channel_mail"("id") on delete cascade,
|
||||
foreign key ("channel_gotify") references "notification_channel_gotify"("id") on delete cascade,
|
||||
foreign key ("channel_matrix") references "notification_channel_matrix"("id") on delete cascade,
|
||||
foreign key ("channel_http") references "notification_channel_http"("id") on delete cascade,
|
||||
unique("hook_id", "channel_mail"),
|
||||
unique("hook_id", "channel_gotify"),
|
||||
unique("hook_id", "channel_matrix"),
|
||||
unique("hook_id", "channel_http")
|
||||
);
|
||||
|
||||
insert into "notification_hook_channel" ("id", "hook_id", "channel_mail", "channel_gotify", "channel_matrix", "channel_http")
|
||||
select random_uuid(), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||
from "notification_hook";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_mail";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_gotify";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_matrix";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_http";
|
@ -0,0 +1,11 @@
|
||||
alter table `notification_channel_mail`
|
||||
add column `name` varchar(254);
|
||||
|
||||
alter table `notification_channel_gotify`
|
||||
add column `name` varchar(254);
|
||||
|
||||
alter table `notification_channel_matrix`
|
||||
add column `name` varchar(254);
|
||||
|
||||
alter table `notification_channel_http`
|
||||
add column `name` varchar(254);
|
@ -0,0 +1,42 @@
|
||||
CREATE TABLE `notification_hook_channel` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`hook_id` varchar(254) not null,
|
||||
`channel_mail` varchar(254),
|
||||
`channel_gotify` varchar(254),
|
||||
`channel_matrix` varchar(254),
|
||||
`channel_http` varchar(254),
|
||||
foreign key (`hook_id`) references `notification_hook`(`id`) on delete cascade,
|
||||
foreign key (`channel_mail`) references `notification_channel_mail`(`id`) on delete cascade,
|
||||
foreign key (`channel_gotify`) references `notification_channel_gotify`(`id`) on delete cascade,
|
||||
foreign key (`channel_matrix`) references `notification_channel_matrix`(`id`) on delete cascade,
|
||||
foreign key (`channel_http`) references `notification_channel_http`(`id`) on delete cascade,
|
||||
unique(`hook_id`, `channel_mail`),
|
||||
unique(`hook_id`, `channel_gotify`),
|
||||
unique(`hook_id`, `channel_matrix`),
|
||||
unique(`hook_id`, `channel_http`)
|
||||
);
|
||||
|
||||
insert into `notification_hook_channel`
|
||||
select md5(rand()), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||
from `notification_hook`;
|
||||
|
||||
alter table `notification_hook`
|
||||
drop constraint `notification_hook_ibfk_2`;
|
||||
alter table `notification_hook`
|
||||
drop constraint `notification_hook_ibfk_3`;
|
||||
alter table `notification_hook`
|
||||
drop constraint `notification_hook_ibfk_4`;
|
||||
alter table `notification_hook`
|
||||
drop constraint `notification_hook_ibfk_5`;
|
||||
|
||||
alter table `notification_hook`
|
||||
drop column `channel_mail`;
|
||||
|
||||
alter table `notification_hook`
|
||||
drop column `channel_gotify`;
|
||||
|
||||
alter table `notification_hook`
|
||||
drop column `channel_matrix`;
|
||||
|
||||
alter table `notification_hook`
|
||||
drop column `channel_http`;
|
@ -0,0 +1,11 @@
|
||||
alter table "notification_channel_mail"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_gotify"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_matrix"
|
||||
add column "name" varchar(254);
|
||||
|
||||
alter table "notification_channel_http"
|
||||
add column "name" varchar(254);
|
@ -0,0 +1,33 @@
|
||||
CREATE TABLE "notification_hook_channel" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"hook_id" varchar(254) not null,
|
||||
"channel_mail" varchar(254),
|
||||
"channel_gotify" varchar(254),
|
||||
"channel_matrix" varchar(254),
|
||||
"channel_http" varchar(254),
|
||||
foreign key ("hook_id") references "notification_hook"("id") on delete cascade,
|
||||
foreign key ("channel_mail") references "notification_channel_mail"("id") on delete cascade,
|
||||
foreign key ("channel_gotify") references "notification_channel_gotify"("id") on delete cascade,
|
||||
foreign key ("channel_matrix") references "notification_channel_matrix"("id") on delete cascade,
|
||||
foreign key ("channel_http") references "notification_channel_http"("id") on delete cascade,
|
||||
unique("hook_id", "channel_mail"),
|
||||
unique("hook_id", "channel_gotify"),
|
||||
unique("hook_id", "channel_matrix"),
|
||||
unique("hook_id", "channel_http")
|
||||
);
|
||||
|
||||
insert into "notification_hook_channel" ("id", "hook_id", "channel_mail", "channel_gotify", "channel_matrix", "channel_http")
|
||||
select md5(random()::text), id, channel_mail, channel_gotify, channel_matrix, channel_http
|
||||
from "notification_hook";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_mail";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_gotify";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_matrix";
|
||||
|
||||
alter table "notification_hook"
|
||||
drop column "channel_http";
|
@ -6,23 +6,23 @@
|
||||
|
||||
package db.migration
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.data.{NonEmptyList, OptionT}
|
||||
import cats.effect.{IO, Sync}
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.StringSyntax._
|
||||
import docspell.notification.api.Channel
|
||||
import docspell.notification.api.PeriodicDueItemsArgs
|
||||
import docspell.store.records.RPeriodicTask
|
||||
import docspell.notification.api._
|
||||
import docspell.store.records._
|
||||
|
||||
import db.migration.data.{PeriodicDueItemsArgsOld, PeriodicQueryArgsOld}
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import doobie.util.transactor.Strategy
|
||||
import emil.MailAddress
|
||||
import emil.javamail.syntax._
|
||||
import io.circe.Encoder
|
||||
import io.circe.syntax._
|
||||
import io.circe.{Decoder, Encoder}
|
||||
import org.flywaydb.core.api.migration.Context
|
||||
|
||||
trait MigrationTasks {
|
||||
@ -31,6 +31,8 @@ trait MigrationTasks {
|
||||
|
||||
implicit val jsonEncoder: Encoder[MailAddress] =
|
||||
Encoder.encodeString.contramap(_.asUnicodeString)
|
||||
implicit val jsonDecoder: Decoder[MailAddress] =
|
||||
Decoder.decodeString.emap(MailAddress.parse)
|
||||
|
||||
def migrateDueItemTasks: ConnectionIO[Unit] =
|
||||
for {
|
||||
@ -42,20 +44,114 @@ trait MigrationTasks {
|
||||
_ <- RPeriodicTask.setEnabledByTask(NotifyDueItemsArgs.taskName, false)
|
||||
} yield ()
|
||||
|
||||
def migrateDueItemTask1(old: RPeriodicTask): ConnectionIO[Int] = {
|
||||
val converted = old.args
|
||||
.parseJsonAs[NotifyDueItemsArgs]
|
||||
.leftMap(_.getMessage())
|
||||
.flatMap(convertArgs)
|
||||
def migratePeriodicItemTasks: ConnectionIO[Unit] =
|
||||
for {
|
||||
tasks2 <- RPeriodicTask.findByTask(PeriodicDueItemsArgsOld.taskName)
|
||||
tasks3 <- RPeriodicTask.findByTask(PeriodicQueryArgsOld.taskName)
|
||||
size = tasks2.size + tasks3.size
|
||||
_ <- Sync[ConnectionIO].delay(
|
||||
logger.info(s"Starting to migrate $size user tasks")
|
||||
)
|
||||
_ <- tasks2.traverse(migratePeriodicDueItemsTask)
|
||||
_ <- tasks3.traverse(migratePeriodicQueryTask)
|
||||
_ <- RPeriodicTask.setEnabledByTask(PeriodicQueryArgsOld.taskName, false)
|
||||
_ <- RPeriodicTask.setEnabledByTask(PeriodicDueItemsArgsOld.taskName, false)
|
||||
} yield ()
|
||||
|
||||
converted match {
|
||||
case Right(args) =>
|
||||
Sync[ConnectionIO].delay(logger.info(s"Converting user task: $old")) *>
|
||||
private def migratePeriodicQueryTask(old: RPeriodicTask): ConnectionIO[Int] =
|
||||
old.args
|
||||
.parseJsonAs[PeriodicQueryArgsOld]
|
||||
.leftMap { ex =>
|
||||
logger.error(ex)(s"Error migrating tasks")
|
||||
0.pure[ConnectionIO]
|
||||
}
|
||||
.map { oldArgs =>
|
||||
val ref = oldArgs.channel match {
|
||||
case Right(c) => saveChannel(c, oldArgs.account)
|
||||
case Left(ref) => ref.pure[ConnectionIO]
|
||||
}
|
||||
|
||||
ref.flatMap(channelRef =>
|
||||
RPeriodicTask.updateTask(
|
||||
old.id,
|
||||
PeriodicQueryArgs.taskName,
|
||||
PeriodicQueryArgs(
|
||||
oldArgs.account,
|
||||
NonEmptyList.of(channelRef),
|
||||
oldArgs.query,
|
||||
oldArgs.bookmark,
|
||||
oldArgs.baseUrl,
|
||||
oldArgs.contentStart
|
||||
).asJson.noSpaces
|
||||
)
|
||||
)
|
||||
}
|
||||
.fold(identity, identity)
|
||||
|
||||
private def migratePeriodicDueItemsTask(old: RPeriodicTask): ConnectionIO[Int] =
|
||||
old.args
|
||||
.parseJsonAs[PeriodicDueItemsArgsOld]
|
||||
.leftMap { ex =>
|
||||
logger.error(ex)(s"Error migrating tasks")
|
||||
0.pure[ConnectionIO]
|
||||
}
|
||||
.map { oldArgs =>
|
||||
val ref = oldArgs.channel match {
|
||||
case Right(c) => saveChannel(c, oldArgs.account)
|
||||
case Left(ref) => ref.pure[ConnectionIO]
|
||||
}
|
||||
|
||||
ref.flatMap(channelRef =>
|
||||
RPeriodicTask.updateTask(
|
||||
old.id,
|
||||
PeriodicDueItemsArgs.taskName,
|
||||
args.asJson.noSpaces
|
||||
PeriodicDueItemsArgs(
|
||||
oldArgs.account,
|
||||
NonEmptyList.of(channelRef),
|
||||
oldArgs.remindDays,
|
||||
oldArgs.daysBack,
|
||||
oldArgs.tagsInclude,
|
||||
oldArgs.tagsExclude,
|
||||
oldArgs.baseUrl
|
||||
).asJson.noSpaces
|
||||
)
|
||||
)
|
||||
}
|
||||
.fold(identity, identity)
|
||||
|
||||
private def saveChannel(ch: Channel, account: AccountId): ConnectionIO[ChannelRef] =
|
||||
(for {
|
||||
newId <- OptionT.liftF(Ident.randomId[ConnectionIO])
|
||||
userId <- OptionT(RUser.findIdByAccount(account))
|
||||
r <- RNotificationChannel.fromChannel(ch, newId, userId)
|
||||
_ <- OptionT.liftF(RNotificationChannel.insert(r))
|
||||
_ <- OptionT.liftF(
|
||||
Sync[ConnectionIO].delay(logger.debug(s"Created channel $r for $account"))
|
||||
)
|
||||
ref = r.asRef
|
||||
} yield ref)
|
||||
.getOrElseF(Sync[ConnectionIO].raiseError(new Exception("User not found!")))
|
||||
|
||||
private def migrateDueItemTask1(old: RPeriodicTask): ConnectionIO[Int] = {
|
||||
val converted = old.args
|
||||
.parseJsonAs[NotifyDueItemsArgs]
|
||||
.leftMap(_.getMessage())
|
||||
.map(convertArgs)
|
||||
|
||||
converted match {
|
||||
case Right(args) =>
|
||||
val task = args
|
||||
.semiflatMap(a =>
|
||||
RPeriodicTask
|
||||
.updateTask(
|
||||
old.id,
|
||||
PeriodicDueItemsArgs.taskName,
|
||||
a.asJson.noSpaces
|
||||
)
|
||||
)
|
||||
.getOrElse(0)
|
||||
|
||||
Sync[ConnectionIO].delay(logger.info(s"Converting user task: $old")) *> task
|
||||
|
||||
case Left(err) =>
|
||||
logger.error(s"Error converting user task: $old. $err")
|
||||
@ -63,22 +159,44 @@ trait MigrationTasks {
|
||||
}
|
||||
}
|
||||
|
||||
def convertArgs(old: NotifyDueItemsArgs): Either[String, PeriodicDueItemsArgs] =
|
||||
old.recipients
|
||||
.traverse(MailAddress.parse)
|
||||
.flatMap(l => NonEmptyList.fromList(l).toRight("No recipients provided"))
|
||||
.map { rec =>
|
||||
PeriodicDueItemsArgs(
|
||||
old.account,
|
||||
Right(Channel.Mail(Ident.unsafe(""), old.smtpConnection, rec)),
|
||||
old.remindDays,
|
||||
old.daysBack,
|
||||
old.tagsInclude,
|
||||
old.tagsExclude,
|
||||
old.itemDetailUrl
|
||||
)
|
||||
private def convertArgs(
|
||||
old: NotifyDueItemsArgs
|
||||
): OptionT[ConnectionIO, PeriodicDueItemsArgs] = {
|
||||
val recs = old.recipients
|
||||
.map(MailAddress.parse)
|
||||
.flatMap {
|
||||
case Right(m) => Some(m)
|
||||
case Left(err) =>
|
||||
logger.warn(s"Cannot read mail address: $err. Skip this while migrating.")
|
||||
None
|
||||
}
|
||||
|
||||
for {
|
||||
userId <- OptionT(RUser.findIdByAccount(old.account))
|
||||
id <- OptionT.liftF(Ident.randomId[ConnectionIO])
|
||||
now <- OptionT.liftF(Timestamp.current[ConnectionIO])
|
||||
chName = Some("migrate notify items")
|
||||
ch = RNotificationChannelMail(
|
||||
id,
|
||||
userId,
|
||||
chName,
|
||||
old.smtpConnection,
|
||||
recs,
|
||||
now
|
||||
)
|
||||
_ <- OptionT.liftF(RNotificationChannelMail.insert(ch))
|
||||
args = PeriodicDueItemsArgs(
|
||||
old.account,
|
||||
NonEmptyList.of(ChannelRef(ch.id, ChannelType.Mail, chName)),
|
||||
old.remindDays,
|
||||
old.daysBack,
|
||||
old.tagsInclude,
|
||||
old.tagsExclude,
|
||||
old.itemDetailUrl
|
||||
)
|
||||
} yield args
|
||||
}
|
||||
|
||||
def mkTransactor(ctx: Context): Transactor[IO] = {
|
||||
val xa = Transactor.fromConnection[IO](ctx.getConnection())
|
||||
Transactor.strategy.set(xa, Strategy.void) // transactions are handled by flyway
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration.data
|
||||
|
||||
import docspell.common._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
/** Arguments to the notification task.
|
||||
*
|
||||
* This tasks queries items with a due date and informs the user via mail.
|
||||
*
|
||||
* If the structure changes, there must be some database migration to update or remove
|
||||
* the json data of the corresponding task.
|
||||
*/
|
||||
final case class PeriodicDueItemsArgsOld(
|
||||
account: AccountId,
|
||||
channel: ChannelOrRef,
|
||||
remindDays: Int,
|
||||
daysBack: Option[Int],
|
||||
tagsInclude: List[Ident],
|
||||
tagsExclude: List[Ident],
|
||||
baseUrl: Option[LenientUri]
|
||||
)
|
||||
|
||||
object PeriodicDueItemsArgsOld {
|
||||
val taskName = Ident.unsafe("periodic-due-items-notify")
|
||||
|
||||
implicit def jsonDecoder(implicit
|
||||
mc: Decoder[MailAddress]
|
||||
): Decoder[PeriodicDueItemsArgsOld] = {
|
||||
implicit val x = ChannelOrRef.jsonDecoder
|
||||
semiauto.deriveDecoder
|
||||
}
|
||||
|
||||
implicit def jsonEncoder(implicit
|
||||
mc: Encoder[MailAddress]
|
||||
): Encoder[PeriodicDueItemsArgsOld] = {
|
||||
implicit val x = ChannelOrRef.jsonEncoder
|
||||
semiauto.deriveEncoder
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration.data
|
||||
|
||||
import docspell.common._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.generic.semiauto
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
final case class PeriodicQueryArgsOld(
|
||||
account: AccountId,
|
||||
channel: ChannelOrRef,
|
||||
query: Option[ItemQueryString],
|
||||
bookmark: Option[String],
|
||||
baseUrl: Option[LenientUri],
|
||||
contentStart: Option[String]
|
||||
)
|
||||
|
||||
object PeriodicQueryArgsOld {
|
||||
val taskName = Ident.unsafe("periodic-query-notify")
|
||||
|
||||
implicit def jsonDecoder(implicit
|
||||
mc: Decoder[MailAddress]
|
||||
): Decoder[PeriodicQueryArgsOld] = {
|
||||
implicit val x = ChannelOrRef.jsonDecoder
|
||||
semiauto.deriveDecoder
|
||||
}
|
||||
|
||||
implicit def jsonEncoder(implicit
|
||||
mc: Encoder[MailAddress]
|
||||
): Encoder[PeriodicQueryArgsOld] = {
|
||||
implicit val x = ChannelOrRef.jsonEncoder
|
||||
semiauto.deriveEncoder
|
||||
}
|
||||
}
|
@ -4,13 +4,14 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.notification
|
||||
package db.migration
|
||||
|
||||
import docspell.notification.api._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
package object api {
|
||||
|
||||
package object data {
|
||||
type ChannelOrRef = Either[ChannelRef, Channel]
|
||||
|
||||
object ChannelOrRef {
|
||||
@ -25,5 +26,4 @@ package object api {
|
||||
cr.fold(_.channelType, _.channelType)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration.h2
|
||||
|
||||
import cats.effect.unsafe.implicits._
|
||||
|
||||
import db.migration.MigrationTasks
|
||||
import doobie.implicits._
|
||||
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||
|
||||
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration.mariadb
|
||||
|
||||
import cats.effect.unsafe.implicits._
|
||||
|
||||
import db.migration.MigrationTasks
|
||||
import doobie.implicits._
|
||||
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||
|
||||
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration.postgresql
|
||||
|
||||
import cats.effect.unsafe.implicits._
|
||||
|
||||
import db.migration.MigrationTasks
|
||||
import doobie.implicits._
|
||||
import org.flywaydb.core.api.migration.{BaseJavaMigration, Context}
|
||||
|
||||
class V1_32_2__MigrateChannels extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migratePeriodicItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import java.time.{Instant, LocalDate}
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
import docspell.notification.api.EventType
|
||||
import docspell.notification.api.{ChannelType, EventType}
|
||||
import docspell.query.{ItemQuery, ItemQueryParser}
|
||||
import docspell.totp.Key
|
||||
|
||||
@ -156,6 +156,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
|
||||
implicit val metaJsonMiniQuery: Meta[JsonMiniQuery] =
|
||||
Meta[String].timap(JsonMiniQuery.unsafeParse)(_.unsafeAsString)
|
||||
|
||||
implicit val channelTypeRead: Read[ChannelType] =
|
||||
Read[String].map(ChannelType.unsafeFromString)
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
|
@ -27,7 +27,11 @@ object QNotification {
|
||||
def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] =
|
||||
for {
|
||||
hooks <- listHooks(event.account.collective, event.eventType)
|
||||
chs <- hooks.traverse(readHookChannel)
|
||||
chs <- hooks.traverse(h =>
|
||||
listChannels(h.id)
|
||||
.flatMap(_.flatTraverse(hc => readHookChannel(h.uid, hc)))
|
||||
.map(c => HookChannel(h, c))
|
||||
)
|
||||
} yield chs
|
||||
|
||||
// --
|
||||
@ -50,21 +54,27 @@ object QNotification {
|
||||
)
|
||||
).query[RNotificationHook].to[Vector]
|
||||
|
||||
def listChannels(hookId: Ident): ConnectionIO[Vector[RNotificationHookChannel]] =
|
||||
RNotificationHookChannel.allOf(hookId)
|
||||
|
||||
def readHookChannel(
|
||||
hook: RNotificationHook
|
||||
): ConnectionIO[HookChannel] =
|
||||
userId: Ident,
|
||||
hook: RNotificationHookChannel
|
||||
): ConnectionIO[Vector[NotificationChannel]] =
|
||||
for {
|
||||
c1 <- read(hook.channelMail)(RNotificationChannelMail.getById)(
|
||||
c1 <- read(hook.channelMail)(RNotificationChannelMail.getById(userId))(
|
||||
ChannelMap.readMail
|
||||
)
|
||||
c2 <- read(hook.channelGotify)(RNotificationChannelGotify.getById)(
|
||||
c2 <- read(hook.channelGotify)(RNotificationChannelGotify.getById(userId))(
|
||||
ChannelMap.readGotify
|
||||
)
|
||||
c3 <- read(hook.channelMatrix)(RNotificationChannelMatrix.getById)(
|
||||
c3 <- read(hook.channelMatrix)(RNotificationChannelMatrix.getById(userId))(
|
||||
ChannelMap.readMatrix
|
||||
)
|
||||
c4 <- read(hook.channelHttp)(RNotificationChannelHttp.getById)(ChannelMap.readHttp)
|
||||
} yield HookChannel(hook, c1 ++ c2 ++ c3 ++ c4)
|
||||
c4 <- read(hook.channelHttp)(RNotificationChannelHttp.getById(userId))(
|
||||
ChannelMap.readHttp
|
||||
)
|
||||
} yield c1 ++ c2 ++ c3 ++ c4
|
||||
|
||||
def readChannel(ch: RNotificationChannel): ConnectionIO[Vector[NotificationChannel]] =
|
||||
ch.fold(
|
||||
|
@ -7,16 +7,29 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api.ChannelRef
|
||||
import docspell.notification.api.ChannelType
|
||||
import docspell.notification.api.{Channel, ChannelRef, ChannelType}
|
||||
|
||||
import doobie._
|
||||
|
||||
sealed trait RNotificationChannel {
|
||||
|
||||
def id: Ident
|
||||
def id: Ident = fold(_.id, _.id, _.id, _.id)
|
||||
|
||||
def name: Option[String] = fold(_.name, _.name, _.name, _.name)
|
||||
|
||||
def channelType: ChannelType =
|
||||
fold(
|
||||
_ => ChannelType.Mail,
|
||||
_ => ChannelType.Gotify,
|
||||
_ => ChannelType.Matrix,
|
||||
_ => ChannelType.Http
|
||||
)
|
||||
|
||||
def asRef: ChannelRef =
|
||||
ChannelRef(id, channelType, name)
|
||||
|
||||
def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
@ -24,7 +37,6 @@ sealed trait RNotificationChannel {
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A
|
||||
|
||||
}
|
||||
|
||||
object RNotificationChannel {
|
||||
@ -37,8 +49,6 @@ object RNotificationChannel {
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f1(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Gotify(r: RNotificationChannelGotify) extends RNotificationChannel {
|
||||
@ -48,8 +58,6 @@ object RNotificationChannel {
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f2(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Matrix(r: RNotificationChannelMatrix) extends RNotificationChannel {
|
||||
@ -59,8 +67,6 @@ object RNotificationChannel {
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f3(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Http(r: RNotificationChannelHttp) extends RNotificationChannel {
|
||||
@ -70,8 +76,6 @@ object RNotificationChannel {
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f4(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
def insert(r: RNotificationChannel): ConnectionIO[Int] =
|
||||
@ -100,42 +104,60 @@ object RNotificationChannel {
|
||||
Matrix.apply
|
||||
) ++ http.map(Http.apply)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Vector[RNotificationChannel]] =
|
||||
def getById(id: Ident, userId: Ident): ConnectionIO[Vector[RNotificationChannel]] =
|
||||
for {
|
||||
mail <- RNotificationChannelMail.getById(id)
|
||||
gotify <- RNotificationChannelGotify.getById(id)
|
||||
matrix <- RNotificationChannelMatrix.getById(id)
|
||||
http <- RNotificationChannelHttp.getById(id)
|
||||
mail <- RNotificationChannelMail.getById(userId)(id)
|
||||
gotify <- RNotificationChannelGotify.getById(userId)(id)
|
||||
matrix <- RNotificationChannelMatrix.getById(userId)(id)
|
||||
http <- RNotificationChannelHttp.getById(userId)(id)
|
||||
} yield mail.map(Email.apply).toVector ++
|
||||
gotify.map(Gotify.apply).toVector ++
|
||||
matrix.map(Matrix.apply).toVector ++
|
||||
http.map(Http.apply).toVector
|
||||
|
||||
def getByRef(ref: ChannelRef): ConnectionIO[Option[RNotificationChannel]] =
|
||||
def getByRef(
|
||||
ref: ChannelRef,
|
||||
userId: Ident
|
||||
): ConnectionIO[Option[RNotificationChannel]] =
|
||||
ref.channelType match {
|
||||
case ChannelType.Mail =>
|
||||
RNotificationChannelMail.getById(ref.id).map(_.map(Email.apply))
|
||||
RNotificationChannelMail.getById(userId)(ref.id).map(_.map(Email.apply))
|
||||
case ChannelType.Matrix =>
|
||||
RNotificationChannelMatrix.getById(ref.id).map(_.map(Matrix.apply))
|
||||
RNotificationChannelMatrix.getById(userId)(ref.id).map(_.map(Matrix.apply))
|
||||
case ChannelType.Gotify =>
|
||||
RNotificationChannelGotify.getById(ref.id).map(_.map(Gotify.apply))
|
||||
RNotificationChannelGotify.getById(userId)(ref.id).map(_.map(Gotify.apply))
|
||||
case ChannelType.Http =>
|
||||
RNotificationChannelHttp.getById(ref.id).map(_.map(Http.apply))
|
||||
RNotificationChannelHttp.getById(userId)(ref.id).map(_.map(Http.apply))
|
||||
}
|
||||
|
||||
def getByHook(r: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
||||
def getByHook(hook: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
||||
def opt(id: Option[Ident]): OptionT[ConnectionIO, Ident] =
|
||||
OptionT.fromOption(id)
|
||||
|
||||
for {
|
||||
mail <- opt(r.channelMail).flatMapF(RNotificationChannelMail.getById).value
|
||||
gotify <- opt(r.channelGotify).flatMapF(RNotificationChannelGotify.getById).value
|
||||
matrix <- opt(r.channelMatrix).flatMapF(RNotificationChannelMatrix.getById).value
|
||||
http <- opt(r.channelHttp).flatMapF(RNotificationChannelHttp.getById).value
|
||||
} yield mail.map(Email.apply).toVector ++
|
||||
gotify.map(Gotify.apply).toVector ++
|
||||
matrix.map(Matrix.apply).toVector ++
|
||||
http.map(Http.apply).toVector
|
||||
def find(
|
||||
r: RNotificationHookChannel
|
||||
): ConnectionIO[Vector[RNotificationChannel]] =
|
||||
for {
|
||||
mail <- opt(r.channelMail)
|
||||
.flatMapF(RNotificationChannelMail.getById(hook.uid))
|
||||
.value
|
||||
gotify <- opt(r.channelGotify)
|
||||
.flatMapF(RNotificationChannelGotify.getById(hook.uid))
|
||||
.value
|
||||
matrix <- opt(r.channelMatrix)
|
||||
.flatMapF(RNotificationChannelMatrix.getById(hook.uid))
|
||||
.value
|
||||
http <- opt(r.channelHttp)
|
||||
.flatMapF(RNotificationChannelHttp.getById(hook.uid))
|
||||
.value
|
||||
} yield mail.map(Email.apply).toVector ++
|
||||
gotify.map(Gotify.apply).toVector ++
|
||||
matrix.map(Matrix.apply).toVector ++
|
||||
http.map(Http.apply).toVector
|
||||
|
||||
RNotificationHookChannel
|
||||
.allOf(hook.id)
|
||||
.flatMap(_.flatTraverse(find))
|
||||
}
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] =
|
||||
@ -145,4 +167,63 @@ object RNotificationChannel {
|
||||
n3 <- RNotificationChannelMatrix.deleteByAccount(id, account)
|
||||
n4 <- RNotificationChannelHttp.deleteByAccount(id, account)
|
||||
} yield n1 + n2 + n3 + n4
|
||||
|
||||
def fromChannel(
|
||||
channel: Channel,
|
||||
id: Ident,
|
||||
userId: Ident
|
||||
): OptionT[ConnectionIO, RNotificationChannel] =
|
||||
for {
|
||||
time <- OptionT.liftF(Timestamp.current[ConnectionIO])
|
||||
logger = Logger.log4s[ConnectionIO](org.log4s.getLogger)
|
||||
r <-
|
||||
channel match {
|
||||
case Channel.Mail(_, name, conn, recipients) =>
|
||||
for {
|
||||
_ <- OptionT.liftF(
|
||||
logger.debug(
|
||||
s"Looking up user smtp for ${userId.id} and ${conn.id}"
|
||||
)
|
||||
)
|
||||
mailConn <- OptionT(RUserEmail.getByUser(userId, conn))
|
||||
rec = RNotificationChannelMail(
|
||||
id,
|
||||
userId,
|
||||
name,
|
||||
mailConn.id,
|
||||
recipients.toList,
|
||||
time
|
||||
).vary
|
||||
} yield rec
|
||||
case Channel.Gotify(_, name, url, appKey, prio) =>
|
||||
OptionT.pure[ConnectionIO](
|
||||
RNotificationChannelGotify(
|
||||
id,
|
||||
userId,
|
||||
name,
|
||||
url,
|
||||
appKey,
|
||||
prio,
|
||||
time
|
||||
).vary
|
||||
)
|
||||
case Channel.Matrix(_, name, homeServer, roomId, accessToken) =>
|
||||
OptionT.pure[ConnectionIO](
|
||||
RNotificationChannelMatrix(
|
||||
id,
|
||||
userId,
|
||||
name,
|
||||
homeServer,
|
||||
roomId,
|
||||
accessToken,
|
||||
"m.text",
|
||||
time
|
||||
).vary
|
||||
)
|
||||
case Channel.Http(_, name, url) =>
|
||||
OptionT.pure[ConnectionIO](
|
||||
RNotificationChannelHttp(id, userId, name, url, time).vary
|
||||
)
|
||||
}
|
||||
} yield r
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
||||
final case class RNotificationChannelGotify(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
name: Option[String],
|
||||
url: LenientUri,
|
||||
appKey: Password,
|
||||
priority: Option[Int],
|
||||
@ -34,27 +35,32 @@ object RNotificationChannelGotify {
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val name = Column[String]("name", this)
|
||||
val url = Column[LenientUri]("url", this)
|
||||
val appKey = Column[Password]("app_key", this)
|
||||
val priority = Column[Int]("priority", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, url, appKey, priority, created)
|
||||
NonEmptyList.of(id, uid, name, url, appKey, priority, created)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelGotify]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelGotify].option
|
||||
def getById(
|
||||
userId: Ident
|
||||
)(id: Ident): ConnectionIO[Option[RNotificationChannelGotify]] =
|
||||
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||
.query[RNotificationChannelGotify]
|
||||
.option
|
||||
|
||||
def insert(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.url},${r.appKey},${r.priority},${r.created}"
|
||||
sql"${r.id},${r.uid},${r.name},${r.url},${r.appKey},${r.priority},${r.created}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||
@ -64,7 +70,8 @@ object RNotificationChannelGotify {
|
||||
DML.set(
|
||||
T.url.setTo(r.url),
|
||||
T.appKey.setTo(r.appKey),
|
||||
T.priority.setTo(r.priority)
|
||||
T.priority.setTo(r.priority),
|
||||
T.name.setTo(r.name)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
||||
final case class RNotificationChannelHttp(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
name: Option[String],
|
||||
url: LenientUri,
|
||||
created: Timestamp
|
||||
) {
|
||||
@ -32,25 +33,32 @@ object RNotificationChannelHttp {
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val name = Column[String]("name", this)
|
||||
val url = Column[LenientUri]("url", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, url, created)
|
||||
NonEmptyList.of(id, uid, name, url, created)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelHttp]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelHttp].option
|
||||
def getById(userId: Ident)(id: Ident): ConnectionIO[Option[RNotificationChannelHttp]] =
|
||||
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||
.query[RNotificationChannelHttp]
|
||||
.option
|
||||
|
||||
def insert(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
||||
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.url},${r.created}")
|
||||
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.name},${r.url},${r.created}")
|
||||
|
||||
def update(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
||||
DML.update(T, T.id === r.id && T.uid === r.uid, DML.set(T.url.setTo(r.url)))
|
||||
DML.update(
|
||||
T,
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(T.url.setTo(r.url), T.name.setTo(r.name))
|
||||
)
|
||||
|
||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = {
|
||||
val user = RUser.as("u")
|
||||
|
@ -19,6 +19,7 @@ import emil.MailAddress
|
||||
final case class RNotificationChannelMail(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
name: Option[String],
|
||||
connection: Ident,
|
||||
recipients: List[MailAddress],
|
||||
created: Timestamp
|
||||
@ -34,12 +35,13 @@ object RNotificationChannelMail {
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val name = Column[String]("name", this)
|
||||
val connection = Column[Ident]("conn_id", this)
|
||||
val recipients = Column[List[MailAddress]]("recipients", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, connection, recipients, created)
|
||||
NonEmptyList.of(id, uid, name, connection, recipients, created)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
@ -49,7 +51,7 @@ object RNotificationChannelMail {
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.connection},${r.recipients},${r.created}"
|
||||
sql"${r.id},${r.uid},${r.name},${r.connection},${r.recipients},${r.created}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannelMail): ConnectionIO[Int] =
|
||||
@ -58,12 +60,15 @@ object RNotificationChannelMail {
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(
|
||||
T.connection.setTo(r.connection),
|
||||
T.recipients.setTo(r.recipients.toList)
|
||||
T.recipients.setTo(r.recipients.toList),
|
||||
T.name.setTo(r.name)
|
||||
)
|
||||
)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMail]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMail].option
|
||||
def getById(userId: Ident)(id: Ident): ConnectionIO[Option[RNotificationChannelMail]] =
|
||||
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||
.query[RNotificationChannelMail]
|
||||
.option
|
||||
|
||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = {
|
||||
val user = RUser.as("u")
|
||||
|
@ -18,6 +18,7 @@ import doobie.implicits._
|
||||
final case class RNotificationChannelMatrix(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
name: Option[String],
|
||||
homeServer: LenientUri,
|
||||
roomId: String,
|
||||
accessToken: Password,
|
||||
@ -34,6 +35,7 @@ object RNotificationChannelMatrix {
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val name = Column[String]("name", this)
|
||||
val homeServer = Column[LenientUri]("home_server", this)
|
||||
val roomId = Column[String]("room_id", this)
|
||||
val accessToken = Column[Password]("access_token", this)
|
||||
@ -41,7 +43,16 @@ object RNotificationChannelMatrix {
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, homeServer, roomId, accessToken, messageType, created)
|
||||
NonEmptyList.of(
|
||||
id,
|
||||
uid,
|
||||
name,
|
||||
homeServer,
|
||||
roomId,
|
||||
accessToken,
|
||||
messageType,
|
||||
created
|
||||
)
|
||||
}
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
@ -50,7 +61,7 @@ object RNotificationChannelMatrix {
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.homeServer},${r.roomId},${r.accessToken},${r.messageType},${r.created}"
|
||||
sql"${r.id},${r.uid},${r.name},${r.homeServer},${r.roomId},${r.accessToken},${r.messageType},${r.created}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannelMatrix): ConnectionIO[Int] =
|
||||
@ -61,12 +72,17 @@ object RNotificationChannelMatrix {
|
||||
T.homeServer.setTo(r.homeServer),
|
||||
T.roomId.setTo(r.roomId),
|
||||
T.accessToken.setTo(r.accessToken),
|
||||
T.messageType.setTo(r.messageType)
|
||||
T.messageType.setTo(r.messageType),
|
||||
T.name.setTo(r.name)
|
||||
)
|
||||
)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMatrix]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMatrix].option
|
||||
def getById(userId: Ident)(
|
||||
id: Ident
|
||||
): ConnectionIO[Option[RNotificationChannelMatrix]] =
|
||||
run(select(T.all), from(T), T.id === id && T.uid === userId)
|
||||
.query[RNotificationChannelMatrix]
|
||||
.option
|
||||
|
||||
def getByAccount(
|
||||
account: AccountId
|
||||
|
@ -7,7 +7,6 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
@ -22,115 +21,18 @@ final case class RNotificationHook(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
enabled: Boolean,
|
||||
channelMail: Option[Ident],
|
||||
channelGotify: Option[Ident],
|
||||
channelMatrix: Option[Ident],
|
||||
channelHttp: Option[Ident],
|
||||
allEvents: Boolean,
|
||||
eventFilter: Option[JsonMiniQuery],
|
||||
created: Timestamp
|
||||
) {
|
||||
def channelId: Ident =
|
||||
channelMail
|
||||
.orElse(channelGotify)
|
||||
.orElse(channelMatrix)
|
||||
.orElse(channelHttp)
|
||||
.getOrElse(
|
||||
sys.error(s"Illegal internal state: notification hook has no channel: ${id.id}")
|
||||
)
|
||||
}
|
||||
) {}
|
||||
|
||||
object RNotificationHook {
|
||||
def mail(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
enabled: Boolean,
|
||||
channelMail: Ident,
|
||||
created: Timestamp
|
||||
): RNotificationHook =
|
||||
RNotificationHook(
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
channelMail.some,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
created
|
||||
)
|
||||
|
||||
def gotify(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
enabled: Boolean,
|
||||
channelGotify: Ident,
|
||||
created: Timestamp
|
||||
): RNotificationHook =
|
||||
RNotificationHook(
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
None,
|
||||
channelGotify.some,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
created
|
||||
)
|
||||
|
||||
def matrix(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
enabled: Boolean,
|
||||
channelMatrix: Ident,
|
||||
created: Timestamp
|
||||
): RNotificationHook =
|
||||
RNotificationHook(
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
None,
|
||||
None,
|
||||
channelMatrix.some,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
created
|
||||
)
|
||||
|
||||
def http(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
enabled: Boolean,
|
||||
channelHttp: Ident,
|
||||
created: Timestamp
|
||||
): RNotificationHook =
|
||||
RNotificationHook(
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
channelHttp.some,
|
||||
false,
|
||||
None,
|
||||
created
|
||||
)
|
||||
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_hook"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val enabled = Column[Boolean]("enabled", this)
|
||||
val channelMail = Column[Ident]("channel_mail", this)
|
||||
val channelGotify = Column[Ident]("channel_gotify", this)
|
||||
val channelMatrix = Column[Ident]("channel_matrix", this)
|
||||
val channelHttp = Column[Ident]("channel_http", this)
|
||||
val allEvents = Column[Boolean]("all_events", this)
|
||||
val eventFilter = Column[JsonMiniQuery]("event_filter", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
@ -140,10 +42,6 @@ object RNotificationHook {
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
channelMail,
|
||||
channelGotify,
|
||||
channelMatrix,
|
||||
channelHttp,
|
||||
allEvents,
|
||||
eventFilter,
|
||||
created
|
||||
@ -157,7 +55,7 @@ object RNotificationHook {
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.enabled},${r.channelMail},${r.channelGotify},${r.channelMatrix},${r.channelHttp},${r.allEvents},${r.eventFilter},${r.created}"
|
||||
sql"${r.id},${r.uid},${r.enabled},${r.allEvents},${r.eventFilter},${r.created}"
|
||||
)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
@ -174,10 +72,6 @@ object RNotificationHook {
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(
|
||||
T.enabled.setTo(r.enabled),
|
||||
T.channelMail.setTo(r.channelMail),
|
||||
T.channelGotify.setTo(r.channelGotify),
|
||||
T.channelMatrix.setTo(r.channelMatrix),
|
||||
T.channelHttp.setTo(r.channelHttp),
|
||||
T.allEvents.setTo(r.allEvents),
|
||||
T.eventFilter.setTo(r.eventFilter)
|
||||
)
|
||||
|
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.{NonEmptyList => Nel}
|
||||
import cats.effect.Sync
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api.{ChannelRef, ChannelType}
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RNotificationHookChannel(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
channelMail: Option[Ident],
|
||||
channelGotify: Option[Ident],
|
||||
channelMatrix: Option[Ident],
|
||||
channelHttp: Option[Ident]
|
||||
) {
|
||||
|
||||
def channelId: Ident =
|
||||
channelMail
|
||||
.orElse(channelGotify)
|
||||
.orElse(channelMatrix)
|
||||
.orElse(channelHttp)
|
||||
.getOrElse(
|
||||
sys.error(s"Illegal internal state: notification hook has no channel: $this")
|
||||
)
|
||||
|
||||
def channelType: ChannelType =
|
||||
channelMail
|
||||
.map(_ => ChannelType.Mail)
|
||||
.orElse(channelGotify.map(_ => ChannelType.Gotify))
|
||||
.orElse(channelMatrix.map(_ => ChannelType.Matrix))
|
||||
.orElse(channelHttp.map(_ => ChannelType.Http))
|
||||
.getOrElse(
|
||||
sys.error(s"Illegal internal state: notification hook has no channel: $this")
|
||||
)
|
||||
}
|
||||
object RNotificationHookChannel {
|
||||
def fromRef(id: Ident, hookId: Ident, ref: ChannelRef): RNotificationHookChannel =
|
||||
ref.channelType match {
|
||||
case ChannelType.Mail => mail(id, hookId, ref.id)
|
||||
case ChannelType.Gotify => gotify(id, hookId, ref.id)
|
||||
case ChannelType.Matrix => matrix(id, hookId, ref.id)
|
||||
case ChannelType.Http => http(id, hookId, ref.id)
|
||||
}
|
||||
|
||||
def mail(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
channelMail: Ident
|
||||
): RNotificationHookChannel =
|
||||
RNotificationHookChannel(
|
||||
id,
|
||||
hookId,
|
||||
channelMail.some,
|
||||
None,
|
||||
None,
|
||||
None
|
||||
)
|
||||
|
||||
def gotify(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
channelGotify: Ident
|
||||
): RNotificationHookChannel =
|
||||
RNotificationHookChannel(
|
||||
id,
|
||||
hookId,
|
||||
None,
|
||||
channelGotify.some,
|
||||
None,
|
||||
None
|
||||
)
|
||||
|
||||
def matrix(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
channelMatrix: Ident
|
||||
): RNotificationHookChannel =
|
||||
RNotificationHookChannel(
|
||||
id,
|
||||
hookId,
|
||||
None,
|
||||
None,
|
||||
channelMatrix.some,
|
||||
None
|
||||
)
|
||||
|
||||
def http(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
channelHttp: Ident
|
||||
): RNotificationHookChannel =
|
||||
RNotificationHookChannel(
|
||||
id,
|
||||
hookId,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
channelHttp.some
|
||||
)
|
||||
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_hook_channel"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val hookId = Column[Ident]("hook_id", this)
|
||||
val channelMail = Column[Ident]("channel_mail", this)
|
||||
val channelGotify = Column[Ident]("channel_gotify", this)
|
||||
val channelMatrix = Column[Ident]("channel_matrix", this)
|
||||
val channelHttp = Column[Ident]("channel_http", this)
|
||||
|
||||
val all: Nel[Column[_]] =
|
||||
Nel.of(id, hookId, channelMail, channelGotify, channelMatrix, channelHttp)
|
||||
}
|
||||
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
val T: Table = Table(None)
|
||||
|
||||
def insert(r: RNotificationHookChannel): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.hookId},${r.channelMail},${r.channelGotify},${r.channelMatrix},${r.channelHttp}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationHookChannel): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.id === r.id && T.hookId === r.hookId,
|
||||
DML.set(
|
||||
T.channelMail.setTo(r.channelMail),
|
||||
T.channelGotify.setTo(r.channelGotify),
|
||||
T.channelMatrix.setTo(r.channelMatrix),
|
||||
T.channelHttp.setTo(r.channelHttp)
|
||||
)
|
||||
)
|
||||
|
||||
def deleteByHook(hookId: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.hookId === hookId)
|
||||
|
||||
def insertAll(rs: List[RNotificationHookChannel]): ConnectionIO[Int] =
|
||||
rs.traverse(insert).map(_.sum)
|
||||
|
||||
def updateAll(hookId: Ident, channels: List[ChannelRef]): ConnectionIO[Int] =
|
||||
channels
|
||||
.traverse(ref => Ident.randomId[ConnectionIO].map(id => fromRef(id, hookId, ref)))
|
||||
.flatMap(all => deleteByHook(hookId) *> insertAll(all))
|
||||
|
||||
def allOf(hookId: Ident): ConnectionIO[Vector[RNotificationHookChannel]] =
|
||||
Select(select(T.all), from(T), T.hookId === hookId).build
|
||||
.query[RNotificationHookChannel]
|
||||
.to[Vector]
|
||||
|
||||
def allOfNel(hookId: Ident): ConnectionIO[Nel[RNotificationHookChannel]] =
|
||||
allOf(hookId)
|
||||
.map(Nel.fromFoldable[Vector, RNotificationHookChannel])
|
||||
.flatMap(
|
||||
_.map(_.pure[ConnectionIO]).getOrElse(
|
||||
Sync[ConnectionIO]
|
||||
.raiseError(new Exception(s"Hook '${hookId.id}' has no associated channels!"))
|
||||
)
|
||||
)
|
||||
|
||||
def resolveRefs(rs: Nel[RNotificationHookChannel]): ConnectionIO[List[ChannelRef]] = {
|
||||
val cmail = RNotificationChannelMail.as("cmail")
|
||||
val cgotify = RNotificationChannelGotify.as("cgotify")
|
||||
val cmatrix = RNotificationChannelMatrix.as("cmatrix")
|
||||
val chttp = RNotificationChannelHttp.as("chttp")
|
||||
|
||||
def selectRef(
|
||||
idList: List[Ident],
|
||||
idCol: Column[Ident],
|
||||
nameCol: Column[String],
|
||||
ctype: ChannelType,
|
||||
table: TableDef
|
||||
) =
|
||||
Nel
|
||||
.fromList(idList)
|
||||
.map(ids =>
|
||||
Select(
|
||||
select(idCol.s, const(ctype.name), nameCol.s),
|
||||
from(table),
|
||||
idCol.in(ids)
|
||||
)
|
||||
)
|
||||
|
||||
val mailRefs = selectRef(
|
||||
rs.toList.flatMap(_.channelMail),
|
||||
cmail.id,
|
||||
cmail.name,
|
||||
ChannelType.Mail,
|
||||
cmail
|
||||
)
|
||||
val gotifyRefs = selectRef(
|
||||
rs.toList.flatMap(_.channelGotify),
|
||||
cgotify.id,
|
||||
cgotify.name,
|
||||
ChannelType.Gotify,
|
||||
cgotify
|
||||
)
|
||||
val matrixRefs = selectRef(
|
||||
rs.toList.flatMap(_.channelMatrix),
|
||||
cmatrix.id,
|
||||
cmatrix.name,
|
||||
ChannelType.Matrix,
|
||||
cmatrix
|
||||
)
|
||||
val httpRefs = selectRef(
|
||||
rs.toList.flatMap(_.channelHttp),
|
||||
chttp.id,
|
||||
chttp.name,
|
||||
ChannelType.Http,
|
||||
chttp
|
||||
)
|
||||
|
||||
val queries = List(mailRefs, gotifyRefs, matrixRefs, httpRefs).flatten
|
||||
Nel.fromList(queries) match {
|
||||
case Some(nel) => union(nel.head, nel.tail: _*).build.query[ChannelRef].to[List]
|
||||
case None => List.empty[ChannelRef].pure[ConnectionIO]
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ module Api exposing
|
||||
, checkCalEvent
|
||||
, confirmMultiple
|
||||
, confirmOtp
|
||||
, createChannel
|
||||
, createHook
|
||||
, createImapSettings
|
||||
, createMailSettings
|
||||
@ -34,6 +35,7 @@ module Api exposing
|
||||
, deleteAttachment
|
||||
, deleteAttachments
|
||||
, deleteBookmark
|
||||
, deleteChannel
|
||||
, deleteCustomField
|
||||
, deleteCustomValue
|
||||
, deleteCustomValueMultiple
|
||||
@ -56,6 +58,8 @@ module Api exposing
|
||||
, fileURL
|
||||
, getAttachmentMeta
|
||||
, getBookmarks
|
||||
, getChannels
|
||||
, getChannelsIgnoreError
|
||||
, getClientSettings
|
||||
, getCollective
|
||||
, getCollectiveSettings
|
||||
@ -172,6 +176,7 @@ module Api exposing
|
||||
, twoFactor
|
||||
, unconfirmMultiple
|
||||
, updateBookmark
|
||||
, updateChannel
|
||||
, updateHook
|
||||
, updateNotifyDueItems
|
||||
, updatePeriodicQuery
|
||||
@ -229,6 +234,7 @@ import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||
import Api.Model.NewCustomField exposing (NewCustomField)
|
||||
import Api.Model.NewFolder exposing (NewFolder)
|
||||
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
||||
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||
import Api.Model.NotificationSampleEventReq exposing (NotificationSampleEventReq)
|
||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
||||
import Api.Model.OptionalId exposing (OptionalId)
|
||||
@ -239,6 +245,8 @@ import Api.Model.OtpConfirm exposing (OtpConfirm)
|
||||
import Api.Model.OtpResult exposing (OtpResult)
|
||||
import Api.Model.OtpState exposing (OtpState)
|
||||
import Api.Model.PasswordChange exposing (PasswordChange)
|
||||
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Api.Model.Person exposing (Person)
|
||||
import Api.Model.PersonList exposing (PersonList)
|
||||
import Api.Model.ReferenceList exposing (ReferenceList)
|
||||
@ -274,10 +282,8 @@ import Data.EquipmentOrder exposing (EquipmentOrder)
|
||||
import Data.EventType exposing (EventType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.FolderOrder exposing (FolderOrder)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Data.OrganizationOrder exposing (OrganizationOrder)
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Data.PersonOrder exposing (PersonOrder)
|
||||
import Data.Priority exposing (Priority)
|
||||
import Data.TagOrder exposing (TagOrder)
|
||||
@ -604,7 +610,7 @@ startOnceNotifyDueItems flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -618,7 +624,7 @@ updateNotifyDueItems flags settings receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -632,7 +638,7 @@ createNotifyDueItems flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -645,7 +651,7 @@ getNotifyDueItems flags receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicDueItemsSettings.decoder)
|
||||
, expect = Http.expectJson receive (JsonDecode.list Api.Model.PeriodicDueItemsSettings.decoder)
|
||||
}
|
||||
|
||||
|
||||
@ -658,7 +664,7 @@ submitNotifyDueItems flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicDueItemsSettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicDueItemsSettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -689,7 +695,7 @@ startOncePeriodicQuery flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery/startonce"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -703,7 +709,7 @@ updatePeriodicQuery flags settings receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -717,7 +723,7 @@ createPeriodicQuery flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -730,7 +736,7 @@ getPeriodicQuery flags receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive (JsonDecode.list Data.PeriodicQuerySettings.decoder)
|
||||
, expect = Http.expectJson receive (JsonDecode.list Api.Model.PeriodicQuerySettings.decoder)
|
||||
}
|
||||
|
||||
|
||||
@ -743,7 +749,7 @@ submitPeriodicQuery flags settings receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/periodicquery"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.PeriodicQuerySettings.encode settings)
|
||||
, body = Http.jsonBody (Api.Model.PeriodicQuerySettings.encode settings)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -2576,6 +2582,63 @@ shareFileURL attachId =
|
||||
|
||||
|
||||
|
||||
--- NotificationChannel
|
||||
|
||||
|
||||
getChannelsTask : Flags -> Task.Task Http.Error (List NotificationChannel)
|
||||
getChannelsTask flags =
|
||||
Http2.authTask
|
||||
{ method = "GET"
|
||||
, url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||
, account = getAccount flags
|
||||
, body = Http.emptyBody
|
||||
, resolver = Http2.jsonResolver (JsonDecode.list Data.NotificationChannel.decoder)
|
||||
, headers = []
|
||||
, timeout = Nothing
|
||||
}
|
||||
|
||||
|
||||
getChannelsIgnoreError : Flags -> (List NotificationChannel -> msg) -> Cmd msg
|
||||
getChannelsIgnoreError flags tagger =
|
||||
getChannelsTask flags
|
||||
|> Task.attempt (Result.map tagger >> Result.withDefault (tagger []))
|
||||
|
||||
|
||||
getChannels : Flags -> (Result Http.Error (List NotificationChannel) -> msg) -> Cmd msg
|
||||
getChannels flags receive =
|
||||
getChannelsTask flags |> Task.attempt receive
|
||||
|
||||
|
||||
deleteChannel : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
deleteChannel flags id receive =
|
||||
Http2.authDelete
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel/" ++ id
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
createChannel : Flags -> NotificationChannel -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
createChannel flags hook receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.NotificationChannel.encode hook)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
updateChannel : Flags -> NotificationChannel -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
updateChannel flags hook receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/channel"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.NotificationChannel.encode hook)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- NotificationHook
|
||||
|
||||
|
||||
@ -2584,7 +2647,7 @@ getHooks flags receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive (JsonDecode.list Data.NotificationHook.decoder)
|
||||
, expect = Http.expectJson receive (JsonDecode.list Api.Model.NotificationHook.decoder)
|
||||
}
|
||||
|
||||
|
||||
@ -2602,7 +2665,7 @@ createHook flags hook receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
||||
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -2612,7 +2675,7 @@ updateHook flags hook receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
||||
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
@ -2642,7 +2705,7 @@ testHook flags hook receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/notification/hook/sendTestEvent"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Data.NotificationHook.encode hook)
|
||||
, body = Http.jsonBody (Api.Model.NotificationHook.encode hook)
|
||||
, expect = Http.expectJson receive Api.Model.NotificationChannelTestResult.decoder
|
||||
}
|
||||
|
||||
|
@ -48,17 +48,11 @@ type alias HttpModel =
|
||||
}
|
||||
|
||||
|
||||
type alias RefModel =
|
||||
{ channelType : ChannelType
|
||||
}
|
||||
|
||||
|
||||
type Model
|
||||
= Matrix MatrixModel
|
||||
| Gotify GotifyModel
|
||||
| Mail MailModel
|
||||
| Http HttpModel
|
||||
| Ref RefModel
|
||||
|
||||
|
||||
type Msg
|
||||
@ -147,11 +141,6 @@ initWith flags channel =
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Data.NotificationChannel.Ref m ->
|
||||
( Ref { channelType = m.channelType }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
channelType : Model -> ChannelType
|
||||
channelType model =
|
||||
@ -168,9 +157,6 @@ channelType model =
|
||||
Http _ ->
|
||||
Data.ChannelType.Http
|
||||
|
||||
Ref ref ->
|
||||
ref.channelType
|
||||
|
||||
|
||||
getChannel : Model -> Maybe NotificationChannel
|
||||
getChannel model =
|
||||
@ -187,9 +173,6 @@ getChannel model =
|
||||
Http mm ->
|
||||
Maybe.map Data.NotificationChannel.Http mm.value
|
||||
|
||||
Ref _ ->
|
||||
Nothing
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
@ -269,12 +252,3 @@ view texts settings model =
|
||||
Http m ->
|
||||
Html.map HttpMsg
|
||||
(Comp.NotificationHttpForm.view texts.httpForm m.form)
|
||||
|
||||
-- Note: currently when retrieving hooks, this is not
|
||||
-- send from the server. The server always sends
|
||||
-- concrete channel details. However, it is possible
|
||||
-- to create hooks with a reference to an existing
|
||||
-- channel, but this is not supported in this client.
|
||||
-- So this channel is ignored here.
|
||||
Ref _ ->
|
||||
span [ class "hidden" ] []
|
||||
|
154
modules/webapp/src/main/elm/Comp/ChannelRefInput.elm
Normal file
154
modules/webapp/src/main/elm/Comp/ChannelRefInput.elm
Normal file
@ -0,0 +1,154 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.ChannelRefInput exposing (Model, Msg, getSelected, init, initSelected, initWith, setOptions, setSelected, update, view)
|
||||
|
||||
import Api
|
||||
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||
import Comp.Dropdown exposing (Option)
|
||||
import Data.ChannelType
|
||||
import Data.DropdownStyle
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (Html)
|
||||
import Messages.Comp.ChannelRefInput exposing (Texts)
|
||||
import Util.String
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ ddm : Comp.Dropdown.Model NotificationChannelRef
|
||||
, all : List NotificationChannelRef
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= DropdownMsg (Comp.Dropdown.Msg NotificationChannelRef)
|
||||
| LoadChannelsResp (List NotificationChannelRef)
|
||||
|
||||
|
||||
emptyModel : Model
|
||||
emptyModel =
|
||||
{ ddm = makeDropdownModel
|
||||
, all = []
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( emptyModel, getOptions flags )
|
||||
|
||||
|
||||
getOptions : Flags -> Cmd Msg
|
||||
getOptions flags =
|
||||
Api.getChannelsIgnoreError flags (List.map Data.NotificationChannel.getRef >> LoadChannelsResp)
|
||||
|
||||
|
||||
setOptions : List NotificationChannelRef -> Msg
|
||||
setOptions refs =
|
||||
LoadChannelsResp refs
|
||||
|
||||
|
||||
initSelected : Flags -> List NotificationChannelRef -> ( Model, Cmd Msg )
|
||||
initSelected flags selected =
|
||||
( update (setSelected selected) emptyModel
|
||||
|> Tuple.first
|
||||
, getOptions flags
|
||||
)
|
||||
|
||||
|
||||
initWith : List NotificationChannelRef -> List NotificationChannelRef -> Model
|
||||
initWith options selected =
|
||||
update (setSelected selected) emptyModel
|
||||
|> Tuple.first
|
||||
|> update (setOptions options)
|
||||
|> Tuple.first
|
||||
|
||||
|
||||
getSelected : Model -> List NotificationChannelRef
|
||||
getSelected model =
|
||||
Comp.Dropdown.getSelected model.ddm
|
||||
|
||||
|
||||
setSelected : List NotificationChannelRef -> Msg
|
||||
setSelected refs =
|
||||
DropdownMsg (Comp.Dropdown.SetSelection refs)
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
DropdownMsg lm ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.Dropdown.update lm model.ddm
|
||||
in
|
||||
( { model | ddm = dm }
|
||||
, Cmd.map DropdownMsg dc
|
||||
)
|
||||
|
||||
LoadChannelsResp refs ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.Dropdown.update (Comp.Dropdown.SetOptions refs) model.ddm
|
||||
in
|
||||
( { model
|
||||
| all = refs
|
||||
, ddm = dm
|
||||
}
|
||||
, Cmd.map DropdownMsg dc
|
||||
)
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
let
|
||||
idShort id =
|
||||
String.slice 0 6 id
|
||||
|
||||
joinName name ct =
|
||||
Option (ct ++ " (" ++ name ++ ")") ""
|
||||
|
||||
mkName ref =
|
||||
Data.ChannelType.fromString ref.channelType
|
||||
|> Maybe.map texts.channelType
|
||||
|> Maybe.withDefault ref.channelType
|
||||
|> joinName (Maybe.withDefault (idShort ref.id) ref.name)
|
||||
|
||||
viewCfg =
|
||||
{ makeOption = mkName
|
||||
, placeholder = texts.placeholder
|
||||
, labelColor = \_ -> \_ -> ""
|
||||
, style = Data.DropdownStyle.mainStyle
|
||||
}
|
||||
in
|
||||
Html.map DropdownMsg
|
||||
(Comp.Dropdown.view2 viewCfg settings model.ddm)
|
||||
|
||||
|
||||
|
||||
--- Helpers
|
||||
|
||||
|
||||
makeDropdownModel : Comp.Dropdown.Model NotificationChannelRef
|
||||
makeDropdownModel =
|
||||
let
|
||||
m =
|
||||
Comp.Dropdown.makeModel
|
||||
{ multiple = True
|
||||
, searchable = \n -> n > 0
|
||||
}
|
||||
in
|
||||
{ m | searchWithAdditional = True }
|
@ -16,22 +16,18 @@ module Comp.DueItemsTaskForm exposing
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.Tag exposing (Tag)
|
||||
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Api.Model.TagList exposing (TagList)
|
||||
import Comp.Basic as B
|
||||
import Comp.CalEventInput
|
||||
import Comp.ChannelForm
|
||||
import Comp.ChannelRefInput
|
||||
import Comp.IntField
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.TagDropdown
|
||||
import Comp.YesNoDimmer
|
||||
import Data.CalEvent exposing (CalEvent)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Data.TagOrder
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Data.Validated exposing (Validated(..))
|
||||
@ -43,13 +39,11 @@ import Markdown
|
||||
import Messages.Comp.DueItemsTaskForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
import Util.Tag
|
||||
import Util.Update
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ settings : PeriodicDueItemsSettings
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, channelModel : Comp.ChannelRefInput.Model
|
||||
, tagInclModel : Comp.TagDropdown.Model
|
||||
, tagExclModel : Comp.TagDropdown.Model
|
||||
, remindDays : Maybe Int
|
||||
@ -99,18 +93,14 @@ type Msg
|
||||
| RequestDelete
|
||||
| YesNoDeleteMsg Comp.YesNoDimmer.Msg
|
||||
| SetSummary String
|
||||
| ChannelMsg Comp.ChannelForm.Msg
|
||||
| ChannelMsg Comp.ChannelRefInput.Msg
|
||||
|
||||
|
||||
initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
|
||||
initWith flags s =
|
||||
let
|
||||
ct =
|
||||
Data.NotificationChannel.channelType s.channel
|
||||
|> Maybe.withDefault Data.ChannelType.Matrix
|
||||
|
||||
( im, ic ) =
|
||||
init flags ct
|
||||
init flags
|
||||
|
||||
newSchedule =
|
||||
Data.CalEvent.fromEvent s.schedule
|
||||
@ -120,7 +110,7 @@ initWith flags s =
|
||||
Comp.CalEventInput.init flags newSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.initWith flags s.channel
|
||||
Comp.ChannelRefInput.initSelected flags s.channels
|
||||
in
|
||||
( { im
|
||||
| settings = s
|
||||
@ -145,8 +135,8 @@ initWith flags s =
|
||||
)
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
let
|
||||
initialSchedule =
|
||||
Data.CalEvent.everyMonth
|
||||
@ -155,9 +145,9 @@ init flags ct =
|
||||
Comp.CalEventInput.init flags initialSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
Comp.ChannelRefInput.init flags
|
||||
in
|
||||
( { settings = Data.PeriodicDueItemsSettings.empty ct
|
||||
( { settings = Api.Model.PeriodicDueItemsSettings.empty
|
||||
, channelModel = cfm
|
||||
, tagInclModel = Comp.TagDropdown.initWith [] []
|
||||
, tagExclModel = Comp.TagDropdown.initWith [] []
|
||||
@ -203,11 +193,17 @@ makeSettings model =
|
||||
Err ValidateCalEventInvalid
|
||||
|
||||
channelM =
|
||||
Result.fromMaybe
|
||||
ValidateChannelRequired
|
||||
(Comp.ChannelForm.getChannel model.channelModel)
|
||||
let
|
||||
list =
|
||||
Comp.ChannelRefInput.getSelected model.channelModel
|
||||
in
|
||||
if list == [] then
|
||||
Err ValidateChannelRequired
|
||||
|
||||
make days timer channel =
|
||||
else
|
||||
Ok list
|
||||
|
||||
make days timer channels =
|
||||
{ prev
|
||||
| tagsInclude = Comp.TagDropdown.getSelected model.tagInclModel
|
||||
, tagsExclude = Comp.TagDropdown.getSelected model.tagExclModel
|
||||
@ -216,7 +212,7 @@ makeSettings model =
|
||||
, enabled = model.enabled
|
||||
, schedule = Data.CalEvent.makeEvent timer
|
||||
, summary = model.summary
|
||||
, channel = channel
|
||||
, channels = channels
|
||||
}
|
||||
in
|
||||
Result.map3 make
|
||||
@ -247,7 +243,7 @@ update flags msg model =
|
||||
ChannelMsg lm ->
|
||||
let
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
Comp.ChannelRefInput.update lm model.channelModel
|
||||
in
|
||||
( { model | channelModel = cfm }
|
||||
, NoAction
|
||||
@ -538,9 +534,9 @@ view2 texts extraClasses settings model =
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
[ formHeader texts.channelHeader
|
||||
, Html.map ChannelMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||
]
|
||||
, formHeader texts.queryLabel
|
||||
, div [ class "mb-4" ]
|
||||
|
@ -14,15 +14,16 @@ module Comp.DueItemsTaskList exposing
|
||||
, view2
|
||||
)
|
||||
|
||||
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelRef
|
||||
import Data.ChannelType
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.DueItemsTaskList exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
import Util.List
|
||||
|
||||
|
||||
type alias Model =
|
||||
@ -94,9 +95,7 @@ viewItem2 texts item =
|
||||
]
|
||||
]
|
||||
, td [ class "text-left mr-2" ]
|
||||
[ Data.NotificationChannel.channelType item.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
[ div [ class " space-x-1" ]
|
||||
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] item.channels)
|
||||
]
|
||||
]
|
||||
|
@ -15,13 +15,11 @@ module Comp.DueItemsTaskManage exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Comp.ChannelMenu
|
||||
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Comp.DueItemsTaskForm
|
||||
import Comp.DueItemsTaskList
|
||||
import Comp.MenuBar as MB
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -35,7 +33,6 @@ type alias Model =
|
||||
, detailModel : Maybe Comp.DueItemsTaskForm.Model
|
||||
, items : List PeriodicDueItemsSettings
|
||||
, formState : FormState
|
||||
, channelMenuOpen : Bool
|
||||
}
|
||||
|
||||
|
||||
@ -57,9 +54,8 @@ type Msg
|
||||
= ListMsg Comp.DueItemsTaskList.Msg
|
||||
| DetailMsg Comp.DueItemsTaskForm.Msg
|
||||
| GetDataResp (Result Http.Error (List PeriodicDueItemsSettings))
|
||||
| NewTaskInit ChannelType
|
||||
| NewTaskInit
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| ToggleChannelMenu
|
||||
|
||||
|
||||
initModel : Model
|
||||
@ -68,7 +64,6 @@ initModel =
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, formState = FormStateInitial
|
||||
, channelMenuOpen = False
|
||||
}
|
||||
|
||||
|
||||
@ -89,11 +84,6 @@ init flags =
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
ToggleChannelMenu ->
|
||||
( { model | channelMenuOpen = not model.channelMenuOpen }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GetDataResp (Ok items) ->
|
||||
( { model
|
||||
| items = items
|
||||
@ -194,12 +184,12 @@ update flags msg model =
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
NewTaskInit ct ->
|
||||
NewTaskInit ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.DueItemsTaskForm.init flags ct
|
||||
Comp.DueItemsTaskForm.init flags
|
||||
in
|
||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
@ -295,18 +285,15 @@ viewForm2 texts settings model =
|
||||
|
||||
viewList2 : Texts -> Model -> List (Html Msg)
|
||||
viewList2 texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.channelMenuOpen
|
||||
, toggleMenu = ToggleChannelMenu
|
||||
, menuLabel = texts.newTask
|
||||
, onItem = NewTaskInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = NewTaskInit
|
||||
, title = texts.newTask
|
||||
, icon = Just "fa fa-plus"
|
||||
, label = texts.newTask
|
||||
}
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
|
@ -115,8 +115,8 @@ dropdownCfg texts =
|
||||
}
|
||||
|
||||
|
||||
viewJson : Texts -> Model -> Html Msg
|
||||
viewJson texts model =
|
||||
viewJson : Texts -> Bool -> Model -> Html Msg
|
||||
viewJson texts enableEventChooser model =
|
||||
let
|
||||
json =
|
||||
Result.withDefault ""
|
||||
@ -125,7 +125,10 @@ viewJson texts model =
|
||||
div
|
||||
[ class "flex flex-col w-full relative"
|
||||
]
|
||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
||||
[ div
|
||||
[ class "flex inline-flex items-center absolute top-2 right-4"
|
||||
, classList [ ( "hidden", not enableEventChooser ) ]
|
||||
]
|
||||
[ Html.map EventTypeMsg
|
||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||
False
|
||||
@ -144,8 +147,8 @@ viewJson texts model =
|
||||
]
|
||||
|
||||
|
||||
viewMessage : Texts -> Model -> Html Msg
|
||||
viewMessage texts model =
|
||||
viewMessage : Texts -> Bool -> Model -> Html Msg
|
||||
viewMessage texts enableEventChooser model =
|
||||
let
|
||||
titleDecoder =
|
||||
D.at [ "message", "title" ] D.string
|
||||
@ -162,7 +165,10 @@ viewMessage texts model =
|
||||
div
|
||||
[ class "flex flex-col w-full relative"
|
||||
]
|
||||
[ div [ class "flex inline-flex items-center absolute top-2 right-4" ]
|
||||
[ div
|
||||
[ class "flex inline-flex items-center absolute top-2 right-4"
|
||||
, classList [ ( "hidden", not enableEventChooser ) ]
|
||||
]
|
||||
[ Html.map EventTypeMsg
|
||||
(Comp.FixedDropdown.viewStyled2 (dropdownCfg texts)
|
||||
False
|
||||
|
454
modules/webapp/src/main/elm/Comp/NotificationChannelManage.elm
Normal file
454
modules/webapp/src/main/elm/Comp/NotificationChannelManage.elm
Normal file
@ -0,0 +1,454 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationChannelManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Comp.Basic as B
|
||||
import Comp.ChannelForm
|
||||
import Comp.ChannelMenu
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationChannelTable
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Http
|
||||
import Messages.Comp.NotificationChannelManage exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ listModel : Comp.NotificationChannelTable.Model
|
||||
, detailModel : Maybe Comp.ChannelForm.Model
|
||||
, items : List NotificationChannel
|
||||
, deleteConfirm : DeleteConfirm
|
||||
, loading : Bool
|
||||
, formState : FormState
|
||||
, newChannelMenuOpen : Bool
|
||||
, jsonFilterError : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type DeleteConfirm
|
||||
= DeleteConfirmOff
|
||||
| DeleteConfirmOn
|
||||
|
||||
|
||||
type SubmitType
|
||||
= SubmitDelete
|
||||
| SubmitUpdate
|
||||
| SubmitCreate
|
||||
|
||||
|
||||
type FormState
|
||||
= FormStateInitial
|
||||
| FormErrorHttp Http.Error
|
||||
| FormSubmitSuccessful SubmitType
|
||||
| FormErrorSubmit String
|
||||
| FormErrorInvalid
|
||||
|
||||
|
||||
type Msg
|
||||
= TableMsg Comp.NotificationChannelTable.Msg
|
||||
| DetailMsg Comp.ChannelForm.Msg
|
||||
| GetDataResp (Result Http.Error (List NotificationChannel))
|
||||
| ToggleNewChannelMenu
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| NewChannelInit ChannelType
|
||||
| BackToTable
|
||||
| Submit
|
||||
| RequestDelete
|
||||
| CancelDelete
|
||||
| DeleteChannelNow String
|
||||
|
||||
|
||||
initModel : Model
|
||||
initModel =
|
||||
{ listModel = Comp.NotificationChannelTable.init
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, loading = False
|
||||
, formState = FormStateInitial
|
||||
, newChannelMenuOpen = False
|
||||
, deleteConfirm = DeleteConfirmOff
|
||||
, jsonFilterError = Nothing
|
||||
}
|
||||
|
||||
|
||||
initCmd : Flags -> Cmd Msg
|
||||
initCmd flags =
|
||||
Api.getChannels flags GetDataResp
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( initModel, initCmd flags )
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update flags msg model =
|
||||
case msg of
|
||||
GetDataResp (Ok res) ->
|
||||
( { model
|
||||
| items = res
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
GetDataResp (Err err) ->
|
||||
( { model | formState = FormErrorHttp err }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
TableMsg lm ->
|
||||
let
|
||||
( mm, action ) =
|
||||
Comp.NotificationChannelTable.update flags lm model.listModel
|
||||
|
||||
( detail, cmd ) =
|
||||
case action of
|
||||
Comp.NotificationChannelTable.NoAction ->
|
||||
( Nothing, Cmd.none )
|
||||
|
||||
Comp.NotificationChannelTable.EditAction channel ->
|
||||
let
|
||||
( dm, dc ) =
|
||||
Comp.ChannelForm.initWith flags channel
|
||||
in
|
||||
( Just dm, Cmd.map DetailMsg dc )
|
||||
in
|
||||
( { model
|
||||
| listModel = mm
|
||||
, detailModel = detail
|
||||
}
|
||||
, cmd
|
||||
)
|
||||
|
||||
DetailMsg lm ->
|
||||
case model.detailModel of
|
||||
Just dm ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.ChannelForm.update flags lm dm
|
||||
in
|
||||
( { model | detailModel = Just mm }
|
||||
, Cmd.map DetailMsg mc
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ToggleNewChannelMenu ->
|
||||
( { model | newChannelMenuOpen = not model.newChannelMenuOpen }, Cmd.none )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
| formState =
|
||||
if res.success then
|
||||
FormSubmitSuccessful submitType
|
||||
|
||||
else
|
||||
FormErrorSubmit res.message
|
||||
, detailModel =
|
||||
if submitType == SubmitDelete then
|
||||
Nothing
|
||||
|
||||
else
|
||||
model.detailModel
|
||||
, loading = False
|
||||
}
|
||||
, if submitType == SubmitDelete then
|
||||
initCmd flags
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
)
|
||||
|
||||
SubmitResp _ (Err err) ->
|
||||
( { model | formState = FormErrorHttp err, loading = False }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
NewChannelInit ct ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
in
|
||||
( { model | detailModel = Just mm, newChannelMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||
|
||||
BackToTable ->
|
||||
( { model | detailModel = Nothing }, initCmd flags )
|
||||
|
||||
Submit ->
|
||||
case model.detailModel of
|
||||
Just dm ->
|
||||
case Comp.ChannelForm.getChannel dm of
|
||||
Just data ->
|
||||
postChannel flags data model
|
||||
|
||||
Nothing ->
|
||||
( { model | formState = FormErrorInvalid }, Cmd.none )
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
RequestDelete ->
|
||||
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none )
|
||||
|
||||
CancelDelete ->
|
||||
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none )
|
||||
|
||||
DeleteChannelNow id ->
|
||||
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
|
||||
, Api.deleteChannel flags id (SubmitResp SubmitDelete)
|
||||
)
|
||||
|
||||
|
||||
postChannel : Flags -> NotificationChannel -> Model -> ( Model, Cmd Msg )
|
||||
postChannel flags channel model =
|
||||
if (Data.NotificationChannel.getRef channel |> .id) == "" then
|
||||
( { model | loading = True }, Api.createChannel flags channel (SubmitResp SubmitCreate) )
|
||||
|
||||
else
|
||||
( { model | loading = True }, Api.updateChannel flags channel (SubmitResp SubmitUpdate) )
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
view : Texts -> UiSettings -> Model -> Html Msg
|
||||
view texts settings model =
|
||||
div [ class "flex flex-col" ]
|
||||
(case model.detailModel of
|
||||
Just msett ->
|
||||
viewForm texts settings model msett
|
||||
|
||||
Nothing ->
|
||||
viewList texts model
|
||||
)
|
||||
|
||||
|
||||
viewState : Texts -> Model -> Html Msg
|
||||
viewState texts model =
|
||||
div
|
||||
[ classList
|
||||
[ ( S.errorMessage, not (isSuccess model.formState) )
|
||||
, ( S.successMessage, isSuccess model.formState )
|
||||
, ( "hidden", model.formState == FormStateInitial )
|
||||
]
|
||||
, class "mb-2"
|
||||
]
|
||||
[ case model.formState of
|
||||
FormStateInitial ->
|
||||
text ""
|
||||
|
||||
FormSubmitSuccessful SubmitCreate ->
|
||||
text texts.channelCreated
|
||||
|
||||
FormSubmitSuccessful SubmitUpdate ->
|
||||
text texts.channelUpdated
|
||||
|
||||
FormSubmitSuccessful SubmitDelete ->
|
||||
text texts.channelDeleted
|
||||
|
||||
FormErrorSubmit m ->
|
||||
text m
|
||||
|
||||
FormErrorHttp err ->
|
||||
text (texts.httpError err)
|
||||
|
||||
FormErrorInvalid ->
|
||||
text texts.formInvalid
|
||||
]
|
||||
|
||||
|
||||
isSuccess : FormState -> Bool
|
||||
isSuccess state =
|
||||
case state of
|
||||
FormSubmitSuccessful _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
viewForm : Texts -> UiSettings -> Model -> Comp.ChannelForm.Model -> List (Html Msg)
|
||||
viewForm texts settings outerModel model =
|
||||
let
|
||||
channelId =
|
||||
Comp.ChannelForm.getChannel model
|
||||
|> Maybe.map Data.NotificationChannel.getRef
|
||||
|> Maybe.map .id
|
||||
|
||||
newChannel =
|
||||
channelId |> (==) (Just "")
|
||||
|
||||
headline =
|
||||
case Comp.ChannelForm.channelType model of
|
||||
Data.ChannelType.Matrix ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://matrix.org"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Matrix"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Mail ->
|
||||
span []
|
||||
[ text texts.notifyEmailInfo
|
||||
]
|
||||
|
||||
Data.ChannelType.Gotify ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://gotify.net"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Gotify"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
span []
|
||||
[ text texts.postRequestInfo
|
||||
]
|
||||
in
|
||||
[ h1 [ class S.header2 ]
|
||||
[ Data.ChannelType.icon (Comp.ChannelForm.channelType model) "w-8 h-8 inline-block mr-2"
|
||||
, if newChannel then
|
||||
text texts.addChannel
|
||||
|
||||
else
|
||||
text texts.updateChannel
|
||||
, div [ class "text-xs opacity-50 font-mono" ]
|
||||
[ Maybe.withDefault "" channelId |> text
|
||||
]
|
||||
]
|
||||
, div [ class "pt-2 pb-4 font-medium" ]
|
||||
[ headline
|
||||
]
|
||||
, MB.view
|
||||
{ start =
|
||||
[ MB.CustomElement <|
|
||||
B.primaryButton
|
||||
{ handler = onClick Submit
|
||||
, title = texts.basics.submitThisForm
|
||||
, icon = "fa fa-save"
|
||||
, label = texts.basics.submit
|
||||
, disabled = False
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
, MB.SecondaryButton
|
||||
{ tagger = BackToTable
|
||||
, title = texts.basics.backToList
|
||||
, icon = Just "fa fa-arrow-left"
|
||||
, label = texts.basics.backToList
|
||||
}
|
||||
]
|
||||
, end =
|
||||
if not newChannel then
|
||||
[ MB.DeleteButton
|
||||
{ tagger = RequestDelete
|
||||
, title = texts.deleteThisChannel
|
||||
, icon = Just "fa fa-trash"
|
||||
, label = texts.basics.delete
|
||||
}
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, div [ class "mt-2" ]
|
||||
[ viewState texts outerModel
|
||||
]
|
||||
, Html.map DetailMsg
|
||||
(Comp.ChannelForm.view texts.notificationForm settings model)
|
||||
, B.loadingDimmer
|
||||
{ active = outerModel.loading
|
||||
, label = texts.basics.loading
|
||||
}
|
||||
, B.contentDimmer
|
||||
(outerModel.deleteConfirm == DeleteConfirmOn)
|
||||
(div [ class "flex flex-col" ]
|
||||
[ div [ class "text-lg" ]
|
||||
[ i [ class "fa fa-info-circle mr-2" ] []
|
||||
, text texts.reallyDeleteChannel
|
||||
]
|
||||
, div [ class "mt-4 flex flex-row items-center" ]
|
||||
[ B.deleteButton
|
||||
{ label = texts.basics.yes
|
||||
, icon = "fa fa-check"
|
||||
, disabled = False
|
||||
, handler = onClick (DeleteChannelNow (Maybe.withDefault "" channelId))
|
||||
, attrs = [ href "#" ]
|
||||
}
|
||||
, B.secondaryButton
|
||||
{ label = texts.basics.no
|
||||
, icon = "fa fa-times"
|
||||
, disabled = False
|
||||
, handler = onClick CancelDelete
|
||||
, attrs = [ href "#", class "ml-2" ]
|
||||
}
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewList : Texts -> Model -> List (Html Msg)
|
||||
viewList texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.newChannelMenuOpen
|
||||
, toggleMenu = ToggleNewChannelMenu
|
||||
, menuLabel = texts.newChannel
|
||||
, onItem = NewChannelInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
, Html.map TableMsg
|
||||
(Comp.NotificationChannelTable.view texts.notificationTable
|
||||
model.listModel
|
||||
model.items
|
||||
)
|
||||
]
|
@ -0,0 +1,87 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Comp.NotificationChannelTable exposing (..)
|
||||
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelType
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.NotificationChannelTable exposing (Texts)
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Action
|
||||
= NoAction
|
||||
| EditAction NotificationChannel
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= Select NotificationChannel
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Action )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
Select channel ->
|
||||
( model, EditAction channel )
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : Texts -> Model -> List NotificationChannel -> Html Msg
|
||||
view texts model channels =
|
||||
table [ class S.tableMain ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [ class "" ] []
|
||||
, th [ class "text-left" ]
|
||||
[ text texts.basics.name
|
||||
]
|
||||
, th [ class "text-left" ]
|
||||
[ text texts.channelType
|
||||
]
|
||||
]
|
||||
]
|
||||
, tbody []
|
||||
(List.map (renderNotificationChannelLine texts model) channels)
|
||||
]
|
||||
|
||||
|
||||
renderNotificationChannelLine : Texts -> Model -> NotificationChannel -> Html Msg
|
||||
renderNotificationChannelLine texts _ channel =
|
||||
let
|
||||
ref =
|
||||
Data.NotificationChannel.getRef channel
|
||||
in
|
||||
tr
|
||||
[ class S.tableRow
|
||||
]
|
||||
[ B.editLinkTableCell texts.basics.edit (Select channel)
|
||||
, td
|
||||
[ class "text-left "
|
||||
, classList [ ( "font-mono", ref.name == Nothing ) ]
|
||||
]
|
||||
[ Maybe.withDefault (String.slice 0 10 ref.id) ref.name |> text
|
||||
]
|
||||
, td [ class "text-left py-4 md:py-2" ]
|
||||
[ text ref.channelType
|
||||
]
|
||||
]
|
@ -17,24 +17,25 @@ import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationGotifyForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationGotify
|
||||
{ channel : NotificationGotify
|
||||
, prioModel : Comp.FixedDropdown.Model Int
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook = Data.NotificationChannel.setTypeGotify Api.Model.NotificationGotify.empty
|
||||
{ channel = Data.NotificationChannel.setTypeGotify Api.Model.NotificationGotify.empty
|
||||
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationGotify -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeGotify hook
|
||||
initWith channel =
|
||||
{ channel = Data.NotificationChannel.setTypeGotify channel
|
||||
, prioModel = Comp.FixedDropdown.init (List.range 0 10)
|
||||
}
|
||||
|
||||
@ -42,6 +43,7 @@ initWith hook =
|
||||
type Msg
|
||||
= SetUrl String
|
||||
| SetAppKey String
|
||||
| SetName String
|
||||
| PrioMsg (Comp.FixedDropdown.Msg Int)
|
||||
|
||||
|
||||
@ -52,30 +54,33 @@ type Msg
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationGotify )
|
||||
update msg model =
|
||||
let
|
||||
hook =
|
||||
model.hook
|
||||
channel =
|
||||
model.channel
|
||||
|
||||
newModel =
|
||||
case msg of
|
||||
SetUrl s ->
|
||||
{ model | hook = { hook | url = s } }
|
||||
{ model | channel = { channel | url = s } }
|
||||
|
||||
SetAppKey s ->
|
||||
{ model | hook = { hook | appKey = s } }
|
||||
{ model | channel = { channel | appKey = s } }
|
||||
|
||||
SetName s ->
|
||||
{ model | channel = { channel | name = Util.Maybe.fromString s } }
|
||||
|
||||
PrioMsg lm ->
|
||||
let
|
||||
( m, sel ) =
|
||||
Comp.FixedDropdown.update lm model.prioModel
|
||||
in
|
||||
{ model | hook = { hook | priority = sel }, prioModel = m }
|
||||
{ model | channel = { channel | priority = sel }, prioModel = m }
|
||||
in
|
||||
( newModel, check newModel.hook )
|
||||
( newModel, check newModel.channel )
|
||||
|
||||
|
||||
check : NotificationGotify -> Maybe NotificationGotify
|
||||
check hook =
|
||||
Just hook
|
||||
check channel =
|
||||
Just channel
|
||||
|
||||
|
||||
|
||||
@ -94,6 +99,25 @@ view texts model =
|
||||
in
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "name"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.basics.name
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetName
|
||||
, placeholder texts.basics.name
|
||||
, value (Maybe.withDefault "" model.channel.name)
|
||||
, name "name"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
@ -107,7 +131,7 @@ view texts model =
|
||||
[ type_ "text"
|
||||
, onInput SetUrl
|
||||
, placeholder texts.gotifyUrl
|
||||
, value model.hook.url
|
||||
, value model.channel.url
|
||||
, name "gotifyurl"
|
||||
, class S.textInput
|
||||
]
|
||||
@ -127,7 +151,7 @@ view texts model =
|
||||
[ type_ "text"
|
||||
, onInput SetAppKey
|
||||
, placeholder texts.appKey
|
||||
, value model.hook.appKey
|
||||
, value model.channel.appKey
|
||||
, name "appkey"
|
||||
, class S.textInput
|
||||
]
|
||||
@ -142,7 +166,7 @@ view texts model =
|
||||
]
|
||||
[ text texts.priority
|
||||
]
|
||||
, Html.map PrioMsg (Comp.FixedDropdown.viewStyled2 cfg False model.hook.priority model.prioModel)
|
||||
, Html.map PrioMsg (Comp.FixedDropdown.viewStyled2 cfg False model.channel.priority model.prioModel)
|
||||
, span [ class "text-sm opacity-75" ]
|
||||
[ text texts.priorityInfo
|
||||
]
|
||||
|
@ -8,7 +8,6 @@
|
||||
module Comp.NotificationHookForm exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, channelType
|
||||
, getHook
|
||||
, init
|
||||
, initWith
|
||||
@ -16,17 +15,16 @@ module Comp.NotificationHookForm exposing
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||
import Comp.Basic as B
|
||||
import Comp.ChannelForm
|
||||
import Comp.ChannelRefInput
|
||||
import Comp.Dropdown
|
||||
import Comp.EventSample
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationTest
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.DropdownStyle as DS
|
||||
import Data.EventType exposing (EventType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -39,7 +37,7 @@ import Util.Maybe
|
||||
type alias Model =
|
||||
{ hook : NotificationHook
|
||||
, enabled : Bool
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, channelModel : Comp.ChannelRefInput.Model
|
||||
, eventsDropdown : Comp.Dropdown.Model EventType
|
||||
, eventSampleModel : Comp.EventSample.Model
|
||||
, testDeliveryModel : Comp.NotificationTest.Model
|
||||
@ -48,16 +46,16 @@ type alias Model =
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
Comp.ChannelRefInput.init flags
|
||||
|
||||
( esm, esc ) =
|
||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||
in
|
||||
( { hook = Data.NotificationHook.empty ct
|
||||
( { hook = Api.Model.NotificationHook.empty
|
||||
, enabled = True
|
||||
, channelModel = cm
|
||||
, eventsDropdown =
|
||||
@ -81,7 +79,7 @@ initWith : Flags -> NotificationHook -> ( Model, Cmd Msg )
|
||||
initWith flags h =
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.initWith flags h.channel
|
||||
Comp.ChannelRefInput.initSelected flags h.channels
|
||||
|
||||
( esm, esc ) =
|
||||
Comp.EventSample.initWith flags Data.EventType.TagsChanged
|
||||
@ -92,7 +90,7 @@ initWith flags h =
|
||||
, eventsDropdown =
|
||||
Comp.Dropdown.makeMultipleList
|
||||
{ options = Data.EventType.all
|
||||
, selected = h.events
|
||||
, selected = List.filterMap Data.EventType.fromString h.events
|
||||
}
|
||||
, eventSampleModel = esm
|
||||
, testDeliveryModel = Comp.NotificationTest.init
|
||||
@ -106,11 +104,6 @@ initWith flags h =
|
||||
)
|
||||
|
||||
|
||||
channelType : Model -> ChannelType
|
||||
channelType model =
|
||||
Comp.ChannelForm.channelType model.channelModel
|
||||
|
||||
|
||||
getHook : Model -> Maybe NotificationHook
|
||||
getHook model =
|
||||
let
|
||||
@ -123,20 +116,28 @@ getHook model =
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just ev
|
||||
Just (List.map Data.EventType.asString ev)
|
||||
|
||||
channel =
|
||||
Comp.ChannelForm.getChannel model.channelModel
|
||||
channels =
|
||||
let
|
||||
list =
|
||||
Comp.ChannelRefInput.getSelected model.channelModel
|
||||
in
|
||||
if list == [] then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just list
|
||||
|
||||
mkHook ev ch =
|
||||
NotificationHook model.hook.id model.enabled ch model.allEvents model.eventFilter ev
|
||||
in
|
||||
Maybe.map2 mkHook events channel
|
||||
Maybe.map2 mkHook events channels
|
||||
|
||||
|
||||
type Msg
|
||||
= ToggleEnabled
|
||||
| ChannelFormMsg Comp.ChannelForm.Msg
|
||||
| ChannelFormMsg Comp.ChannelRefInput.Msg
|
||||
| EventMsg (Comp.Dropdown.Msg EventType)
|
||||
| EventSampleMsg Comp.EventSample.Msg
|
||||
| DeliveryTestMsg Comp.NotificationTest.Msg
|
||||
@ -163,7 +164,7 @@ update flags msg model =
|
||||
ChannelFormMsg lm ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
Comp.ChannelRefInput.update lm model.channelModel
|
||||
in
|
||||
( { model | channelModel = cm }, Cmd.map ChannelFormMsg cc )
|
||||
|
||||
@ -229,9 +230,9 @@ view texts settings model =
|
||||
}
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel))
|
||||
[ formHeader texts.channelHeader
|
||||
, Html.map ChannelFormMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.events
|
||||
@ -290,21 +291,21 @@ view texts settings model =
|
||||
]
|
||||
, div
|
||||
[ class "mt-4"
|
||||
, classList [ ( "hidden", channelType model /= Data.ChannelType.Http ) ]
|
||||
]
|
||||
[ h3 [ class S.header3 ]
|
||||
[ text texts.samplePayload
|
||||
]
|
||||
, Html.map EventSampleMsg
|
||||
(Comp.EventSample.viewJson texts.eventSample model.eventSampleModel)
|
||||
]
|
||||
, div
|
||||
[ class "mt-4"
|
||||
, classList [ ( "hidden", channelType model == Data.ChannelType.Http ) ]
|
||||
]
|
||||
[ formHeader texts.samplePayload
|
||||
, div [ class "opacity-80 mb-1" ]
|
||||
[ text texts.payloadInfo
|
||||
]
|
||||
, Html.map EventSampleMsg
|
||||
(Comp.EventSample.viewMessage texts.eventSample model.eventSampleModel)
|
||||
(Comp.EventSample.viewMessage texts.eventSample True model.eventSampleModel)
|
||||
, div [ class "py-2 text-center text-sm" ]
|
||||
[ text texts.jsonPayload
|
||||
, i [ class "fa fa-arrow-down ml-1 mr-3" ] []
|
||||
, i [ class "fa fa-arrow-up mr-1" ] []
|
||||
, text texts.messagePayload
|
||||
]
|
||||
, Html.map EventSampleMsg
|
||||
(Comp.EventSample.viewJson texts.eventSample False model.eventSampleModel)
|
||||
]
|
||||
, div [ class "mt-4" ]
|
||||
[ formHeader "Test Delviery"
|
||||
|
@ -15,14 +15,12 @@ module Comp.NotificationHookManage exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||
import Comp.Basic as B
|
||||
import Comp.ChannelMenu
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.NotificationHookForm
|
||||
import Comp.NotificationHookTable
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -39,7 +37,6 @@ type alias Model =
|
||||
, deleteConfirm : DeleteConfirm
|
||||
, loading : Bool
|
||||
, formState : FormState
|
||||
, newHookMenuOpen : Bool
|
||||
, jsonFilterError : Maybe String
|
||||
}
|
||||
|
||||
@ -67,9 +64,8 @@ type Msg
|
||||
= TableMsg Comp.NotificationHookTable.Msg
|
||||
| DetailMsg Comp.NotificationHookForm.Msg
|
||||
| GetDataResp (Result Http.Error (List NotificationHook))
|
||||
| ToggleNewHookMenu
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| NewHookInit ChannelType
|
||||
| NewHookInit
|
||||
| BackToTable
|
||||
| Submit
|
||||
| RequestDelete
|
||||
@ -85,7 +81,6 @@ initModel =
|
||||
, items = []
|
||||
, loading = False
|
||||
, formState = FormStateInitial
|
||||
, newHookMenuOpen = False
|
||||
, deleteConfirm = DeleteConfirmOff
|
||||
, jsonFilterError = Nothing
|
||||
}
|
||||
@ -177,9 +172,6 @@ update flags msg model =
|
||||
Nothing ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ToggleNewHookMenu ->
|
||||
( { model | newHookMenuOpen = not model.newHookMenuOpen }, Cmd.none )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
| formState =
|
||||
@ -208,12 +200,12 @@ update flags msg model =
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
NewHookInit ct ->
|
||||
NewHookInit ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.NotificationHookForm.init flags ct
|
||||
Comp.NotificationHookForm.init flags
|
||||
in
|
||||
( { model | detailModel = Just mm, newHookMenuOpen = False }, Cmd.map DetailMsg mc )
|
||||
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc )
|
||||
|
||||
BackToTable ->
|
||||
( { model | detailModel = Nothing }, initCmd flags )
|
||||
@ -327,60 +319,14 @@ viewForm texts settings outerModel model =
|
||||
let
|
||||
newHook =
|
||||
model.hook.id == ""
|
||||
|
||||
headline =
|
||||
case Comp.NotificationHookForm.channelType model of
|
||||
Data.ChannelType.Matrix ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://matrix.org"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Matrix"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Mail ->
|
||||
span []
|
||||
[ text texts.notifyEmailInfo
|
||||
]
|
||||
|
||||
Data.ChannelType.Gotify ->
|
||||
span []
|
||||
[ text texts.integrate
|
||||
, a
|
||||
[ href "https://gotify.net"
|
||||
, target "_blank"
|
||||
, class S.link
|
||||
, class "mx-3"
|
||||
]
|
||||
[ i [ class "fa fa-external-link-alt mr-1" ] []
|
||||
, text "Gotify"
|
||||
]
|
||||
, text texts.intoDocspell
|
||||
]
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
span []
|
||||
[ text texts.postRequestInfo
|
||||
]
|
||||
in
|
||||
[ h1 [ class S.header2 ]
|
||||
[ Data.ChannelType.icon (Comp.NotificationHookForm.channelType model) "w-8 h-8 inline-block mr-4"
|
||||
, if newHook then
|
||||
[ if newHook then
|
||||
text texts.addWebhook
|
||||
|
||||
else
|
||||
text texts.updateWebhook
|
||||
]
|
||||
, div [ class "pt-2 pb-4 font-medium" ]
|
||||
[ headline
|
||||
]
|
||||
, MB.view
|
||||
{ start =
|
||||
[ MB.CustomElement <|
|
||||
@ -452,18 +398,15 @@ viewForm texts settings outerModel model =
|
||||
|
||||
viewList : Texts -> Model -> List (Html Msg)
|
||||
viewList texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.newHookMenuOpen
|
||||
, toggleMenu = ToggleNewHookMenu
|
||||
, menuLabel = texts.newHook
|
||||
, onItem = NewHookInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = NewHookInit
|
||||
, title = texts.newHook
|
||||
, icon = Just "fa fa-plus"
|
||||
, label = texts.newHook
|
||||
}
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
|
@ -14,15 +14,13 @@ module Comp.NotificationHookTable exposing
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelType
|
||||
import Data.ChannelRef
|
||||
import Data.EventType
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationChannel
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Messages.Comp.NotificationHookTable exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
@ -80,7 +78,7 @@ view texts model hooks =
|
||||
|
||||
|
||||
renderNotificationHookLine : Texts -> Model -> NotificationHook -> Html Msg
|
||||
renderNotificationHookLine texts model hook =
|
||||
renderNotificationHookLine texts _ hook =
|
||||
let
|
||||
eventName =
|
||||
texts.eventType >> .name
|
||||
@ -93,14 +91,17 @@ renderNotificationHookLine texts model hook =
|
||||
[ Util.Html.checkbox2 hook.enabled
|
||||
]
|
||||
, td [ class "text-left py-4 md:py-2" ]
|
||||
[ Data.NotificationChannel.channelType hook.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
[ div [ class "space-x-1" ]
|
||||
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] hook.channels)
|
||||
]
|
||||
, td [ class "text-left hidden sm:table-cell" ]
|
||||
[ List.map eventName hook.events
|
||||
|> String.join ", "
|
||||
|> text
|
||||
[ if hook.allEvents then
|
||||
text texts.allEvents
|
||||
|
||||
else
|
||||
List.filterMap Data.EventType.fromString hook.events
|
||||
|> List.map eventName
|
||||
|> String.join ", "
|
||||
|> text
|
||||
]
|
||||
]
|
||||
|
@ -15,29 +15,31 @@ import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationHttpForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationHttp
|
||||
{ channel : NotificationHttp
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook =
|
||||
{ channel =
|
||||
Data.NotificationChannel.setTypeHttp
|
||||
Api.Model.NotificationHttp.empty
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationHttp -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeHttp hook
|
||||
initWith channel =
|
||||
{ channel = Data.NotificationChannel.setTypeHttp channel
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SetUrl String
|
||||
| SetName String
|
||||
|
||||
|
||||
|
||||
@ -47,26 +49,29 @@ type Msg
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationHttp )
|
||||
update msg model =
|
||||
let
|
||||
newHook =
|
||||
updateHook msg model.hook
|
||||
newChannel =
|
||||
updateChannel msg model.channel
|
||||
in
|
||||
( { model | hook = newHook }, check newHook )
|
||||
( { model | channel = newChannel }, check newChannel )
|
||||
|
||||
|
||||
check : NotificationHttp -> Maybe NotificationHttp
|
||||
check hook =
|
||||
if hook.url == "" then
|
||||
check channel =
|
||||
if channel.url == "" then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just hook
|
||||
Just channel
|
||||
|
||||
|
||||
updateHook : Msg -> NotificationHttp -> NotificationHttp
|
||||
updateHook msg hook =
|
||||
updateChannel : Msg -> NotificationHttp -> NotificationHttp
|
||||
updateChannel msg channel =
|
||||
case msg of
|
||||
SetUrl s ->
|
||||
{ hook | url = s }
|
||||
{ channel | url = s }
|
||||
|
||||
SetName s ->
|
||||
{ channel | name = Util.Maybe.fromString s }
|
||||
|
||||
|
||||
|
||||
@ -77,6 +82,25 @@ view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "name"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.basics.name
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetName
|
||||
, placeholder texts.basics.name
|
||||
, value (Maybe.withDefault "" model.channel.name)
|
||||
, name "name"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
@ -90,7 +114,7 @@ view texts model =
|
||||
[ type_ "text"
|
||||
, onInput SetUrl
|
||||
, placeholder texts.httpUrl
|
||||
, value model.hook.url
|
||||
, value model.channel.url
|
||||
, name "httpurl"
|
||||
, class S.textInput
|
||||
]
|
||||
|
@ -23,13 +23,15 @@ import Html.Events exposing (onInput)
|
||||
import Http
|
||||
import Messages.Comp.NotificationMailForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationMail
|
||||
{ channel : NotificationMail
|
||||
, connectionModel : Comp.Dropdown.Model String
|
||||
, recipients : List String
|
||||
, recipientsModel : Comp.EmailInput.Model
|
||||
, name : Maybe String
|
||||
, formState : FormState
|
||||
}
|
||||
|
||||
@ -46,10 +48,11 @@ type ValidateError
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
( { hook = Data.NotificationChannel.setTypeMail Api.Model.NotificationMail.empty
|
||||
( { channel = Data.NotificationChannel.setTypeMail Api.Model.NotificationMail.empty
|
||||
, connectionModel = Comp.Dropdown.makeSingle
|
||||
, recipients = []
|
||||
, recipientsModel = Comp.EmailInput.init
|
||||
, name = Nothing
|
||||
, formState = FormStateInitial
|
||||
}
|
||||
, Cmd.batch
|
||||
@ -59,17 +62,17 @@ init flags =
|
||||
|
||||
|
||||
initWith : Flags -> NotificationMail -> ( Model, Cmd Msg )
|
||||
initWith flags hook =
|
||||
initWith flags channel =
|
||||
let
|
||||
( mm, mc ) =
|
||||
init flags
|
||||
|
||||
( cm, _ ) =
|
||||
Comp.Dropdown.update (Comp.Dropdown.SetSelection [ hook.connection ]) mm.connectionModel
|
||||
Comp.Dropdown.update (Comp.Dropdown.SetSelection [ channel.connection ]) mm.connectionModel
|
||||
in
|
||||
( { mm
|
||||
| hook = Data.NotificationChannel.setTypeMail hook
|
||||
, recipients = hook.recipients
|
||||
| channel = Data.NotificationChannel.setTypeMail channel
|
||||
, recipients = channel.recipients
|
||||
, connectionModel = cm
|
||||
}
|
||||
, mc
|
||||
@ -80,6 +83,7 @@ type Msg
|
||||
= ConnResp (Result Http.Error EmailSettingsList)
|
||||
| ConnMsg (Comp.Dropdown.Msg String)
|
||||
| RecipientMsg Comp.EmailInput.Msg
|
||||
| SetName String
|
||||
|
||||
|
||||
|
||||
@ -108,12 +112,12 @@ check model =
|
||||
|> List.head
|
||||
|
||||
h =
|
||||
model.hook
|
||||
model.channel
|
||||
|
||||
makeHook _ rec conn =
|
||||
{ h | connection = conn, recipients = rec }
|
||||
makeChannel _ rec conn =
|
||||
{ h | connection = conn, recipients = rec, name = model.name }
|
||||
in
|
||||
Maybe.map3 makeHook formState recipients connection
|
||||
Maybe.map3 makeChannel formState recipients connection
|
||||
|
||||
|
||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe NotificationMail )
|
||||
@ -152,6 +156,16 @@ update flags msg model =
|
||||
, Nothing
|
||||
)
|
||||
|
||||
SetName s ->
|
||||
let
|
||||
model_ =
|
||||
{ model | name = Util.Maybe.fromString s }
|
||||
in
|
||||
( model_
|
||||
, Cmd.none
|
||||
, check model_
|
||||
)
|
||||
|
||||
ConnMsg lm ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
@ -201,7 +215,26 @@ view texts settings model =
|
||||
}
|
||||
in
|
||||
div []
|
||||
[ div [ class "mb-4" ]
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "name"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.basics.name
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetName
|
||||
, placeholder texts.basics.name
|
||||
, value (Maybe.withDefault "" model.name)
|
||||
, name "name"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ label [ class S.inputLabel ]
|
||||
[ text texts.sendVia
|
||||
, B.inputRequired
|
||||
|
@ -15,22 +15,23 @@ import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onInput)
|
||||
import Messages.Comp.NotificationMatrixForm exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ hook : NotificationMatrix
|
||||
{ channel : NotificationMatrix
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ hook = Data.NotificationChannel.setTypeMatrix Api.Model.NotificationMatrix.empty
|
||||
{ channel = Data.NotificationChannel.setTypeMatrix Api.Model.NotificationMatrix.empty
|
||||
}
|
||||
|
||||
|
||||
initWith : NotificationMatrix -> Model
|
||||
initWith hook =
|
||||
{ hook = Data.NotificationChannel.setTypeMatrix hook
|
||||
initWith channel =
|
||||
{ channel = Data.NotificationChannel.setTypeMatrix channel
|
||||
}
|
||||
|
||||
|
||||
@ -38,6 +39,7 @@ type Msg
|
||||
= SetHomeServer String
|
||||
| SetRoomId String
|
||||
| SetAccessKey String
|
||||
| SetName String
|
||||
|
||||
|
||||
|
||||
@ -47,28 +49,31 @@ type Msg
|
||||
update : Msg -> Model -> ( Model, Maybe NotificationMatrix )
|
||||
update msg model =
|
||||
let
|
||||
newHook =
|
||||
updateHook msg model.hook
|
||||
newChannel =
|
||||
updateChannel msg model.channel
|
||||
in
|
||||
( { model | hook = newHook }, check newHook )
|
||||
( { model | channel = newChannel }, check newChannel )
|
||||
|
||||
|
||||
check : NotificationMatrix -> Maybe NotificationMatrix
|
||||
check hook =
|
||||
Just hook
|
||||
check channel =
|
||||
Just channel
|
||||
|
||||
|
||||
updateHook : Msg -> NotificationMatrix -> NotificationMatrix
|
||||
updateHook msg hook =
|
||||
updateChannel : Msg -> NotificationMatrix -> NotificationMatrix
|
||||
updateChannel msg channel =
|
||||
case msg of
|
||||
SetHomeServer s ->
|
||||
{ hook | homeServer = s }
|
||||
{ channel | homeServer = s }
|
||||
|
||||
SetRoomId s ->
|
||||
{ hook | roomId = s }
|
||||
{ channel | roomId = s }
|
||||
|
||||
SetAccessKey s ->
|
||||
{ hook | accessToken = s }
|
||||
{ channel | accessToken = s }
|
||||
|
||||
SetName s ->
|
||||
{ channel | name = Util.Maybe.fromString s }
|
||||
|
||||
|
||||
|
||||
@ -79,6 +84,25 @@ view : Texts -> Model -> Html Msg
|
||||
view texts model =
|
||||
div []
|
||||
[ div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
[ for "name"
|
||||
, class S.inputLabel
|
||||
]
|
||||
[ text texts.basics.name
|
||||
]
|
||||
, input
|
||||
[ type_ "text"
|
||||
, onInput SetName
|
||||
, placeholder texts.basics.name
|
||||
, value (Maybe.withDefault "" model.channel.name)
|
||||
, name "name"
|
||||
, class S.textInput
|
||||
]
|
||||
[]
|
||||
]
|
||||
, div
|
||||
[ class "mb-2"
|
||||
]
|
||||
[ label
|
||||
@ -92,7 +116,7 @@ view texts model =
|
||||
[ type_ "text"
|
||||
, onInput SetHomeServer
|
||||
, placeholder texts.homeServer
|
||||
, value model.hook.homeServer
|
||||
, value model.channel.homeServer
|
||||
, name "homeserver"
|
||||
, class S.textInput
|
||||
]
|
||||
@ -112,7 +136,7 @@ view texts model =
|
||||
[ type_ "text"
|
||||
, onInput SetRoomId
|
||||
, placeholder texts.roomId
|
||||
, value model.hook.roomId
|
||||
, value model.channel.roomId
|
||||
, name "roomid"
|
||||
, class S.textInput
|
||||
]
|
||||
@ -131,7 +155,7 @@ view texts model =
|
||||
, textarea
|
||||
[ onInput SetAccessKey
|
||||
, placeholder texts.accessKey
|
||||
, value model.hook.accessToken
|
||||
, value model.channel.accessToken
|
||||
, name "accesskey"
|
||||
, class S.textAreaInput
|
||||
]
|
||||
|
@ -9,10 +9,10 @@ module Comp.NotificationTest exposing (Model, Msg, ViewConfig, init, update, vie
|
||||
|
||||
import Api
|
||||
import Api.Model.NotificationChannelTestResult exposing (NotificationChannelTestResult)
|
||||
import Api.Model.NotificationHook exposing (NotificationHook)
|
||||
import Comp.Basic as B
|
||||
import Comp.MenuBar as MB
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.NotificationHook exposing (NotificationHook)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
|
@ -16,16 +16,15 @@ module Comp.PeriodicQueryTaskForm exposing
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Comp.Basic as B
|
||||
import Comp.BookmarkDropdown
|
||||
import Comp.CalEventInput
|
||||
import Comp.ChannelForm
|
||||
import Comp.ChannelRefInput
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.PowerSearchInput
|
||||
import Data.CalEvent exposing (CalEvent)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Data.Validated exposing (Validated(..))
|
||||
import Html exposing (..)
|
||||
@ -44,7 +43,7 @@ type alias Model =
|
||||
, schedule : Maybe CalEvent
|
||||
, scheduleModel : Comp.CalEventInput.Model
|
||||
, queryModel : Comp.PowerSearchInput.Model
|
||||
, channelModel : Comp.ChannelForm.Model
|
||||
, channelModel : Comp.ChannelRefInput.Model
|
||||
, bookmarkDropdown : Comp.BookmarkDropdown.Model
|
||||
, contentStart : Maybe String
|
||||
, formState : FormState
|
||||
@ -78,7 +77,7 @@ type Msg
|
||||
| ToggleEnabled
|
||||
| CalEventMsg Comp.CalEventInput.Msg
|
||||
| QueryMsg Comp.PowerSearchInput.Msg
|
||||
| ChannelMsg Comp.ChannelForm.Msg
|
||||
| ChannelMsg Comp.ChannelRefInput.Msg
|
||||
| BookmarkMsg Comp.BookmarkDropdown.Msg
|
||||
| SetContentStart String
|
||||
| StartOnce
|
||||
@ -105,7 +104,7 @@ initWith flags s =
|
||||
Comp.PowerSearchInput.init
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.initWith flags s.channel
|
||||
Comp.ChannelRefInput.initSelected flags s.channels
|
||||
|
||||
( bm, bc ) =
|
||||
Comp.BookmarkDropdown.init flags s.bookmark
|
||||
@ -132,8 +131,8 @@ initWith flags s =
|
||||
)
|
||||
|
||||
|
||||
init : Flags -> ChannelType -> ( Model, Cmd Msg )
|
||||
init flags ct =
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init flags =
|
||||
let
|
||||
initialSchedule =
|
||||
Data.CalEvent.everyMonth
|
||||
@ -142,12 +141,12 @@ init flags ct =
|
||||
Comp.CalEventInput.init flags initialSchedule
|
||||
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.init flags ct
|
||||
Comp.ChannelRefInput.init flags
|
||||
|
||||
( bm, bc ) =
|
||||
Comp.BookmarkDropdown.init flags Nothing
|
||||
in
|
||||
( { settings = Data.PeriodicQuerySettings.empty ct
|
||||
( { settings = Api.Model.PeriodicQuerySettings.empty
|
||||
, enabled = False
|
||||
, schedule = Just initialSchedule
|
||||
, scheduleModel = sm
|
||||
@ -210,16 +209,22 @@ makeSettings model =
|
||||
Result.Ok ( qstr, bm )
|
||||
|
||||
channelM =
|
||||
Result.fromMaybe
|
||||
ValidateChannelRequired
|
||||
(Comp.ChannelForm.getChannel model.channelModel)
|
||||
let
|
||||
list =
|
||||
Comp.ChannelRefInput.getSelected model.channelModel
|
||||
in
|
||||
if list == [] then
|
||||
Err ValidateChannelRequired
|
||||
|
||||
make timer channel q =
|
||||
else
|
||||
Ok list
|
||||
|
||||
make timer channels q =
|
||||
{ prev
|
||||
| enabled = model.enabled
|
||||
, schedule = Data.CalEvent.makeEvent timer
|
||||
, summary = model.summary
|
||||
, channel = channel
|
||||
, channels = channels
|
||||
, query = Tuple.first q
|
||||
, bookmark = Tuple.second q
|
||||
, contentStart = model.contentStart
|
||||
@ -285,7 +290,7 @@ update flags msg model =
|
||||
ChannelMsg lm ->
|
||||
let
|
||||
( cfm, cfc ) =
|
||||
Comp.ChannelForm.update flags lm model.channelModel
|
||||
Comp.ChannelRefInput.update lm model.channelModel
|
||||
in
|
||||
{ model = { model | channelModel = cfm }
|
||||
, action = NoAction
|
||||
@ -536,9 +541,9 @@ view texts extraClasses settings model =
|
||||
]
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader (texts.channelHeader (Comp.ChannelForm.channelType model.channelModel)) False
|
||||
[ formHeader texts.channelHeader True
|
||||
, Html.map ChannelMsg
|
||||
(Comp.ChannelForm.view texts.channelForm settings model.channelModel)
|
||||
(Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
|
||||
]
|
||||
, div [ class "mb-4" ]
|
||||
[ formHeader texts.queryLabel True
|
||||
|
@ -14,15 +14,17 @@ module Comp.PeriodicQueryTaskList exposing
|
||||
, view2
|
||||
)
|
||||
|
||||
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Comp.Basic as B
|
||||
import Data.ChannelRef
|
||||
import Data.ChannelType
|
||||
import Data.NotificationChannel
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Messages.Comp.PeriodicQueryTaskList exposing (Texts)
|
||||
import Styles as S
|
||||
import Util.Html
|
||||
import Util.List
|
||||
|
||||
|
||||
type alias Model =
|
||||
@ -94,9 +96,7 @@ viewItem2 texts item =
|
||||
]
|
||||
]
|
||||
, td [ class "text-left py-4 md:py-2" ]
|
||||
[ Data.NotificationChannel.channelType item.channel
|
||||
|> Maybe.map Data.ChannelType.asString
|
||||
|> Maybe.withDefault "-"
|
||||
|> text
|
||||
[ div [ class " space-x-1" ]
|
||||
(Data.ChannelRef.asDivs texts.channelType [ class "inline" ] item.channels)
|
||||
]
|
||||
]
|
||||
|
@ -15,17 +15,16 @@ module Comp.PeriodicQueryTaskManage exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Comp.ChannelMenu
|
||||
import Comp.MenuBar as MB
|
||||
import Comp.PeriodicQueryTaskForm
|
||||
import Comp.PeriodicQueryTaskList
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.PeriodicQuerySettings exposing (PeriodicQuerySettings)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
import Http
|
||||
import Messages.Comp.PeriodicQueryTaskManage exposing (Texts)
|
||||
import Styles as S
|
||||
@ -36,7 +35,6 @@ type alias Model =
|
||||
, detailModel : Maybe Comp.PeriodicQueryTaskForm.Model
|
||||
, items : List PeriodicQuerySettings
|
||||
, formState : FormState
|
||||
, channelMenuOpen : Bool
|
||||
}
|
||||
|
||||
|
||||
@ -58,9 +56,8 @@ type Msg
|
||||
= ListMsg Comp.PeriodicQueryTaskList.Msg
|
||||
| DetailMsg Comp.PeriodicQueryTaskForm.Msg
|
||||
| GetDataResp (Result Http.Error (List PeriodicQuerySettings))
|
||||
| NewTaskInit ChannelType
|
||||
| NewTaskInit
|
||||
| SubmitResp SubmitType (Result Http.Error BasicResult)
|
||||
| ToggleChannelMenu
|
||||
|
||||
|
||||
initModel : Model
|
||||
@ -69,7 +66,6 @@ initModel =
|
||||
, detailModel = Nothing
|
||||
, items = []
|
||||
, formState = FormStateInitial
|
||||
, channelMenuOpen = False
|
||||
}
|
||||
|
||||
|
||||
@ -195,12 +191,12 @@ update flags msg model =
|
||||
Nothing ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
NewTaskInit ct ->
|
||||
NewTaskInit ->
|
||||
let
|
||||
( mm, mc ) =
|
||||
Comp.PeriodicQueryTaskForm.init flags ct
|
||||
Comp.PeriodicQueryTaskForm.init flags
|
||||
in
|
||||
( { model | detailModel = Just mm, channelMenuOpen = False }, Cmd.map DetailMsg mc, Sub.none )
|
||||
( { model | detailModel = Just mm }, Cmd.map DetailMsg mc, Sub.none )
|
||||
|
||||
SubmitResp submitType (Ok res) ->
|
||||
( { model
|
||||
@ -231,9 +227,6 @@ update flags msg model =
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
ToggleChannelMenu ->
|
||||
( { model | channelMenuOpen = not model.channelMenuOpen }, Cmd.none, Sub.none )
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
@ -301,18 +294,15 @@ viewForm2 texts settings model =
|
||||
|
||||
viewList2 : Texts -> Model -> List (Html Msg)
|
||||
viewList2 texts model =
|
||||
let
|
||||
menuModel =
|
||||
{ menuOpen = model.channelMenuOpen
|
||||
, toggleMenu = ToggleChannelMenu
|
||||
, menuLabel = texts.newTask
|
||||
, onItem = NewTaskInit
|
||||
}
|
||||
in
|
||||
[ MB.view
|
||||
{ start = []
|
||||
, end =
|
||||
[ Comp.ChannelMenu.channelMenu texts.channelType menuModel
|
||||
[ MB.PrimaryButton
|
||||
{ tagger = NewTaskInit
|
||||
, title = texts.newTask
|
||||
, icon = Just "fa fa-plus"
|
||||
, label = texts.newTask
|
||||
}
|
||||
]
|
||||
, rootClasses = "mb-4"
|
||||
}
|
||||
|
@ -7,27 +7,63 @@
|
||||
|
||||
module Data.ChannelRef exposing (..)
|
||||
|
||||
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
import Html exposing (Attribute, Html, div, span, text)
|
||||
import Html.Attributes exposing (class)
|
||||
import Messages.Data.ChannelType as M
|
||||
import Util.List
|
||||
|
||||
|
||||
type alias ChannelRef =
|
||||
{ id : String
|
||||
, channelType : ChannelType
|
||||
}
|
||||
channelType : NotificationChannelRef -> Maybe ChannelType
|
||||
channelType ref =
|
||||
Data.ChannelType.fromString ref.channelType
|
||||
|
||||
|
||||
decoder : D.Decoder ChannelRef
|
||||
decoder =
|
||||
D.map2 ChannelRef
|
||||
(D.field "id" D.string)
|
||||
(D.field "channelType" Data.ChannelType.decoder)
|
||||
split : M.Texts -> NotificationChannelRef -> ( String, String )
|
||||
split texts ref =
|
||||
let
|
||||
chStr =
|
||||
channelType ref
|
||||
|> Maybe.map texts
|
||||
|> Maybe.withDefault ref.channelType
|
||||
|
||||
name =
|
||||
Maybe.withDefault (String.slice 0 6 ref.id) ref.name
|
||||
in
|
||||
( chStr, name )
|
||||
|
||||
|
||||
encode : ChannelRef -> E.Value
|
||||
encode cref =
|
||||
E.object
|
||||
[ ( "id", E.string cref.id )
|
||||
, ( "channelType", Data.ChannelType.encode cref.channelType )
|
||||
asString : M.Texts -> NotificationChannelRef -> String
|
||||
asString texts ref =
|
||||
let
|
||||
( chStr, name ) =
|
||||
split texts ref
|
||||
in
|
||||
chStr ++ " (" ++ name ++ ")"
|
||||
|
||||
|
||||
asDiv : List (Attribute msg) -> M.Texts -> NotificationChannelRef -> Html msg
|
||||
asDiv attrs texts ref =
|
||||
let
|
||||
( chStr, name ) =
|
||||
split texts ref
|
||||
in
|
||||
div attrs
|
||||
[ text chStr
|
||||
, span [ class "ml-1 text-xs opacity-75" ]
|
||||
[ text ("(" ++ name ++ ")")
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
asStringJoined : M.Texts -> List NotificationChannelRef -> String
|
||||
asStringJoined texts refs =
|
||||
List.map (asString texts) refs
|
||||
|> Util.List.distinct
|
||||
|> String.join ", "
|
||||
|
||||
|
||||
asDivs : M.Texts -> List (Attribute msg) -> List NotificationChannelRef -> List (Html msg)
|
||||
asDivs texts inner refs =
|
||||
List.map (asDiv inner texts) refs
|
||||
|
@ -12,17 +12,18 @@ module Data.NotificationChannel exposing
|
||||
, decoder
|
||||
, empty
|
||||
, encode
|
||||
, getRef
|
||||
, setTypeGotify
|
||||
, setTypeHttp
|
||||
, setTypeMail
|
||||
, setTypeMatrix
|
||||
)
|
||||
|
||||
import Api.Model.NotificationChannelRef exposing (NotificationChannelRef)
|
||||
import Api.Model.NotificationGotify exposing (NotificationGotify)
|
||||
import Api.Model.NotificationHttp exposing (NotificationHttp)
|
||||
import Api.Model.NotificationMail exposing (NotificationMail)
|
||||
import Api.Model.NotificationMatrix exposing (NotificationMatrix)
|
||||
import Data.ChannelRef exposing (ChannelRef)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
@ -33,7 +34,6 @@ type NotificationChannel
|
||||
| Mail NotificationMail
|
||||
| Gotify NotificationGotify
|
||||
| Http NotificationHttp
|
||||
| Ref ChannelRef
|
||||
|
||||
|
||||
empty : ChannelType -> NotificationChannel
|
||||
@ -87,46 +87,49 @@ decoder =
|
||||
, D.map Mail Api.Model.NotificationMail.decoder
|
||||
, D.map Matrix Api.Model.NotificationMatrix.decoder
|
||||
, D.map Http Api.Model.NotificationHttp.decoder
|
||||
, D.map Ref Data.ChannelRef.decoder
|
||||
]
|
||||
|
||||
|
||||
fold :
|
||||
(NotificationMail -> a)
|
||||
-> (NotificationGotify -> a)
|
||||
-> (NotificationMatrix -> a)
|
||||
-> (NotificationHttp -> a)
|
||||
-> NotificationChannel
|
||||
-> a
|
||||
fold fa fb fc fd channel =
|
||||
case channel of
|
||||
Mail ch ->
|
||||
fa ch
|
||||
|
||||
Gotify ch ->
|
||||
fb ch
|
||||
|
||||
Matrix ch ->
|
||||
fc ch
|
||||
|
||||
Http ch ->
|
||||
fd ch
|
||||
|
||||
|
||||
encode : NotificationChannel -> E.Value
|
||||
encode channel =
|
||||
case channel of
|
||||
Matrix ch ->
|
||||
Api.Model.NotificationMatrix.encode ch
|
||||
|
||||
Mail ch ->
|
||||
Api.Model.NotificationMail.encode ch
|
||||
|
||||
Gotify ch ->
|
||||
Api.Model.NotificationGotify.encode ch
|
||||
|
||||
Http ch ->
|
||||
Api.Model.NotificationHttp.encode ch
|
||||
|
||||
Ref ch ->
|
||||
Data.ChannelRef.encode ch
|
||||
fold
|
||||
Api.Model.NotificationMail.encode
|
||||
Api.Model.NotificationGotify.encode
|
||||
Api.Model.NotificationMatrix.encode
|
||||
Api.Model.NotificationHttp.encode
|
||||
channel
|
||||
|
||||
|
||||
channelType : NotificationChannel -> Maybe ChannelType
|
||||
channelType ch =
|
||||
case ch of
|
||||
Matrix m ->
|
||||
Data.ChannelType.fromString m.channelType
|
||||
|
||||
Mail m ->
|
||||
Data.ChannelType.fromString m.channelType
|
||||
|
||||
Gotify m ->
|
||||
Data.ChannelType.fromString m.channelType
|
||||
|
||||
Http m ->
|
||||
Data.ChannelType.fromString m.channelType
|
||||
|
||||
Ref m ->
|
||||
Just m.channelType
|
||||
fold
|
||||
(.channelType >> Data.ChannelType.fromString)
|
||||
(.channelType >> Data.ChannelType.fromString)
|
||||
(.channelType >> Data.ChannelType.fromString)
|
||||
(.channelType >> Data.ChannelType.fromString)
|
||||
ch
|
||||
|
||||
|
||||
asString : NotificationChannel -> String
|
||||
@ -144,5 +147,12 @@ asString channel =
|
||||
Http ch ->
|
||||
"Http @ " ++ ch.url
|
||||
|
||||
Ref ch ->
|
||||
"Ref(" ++ Data.ChannelType.asString ch.channelType ++ "/" ++ ch.id ++ ")"
|
||||
|
||||
getRef : NotificationChannel -> NotificationChannelRef
|
||||
getRef channel =
|
||||
fold
|
||||
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||
(\c -> NotificationChannelRef c.id c.channelType c.name)
|
||||
channel
|
||||
|
@ -1,62 +0,0 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Data.NotificationHook exposing (NotificationHook, decoder, empty, encode)
|
||||
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.EventType exposing (EventType)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type alias NotificationHook =
|
||||
{ id : String
|
||||
, enabled : Bool
|
||||
, channel : NotificationChannel
|
||||
, allEvents : Bool
|
||||
, eventFilter : Maybe String
|
||||
, events : List EventType
|
||||
}
|
||||
|
||||
|
||||
empty : ChannelType -> NotificationHook
|
||||
empty ct =
|
||||
{ id = ""
|
||||
, enabled = True
|
||||
, channel = Data.NotificationChannel.empty ct
|
||||
, allEvents = False
|
||||
, eventFilter = Nothing
|
||||
, events = []
|
||||
}
|
||||
|
||||
|
||||
decoder : D.Decoder NotificationHook
|
||||
decoder =
|
||||
D.map6 NotificationHook
|
||||
(D.field "id" D.string)
|
||||
(D.field "enabled" D.bool)
|
||||
(D.field "channel" Data.NotificationChannel.decoder)
|
||||
(D.field "allEvents" D.bool)
|
||||
(D.field "eventFilter" (D.maybe D.string))
|
||||
(D.field "events" (D.list Data.EventType.decoder))
|
||||
|
||||
|
||||
encode : NotificationHook -> E.Value
|
||||
encode hook =
|
||||
E.object
|
||||
[ ( "id", E.string hook.id )
|
||||
, ( "enabled", E.bool hook.enabled )
|
||||
, ( "channel", Data.NotificationChannel.encode hook.channel )
|
||||
, ( "allEvents", E.bool hook.allEvents )
|
||||
, ( "eventFilter", Maybe.map E.string hook.eventFilter |> Maybe.withDefault E.null )
|
||||
, ( "events", E.list Data.EventType.encode hook.events )
|
||||
]
|
||||
|
||||
|
||||
|
||||
--- private
|
@ -1,77 +0,0 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Data.PeriodicDueItemsSettings exposing (..)
|
||||
|
||||
import Api.Model.Tag exposing (Tag)
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Json.Decode as Decode
|
||||
import Json.Decode.Pipeline as P
|
||||
import Json.Encode as Encode
|
||||
|
||||
|
||||
|
||||
{--
|
||||
- Settings for notifying about due items.
|
||||
--}
|
||||
|
||||
|
||||
type alias PeriodicDueItemsSettings =
|
||||
{ id : String
|
||||
, enabled : Bool
|
||||
, summary : Maybe String
|
||||
, channel : NotificationChannel
|
||||
, schedule : String
|
||||
, remindDays : Int
|
||||
, capOverdue : Bool
|
||||
, tagsInclude : List Tag
|
||||
, tagsExclude : List Tag
|
||||
}
|
||||
|
||||
|
||||
empty : ChannelType -> PeriodicDueItemsSettings
|
||||
empty ct =
|
||||
{ id = ""
|
||||
, enabled = False
|
||||
, summary = Nothing
|
||||
, channel = Data.NotificationChannel.empty ct
|
||||
, schedule = ""
|
||||
, remindDays = 0
|
||||
, capOverdue = False
|
||||
, tagsInclude = []
|
||||
, tagsExclude = []
|
||||
}
|
||||
|
||||
|
||||
decoder : Decode.Decoder PeriodicDueItemsSettings
|
||||
decoder =
|
||||
Decode.succeed PeriodicDueItemsSettings
|
||||
|> P.required "id" Decode.string
|
||||
|> P.required "enabled" Decode.bool
|
||||
|> P.optional "summary" (Decode.maybe Decode.string) Nothing
|
||||
|> P.required "channel" Data.NotificationChannel.decoder
|
||||
|> P.required "schedule" Decode.string
|
||||
|> P.required "remindDays" Decode.int
|
||||
|> P.required "capOverdue" Decode.bool
|
||||
|> P.required "tagsInclude" (Decode.list Api.Model.Tag.decoder)
|
||||
|> P.required "tagsExclude" (Decode.list Api.Model.Tag.decoder)
|
||||
|
||||
|
||||
encode : PeriodicDueItemsSettings -> Encode.Value
|
||||
encode value =
|
||||
Encode.object
|
||||
[ ( "id", Encode.string value.id )
|
||||
, ( "enabled", Encode.bool value.enabled )
|
||||
, ( "summary", (Maybe.map Encode.string >> Maybe.withDefault Encode.null) value.summary )
|
||||
, ( "channel", Data.NotificationChannel.encode value.channel )
|
||||
, ( "schedule", Encode.string value.schedule )
|
||||
, ( "remindDays", Encode.int value.remindDays )
|
||||
, ( "capOverdue", Encode.bool value.capOverdue )
|
||||
, ( "tagsInclude", Encode.list Api.Model.Tag.encode value.tagsInclude )
|
||||
, ( "tagsExclude", Encode.list Api.Model.Tag.encode value.tagsExclude )
|
||||
]
|
@ -1,65 +0,0 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Data.PeriodicQuerySettings exposing (PeriodicQuerySettings, decoder, empty, encode)
|
||||
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Data.NotificationChannel exposing (NotificationChannel)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type alias PeriodicQuerySettings =
|
||||
{ id : String
|
||||
, enabled : Bool
|
||||
, summary : Maybe String
|
||||
, channel : NotificationChannel
|
||||
, query : Maybe String
|
||||
, bookmark : Maybe String
|
||||
, contentStart : Maybe String
|
||||
, schedule : String
|
||||
}
|
||||
|
||||
|
||||
empty : ChannelType -> PeriodicQuerySettings
|
||||
empty ct =
|
||||
{ id = ""
|
||||
, enabled = False
|
||||
, summary = Nothing
|
||||
, channel = Data.NotificationChannel.empty ct
|
||||
, query = Nothing
|
||||
, bookmark = Nothing
|
||||
, contentStart = Nothing
|
||||
, schedule = ""
|
||||
}
|
||||
|
||||
|
||||
decoder : D.Decoder PeriodicQuerySettings
|
||||
decoder =
|
||||
D.map8 PeriodicQuerySettings
|
||||
(D.field "id" D.string)
|
||||
(D.field "enabled" D.bool)
|
||||
(D.maybe (D.field "summary" D.string))
|
||||
(D.field "channel" Data.NotificationChannel.decoder)
|
||||
(D.maybe (D.field "query" D.string))
|
||||
(D.maybe (D.field "bookmark" D.string))
|
||||
(D.maybe (D.field "contentStart" D.string))
|
||||
(D.field "schedule" D.string)
|
||||
|
||||
|
||||
encode : PeriodicQuerySettings -> E.Value
|
||||
encode s =
|
||||
E.object
|
||||
[ ( "id", E.string s.id )
|
||||
, ( "enabled", E.bool s.enabled )
|
||||
, ( "summary", Maybe.map E.string s.summary |> Maybe.withDefault E.null )
|
||||
, ( "channel", Data.NotificationChannel.encode s.channel )
|
||||
, ( "query", Maybe.map E.string s.query |> Maybe.withDefault E.null )
|
||||
, ( "bookmark", Maybe.map E.string s.bookmark |> Maybe.withDefault E.null )
|
||||
, ( "contentStart", Maybe.map E.string s.contentStart |> Maybe.withDefault E.null )
|
||||
, ( "schedule", E.string s.schedule )
|
||||
]
|
@ -0,0 +1,41 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Messages.Comp.ChannelRefInput exposing
|
||||
( Texts
|
||||
, de
|
||||
, gb
|
||||
)
|
||||
|
||||
import Messages.Basics
|
||||
import Messages.Data.ChannelType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, placeholder : String
|
||||
, noCategory : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, placeholder = "Choose…"
|
||||
, noCategory = "No channel"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, placeholder = "Wähle…"
|
||||
, noCategory = "Kein Kanal"
|
||||
}
|
@ -15,6 +15,7 @@ import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.CalEventInput
|
||||
import Messages.Comp.ChannelForm
|
||||
import Messages.Comp.ChannelRefInput
|
||||
import Messages.Comp.HttpError
|
||||
import Messages.Comp.TagDropdown
|
||||
import Messages.Data.ChannelType
|
||||
@ -26,6 +27,8 @@ type alias Texts =
|
||||
, httpError : Http.Error -> String
|
||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||
, tagDropdown : Messages.Comp.TagDropdown.Texts
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||
, reallyDeleteTask : String
|
||||
, startOnce : String
|
||||
, startTaskNow : String
|
||||
@ -50,7 +53,7 @@ type alias Texts =
|
||||
, recipientsRequired : String
|
||||
, queryLabel : String
|
||||
, channelRequired : String
|
||||
, channelHeader : Messages.Data.ChannelType.Texts
|
||||
, channelHeader : String
|
||||
}
|
||||
|
||||
|
||||
@ -61,6 +64,8 @@ gb =
|
||||
, httpError = Messages.Comp.HttpError.gb
|
||||
, channelForm = Messages.Comp.ChannelForm.gb
|
||||
, tagDropdown = Messages.Comp.TagDropdown.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||
, reallyDeleteTask = "Really delete this notification task?"
|
||||
, startOnce = "Start Once"
|
||||
, startTaskNow = "Start this task now"
|
||||
@ -89,7 +94,7 @@ gb =
|
||||
, recipientsRequired = "At least one recipient is required."
|
||||
, queryLabel = "Query"
|
||||
, channelRequired = "A valid channel must be given."
|
||||
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
|
||||
, channelHeader = "Channels"
|
||||
}
|
||||
|
||||
|
||||
@ -98,8 +103,10 @@ de =
|
||||
{ basics = Messages.Basics.de
|
||||
, calEventInput = Messages.Comp.CalEventInput.de
|
||||
, httpError = Messages.Comp.HttpError.de
|
||||
, channelForm = Messages.Comp.ChannelForm.gb
|
||||
, tagDropdown = Messages.Comp.TagDropdown.gb
|
||||
, channelForm = Messages.Comp.ChannelForm.de
|
||||
, tagDropdown = Messages.Comp.TagDropdown.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
||||
, startOnce = "Jetzt starten"
|
||||
, startTaskNow = "Starte den Auftrag sofort"
|
||||
@ -128,5 +135,5 @@ de =
|
||||
, recipientsRequired = "Mindestens ein Empfänger muss angegeben werden."
|
||||
, queryLabel = "Abfrage"
|
||||
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
||||
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
|
||||
, channelHeader = "Kanäle"
|
||||
}
|
||||
|
@ -12,32 +12,33 @@ module Messages.Comp.DueItemsTaskList exposing
|
||||
)
|
||||
|
||||
import Messages.Basics
|
||||
import Messages.Data.ChannelType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, summary : String
|
||||
, schedule : String
|
||||
, connection : String
|
||||
, recipients : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, summary = "Summary"
|
||||
, schedule = "Schedule"
|
||||
, connection = "Connection"
|
||||
, recipients = "Recipients"
|
||||
, connection = "Channel"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, summary = "Kurzbeschreibung"
|
||||
, schedule = "Zeitplan"
|
||||
, connection = "Verbindung"
|
||||
, recipients = "Empfänger"
|
||||
, connection = "Kanal"
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Messages.Comp.NotificationChannelManage exposing (Texts, de, gb)
|
||||
|
||||
import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.ChannelForm
|
||||
import Messages.Comp.HttpError
|
||||
import Messages.Comp.NotificationChannelTable
|
||||
import Messages.Data.ChannelType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, notificationForm : Messages.Comp.ChannelForm.Texts
|
||||
, notificationTable : Messages.Comp.NotificationChannelTable.Texts
|
||||
, httpError : Http.Error -> String
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, newChannel : String
|
||||
, channelCreated : String
|
||||
, channelUpdated : String
|
||||
, channelDeleted : String
|
||||
, formInvalid : String
|
||||
, integrate : String
|
||||
, intoDocspell : String
|
||||
, postRequestInfo : String
|
||||
, notifyEmailInfo : String
|
||||
, addChannel : String
|
||||
, updateChannel : String
|
||||
, deleteThisChannel : String
|
||||
, reallyDeleteChannel : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, notificationForm = Messages.Comp.ChannelForm.gb
|
||||
, notificationTable = Messages.Comp.NotificationChannelTable.gb
|
||||
, httpError = Messages.Comp.HttpError.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, newChannel = "New Channel"
|
||||
, channelCreated = "Channel created"
|
||||
, channelUpdated = "Channel updated"
|
||||
, channelDeleted = "Channel deleted"
|
||||
, formInvalid = "Please fill in all required fields"
|
||||
, integrate = "Integrate"
|
||||
, intoDocspell = "into Docspell"
|
||||
, postRequestInfo = "Docspell will send POST requests with JSON payload."
|
||||
, notifyEmailInfo = "Get notified via e-mail."
|
||||
, addChannel = "Add new channel"
|
||||
, updateChannel = "Update channel"
|
||||
, deleteThisChannel = "Kanal löschen"
|
||||
, reallyDeleteChannel = "Really delete this channel?"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, notificationForm = Messages.Comp.ChannelForm.de
|
||||
, notificationTable = Messages.Comp.NotificationChannelTable.de
|
||||
, httpError = Messages.Comp.HttpError.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, newChannel = "Neuer Kanal"
|
||||
, channelCreated = "Kanal wurde angelegt."
|
||||
, channelUpdated = "Kanal wurde aktualisiert."
|
||||
, channelDeleted = "Kanal wurde entfernt."
|
||||
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
|
||||
, integrate = "Integriere"
|
||||
, intoDocspell = "in Docspell"
|
||||
, postRequestInfo = "Docspell wird JSON POST requests senden."
|
||||
, notifyEmailInfo = "Werde per E-Mail benachrichtigt."
|
||||
, addChannel = "Neuen Kanal hinzufügen"
|
||||
, updateChannel = "Kanal aktualisieren"
|
||||
, deleteThisChannel = "Kanal löschen"
|
||||
, reallyDeleteChannel = "Den Kanal wirklich löschen?"
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{-
|
||||
Copyright 2020 Eike K. & Contributors
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-}
|
||||
|
||||
|
||||
module Messages.Comp.NotificationChannelTable exposing (..)
|
||||
|
||||
import Data.EventType exposing (EventType)
|
||||
import Messages.Basics
|
||||
import Messages.Data.EventType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, eventType : EventType -> Messages.Data.EventType.Texts
|
||||
, channelType : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, eventType = Messages.Data.EventType.gb
|
||||
, channelType = "Channel type"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, eventType = Messages.Data.EventType.de
|
||||
, channelType = "Kanaltyp"
|
||||
}
|
@ -13,18 +13,17 @@ module Messages.Comp.NotificationHookForm exposing
|
||||
|
||||
import Data.EventType exposing (EventType)
|
||||
import Messages.Basics
|
||||
import Messages.Comp.ChannelForm
|
||||
import Messages.Comp.ChannelRefInput
|
||||
import Messages.Comp.EventSample
|
||||
import Messages.Data.ChannelType
|
||||
import Messages.Data.EventType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||
, eventType : EventType -> Messages.Data.EventType.Texts
|
||||
, eventSample : Messages.Comp.EventSample.Texts
|
||||
, channelHeader : Messages.Data.ChannelType.Texts
|
||||
, channelHeader : String
|
||||
, enableDisable : String
|
||||
, eventsInfo : String
|
||||
, selectEvents : String
|
||||
@ -34,16 +33,19 @@ type alias Texts =
|
||||
, eventFilter : String
|
||||
, eventFilterInfo : String
|
||||
, eventFilterClickForHelp : String
|
||||
, jsonPayload : String
|
||||
, messagePayload : String
|
||||
, payloadInfo : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, channelForm = Messages.Comp.ChannelForm.gb
|
||||
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||
, eventType = Messages.Data.EventType.gb
|
||||
, eventSample = Messages.Comp.EventSample.gb
|
||||
, channelHeader = Messages.Data.ChannelType.gb
|
||||
, channelHeader = "Select channels"
|
||||
, enableDisable = "Enabled / Disabled"
|
||||
, eventsInfo = "Select events that trigger this webhook"
|
||||
, selectEvents = "Select…"
|
||||
@ -53,16 +55,19 @@ gb =
|
||||
, eventFilter = "Event Filter Expression"
|
||||
, eventFilterInfo = "Optional specify an expression to filter events based on their JSON structure."
|
||||
, eventFilterClickForHelp = "Click here for help"
|
||||
, jsonPayload = "JSON"
|
||||
, messagePayload = "Message"
|
||||
, payloadInfo = "Message payloads are sent to gotify, email and matrix. The JSON is sent to http channel."
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, channelForm = Messages.Comp.ChannelForm.de
|
||||
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||
, eventType = Messages.Data.EventType.de
|
||||
, eventSample = Messages.Comp.EventSample.de
|
||||
, channelHeader = Messages.Data.ChannelType.de
|
||||
, channelHeader = "Kanäle"
|
||||
, enableDisable = "Aktiviert / Deaktivert"
|
||||
, eventsInfo = "Wähle die Ereignisse, die diesen webhook auslösen"
|
||||
, selectEvents = "Wähle…"
|
||||
@ -72,4 +77,7 @@ de =
|
||||
, eventFilter = "Ereignisfilter"
|
||||
, eventFilterInfo = "Optionaler Ausdruck zum filtern von Ereignissen auf Basis ihrer JSON Struktur."
|
||||
, eventFilterClickForHelp = "Klicke für Hilfe"
|
||||
, jsonPayload = "JSON"
|
||||
, messagePayload = "Nachricht"
|
||||
, payloadInfo = "Es werden abhängig vom Kanal JSON oder Nachricht-Formate versendet. Der HTTP Kanal empfängt nur JSON, an die anderen wird das Nachrichtformat gesendet."
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ module Messages.Comp.NotificationHookManage exposing
|
||||
, gb
|
||||
)
|
||||
|
||||
import Html exposing (Html, text)
|
||||
import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.HttpError
|
||||
@ -27,9 +26,6 @@ type alias Texts =
|
||||
, httpError : Http.Error -> String
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, newHook : String
|
||||
, matrix : String
|
||||
, gotify : String
|
||||
, email : String
|
||||
, httpRequest : String
|
||||
, hookCreated : String
|
||||
, hookUpdated : String
|
||||
@ -39,12 +35,8 @@ type alias Texts =
|
||||
, reallyDeleteHook : String
|
||||
, formInvalid : String
|
||||
, invalidJsonFilter : String -> String
|
||||
, integrate : String
|
||||
, intoDocspell : String
|
||||
, postRequestInfo : String
|
||||
, updateWebhook : String
|
||||
, addWebhook : String
|
||||
, notifyEmailInfo : String
|
||||
}
|
||||
|
||||
|
||||
@ -56,9 +48,6 @@ gb =
|
||||
, httpError = Messages.Comp.HttpError.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, newHook = "New Webhook"
|
||||
, matrix = "Matrix"
|
||||
, gotify = "Gotify"
|
||||
, email = "E-Mail"
|
||||
, httpRequest = "HTTP Request"
|
||||
, hookCreated = "Webhook created"
|
||||
, hookUpdated = "Webhook updated"
|
||||
@ -68,12 +57,8 @@ gb =
|
||||
, reallyDeleteHook = "Really delete this webhook?"
|
||||
, formInvalid = "Please fill in all required fields"
|
||||
, invalidJsonFilter = \m -> "Event filter invalid: " ++ m
|
||||
, integrate = "Integrate"
|
||||
, intoDocspell = "into Docspell"
|
||||
, postRequestInfo = "Docspell will send POST requests with JSON payload."
|
||||
, updateWebhook = "Update webhook"
|
||||
, addWebhook = "Add new webhook"
|
||||
, notifyEmailInfo = "Get notified via e-mail."
|
||||
}
|
||||
|
||||
|
||||
@ -85,9 +70,6 @@ de =
|
||||
, httpError = Messages.Comp.HttpError.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, newHook = "Neuer Webhook"
|
||||
, matrix = "Matrix"
|
||||
, gotify = "Gotify"
|
||||
, email = "E-Mail"
|
||||
, httpRequest = "HTTP Request"
|
||||
, hookCreated = "Webhook erstellt"
|
||||
, hookUpdated = "Webhook aktualisiert"
|
||||
@ -97,10 +79,6 @@ de =
|
||||
, reallyDeleteHook = "Den webhook wirklich löschen?"
|
||||
, formInvalid = "Bitte alle erforderlichen Felder ausfüllen"
|
||||
, invalidJsonFilter = \m -> "Ereignisfilter ist falsch: " ++ m
|
||||
, integrate = "Integriere"
|
||||
, intoDocspell = "in Docspell"
|
||||
, postRequestInfo = "Docspell wird JSON POST requests senden."
|
||||
, updateWebhook = "Webhook aktualisieren"
|
||||
, addWebhook = "Neuen Webhook hinzufügen"
|
||||
, notifyEmailInfo = "Werde per E-Mail benachrichtigt."
|
||||
}
|
||||
|
@ -13,15 +13,18 @@ module Messages.Comp.NotificationHookTable exposing
|
||||
|
||||
import Data.EventType exposing (EventType)
|
||||
import Messages.Basics
|
||||
import Messages.Data.ChannelType
|
||||
import Messages.Data.EventType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, eventType : EventType -> Messages.Data.EventType.Texts
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, enabled : String
|
||||
, channel : String
|
||||
, events : String
|
||||
, allEvents : String
|
||||
}
|
||||
|
||||
|
||||
@ -29,9 +32,11 @@ gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, eventType = Messages.Data.EventType.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, enabled = "Enabled"
|
||||
, channel = "Channel"
|
||||
, events = "Events"
|
||||
, allEvents = "All"
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +44,9 @@ de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, eventType = Messages.Data.EventType.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, enabled = "Aktiv"
|
||||
, channel = "Kanal"
|
||||
, events = "Ereignisse"
|
||||
, allEvents = "Alle"
|
||||
}
|
||||
|
@ -11,14 +11,13 @@ module Messages.Comp.PeriodicQueryTaskForm exposing
|
||||
, gb
|
||||
)
|
||||
|
||||
import Data.ChannelType exposing (ChannelType)
|
||||
import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.BookmarkDropdown
|
||||
import Messages.Comp.CalEventInput
|
||||
import Messages.Comp.ChannelForm
|
||||
import Messages.Comp.ChannelRefInput
|
||||
import Messages.Comp.HttpError
|
||||
import Messages.Data.ChannelType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
@ -26,6 +25,7 @@ type alias Texts =
|
||||
, calEventInput : Messages.Comp.CalEventInput.Texts
|
||||
, channelForm : Messages.Comp.ChannelForm.Texts
|
||||
, bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
|
||||
, channelRef : Messages.Comp.ChannelRefInput.Texts
|
||||
, httpError : Http.Error -> String
|
||||
, reallyDeleteTask : String
|
||||
, startOnce : String
|
||||
@ -41,7 +41,7 @@ type alias Texts =
|
||||
, invalidCalEvent : String
|
||||
, channelRequired : String
|
||||
, queryStringRequired : String
|
||||
, channelHeader : ChannelType -> String
|
||||
, channelHeader : String
|
||||
, messageContentTitle : String
|
||||
, messageContentLabel : String
|
||||
, messageContentInfo : String
|
||||
@ -56,6 +56,7 @@ gb =
|
||||
, channelForm = Messages.Comp.ChannelForm.gb
|
||||
, httpError = Messages.Comp.HttpError.gb
|
||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
|
||||
, channelRef = Messages.Comp.ChannelRefInput.gb
|
||||
, reallyDeleteTask = "Really delete this notification task?"
|
||||
, startOnce = "Start Once"
|
||||
, startTaskNow = "Start this task now"
|
||||
@ -74,7 +75,7 @@ gb =
|
||||
, queryLabel = "Query"
|
||||
, channelRequired = "A valid channel must be given."
|
||||
, queryStringRequired = "A query string and/or bookmark must be supplied"
|
||||
, channelHeader = \ct -> "Connection details for " ++ Messages.Data.ChannelType.gb ct
|
||||
, channelHeader = "Channels"
|
||||
, messageContentTitle = "Customize message"
|
||||
, messageContentLabel = "Beginning of message"
|
||||
, messageContentInfo = "Insert text that is prependend to the generated message."
|
||||
@ -89,6 +90,7 @@ de =
|
||||
, channelForm = Messages.Comp.ChannelForm.de
|
||||
, httpError = Messages.Comp.HttpError.de
|
||||
, bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
|
||||
, channelRef = Messages.Comp.ChannelRefInput.de
|
||||
, reallyDeleteTask = "Diesen Benachrichtigungsauftrag wirklich löschen?"
|
||||
, startOnce = "Jetzt starten"
|
||||
, startTaskNow = "Starte den Auftrag sofort"
|
||||
@ -107,7 +109,7 @@ de =
|
||||
, queryLabel = "Abfrage"
|
||||
, channelRequired = "Ein Versandkanal muss angegeben werden."
|
||||
, queryStringRequired = "Eine Suchabfrage und/oder ein Bookmark muss angegeben werden."
|
||||
, channelHeader = \ct -> "Details für " ++ Messages.Data.ChannelType.de ct
|
||||
, channelHeader = "Kanäle"
|
||||
, messageContentTitle = "Nachricht anpassen"
|
||||
, messageContentLabel = "Anfang der Nachricht"
|
||||
, messageContentInfo = "Dieser Text wird an den Anfang der generierten Nachricht angefügt."
|
||||
|
@ -12,32 +12,33 @@ module Messages.Comp.PeriodicQueryTaskList exposing
|
||||
)
|
||||
|
||||
import Messages.Basics
|
||||
import Messages.Data.ChannelType
|
||||
|
||||
|
||||
type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, channelType : Messages.Data.ChannelType.Texts
|
||||
, summary : String
|
||||
, schedule : String
|
||||
, connection : String
|
||||
, recipients : String
|
||||
}
|
||||
|
||||
|
||||
gb : Texts
|
||||
gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, channelType = Messages.Data.ChannelType.gb
|
||||
, summary = "Summary"
|
||||
, schedule = "Schedule"
|
||||
, connection = "Connection"
|
||||
, recipients = "Recipients"
|
||||
, connection = "Channel"
|
||||
}
|
||||
|
||||
|
||||
de : Texts
|
||||
de =
|
||||
{ basics = Messages.Basics.de
|
||||
, channelType = Messages.Data.ChannelType.de
|
||||
, summary = "Kurzbeschreibung"
|
||||
, schedule = "Zeitplan"
|
||||
, connection = "Verbindung"
|
||||
, recipients = "Empfänger"
|
||||
, connection = "Kanal"
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ gb ct =
|
||||
"E-Mail"
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
"HTTP request"
|
||||
"JSON"
|
||||
|
||||
|
||||
de : Texts
|
||||
@ -43,4 +43,4 @@ de ct =
|
||||
"E-Mail"
|
||||
|
||||
Data.ChannelType.Http ->
|
||||
"HTTP Request"
|
||||
"JSON"
|
||||
|
@ -15,6 +15,7 @@ import Messages.Comp.ChangePasswordForm
|
||||
import Messages.Comp.DueItemsTaskManage
|
||||
import Messages.Comp.EmailSettingsManage
|
||||
import Messages.Comp.ImapSettingsManage
|
||||
import Messages.Comp.NotificationChannelManage
|
||||
import Messages.Comp.NotificationHookManage
|
||||
import Messages.Comp.OtpSetup
|
||||
import Messages.Comp.PeriodicQueryTaskManage
|
||||
@ -31,6 +32,7 @@ type alias Texts =
|
||||
, scanMailboxManage : Messages.Comp.ScanMailboxManage.Texts
|
||||
, notificationHookManage : Messages.Comp.NotificationHookManage.Texts
|
||||
, periodicQueryTask : Messages.Comp.PeriodicQueryTaskManage.Texts
|
||||
, channelManage : Messages.Comp.NotificationChannelManage.Texts
|
||||
, otpSetup : Messages.Comp.OtpSetup.Texts
|
||||
, userSettings : String
|
||||
, uiSettings : String
|
||||
@ -38,6 +40,7 @@ type alias Texts =
|
||||
, scanMailbox : String
|
||||
, emailSettingSmtp : String
|
||||
, emailSettingImap : String
|
||||
, channelSettings : String
|
||||
, changePassword : String
|
||||
, uiSettingsInfo : String
|
||||
, scanMailboxInfo1 : String
|
||||
@ -50,6 +53,8 @@ type alias Texts =
|
||||
, webhookInfoText : String
|
||||
, dueItemsInfoText : String
|
||||
, periodicQueryInfoText : String
|
||||
, channels : String
|
||||
, channelInfoText : String
|
||||
}
|
||||
|
||||
|
||||
@ -63,6 +68,7 @@ gb =
|
||||
, scanMailboxManage = Messages.Comp.ScanMailboxManage.gb
|
||||
, notificationHookManage = Messages.Comp.NotificationHookManage.gb
|
||||
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.gb
|
||||
, channelManage = Messages.Comp.NotificationChannelManage.gb
|
||||
, otpSetup = Messages.Comp.OtpSetup.gb
|
||||
, userSettings = "User Settings"
|
||||
, uiSettings = "UI Settings"
|
||||
@ -71,6 +77,7 @@ gb =
|
||||
, emailSettingSmtp = "E-Mail Settings (SMTP)"
|
||||
, emailSettingImap = "E-Mail Settings (IMAP)"
|
||||
, changePassword = "Change Password"
|
||||
, channelSettings = "Notification Channels"
|
||||
, uiSettingsInfo =
|
||||
"These settings only affect the web ui. They are stored in the browser, "
|
||||
++ "so they are separated between browsers and devices."
|
||||
@ -103,14 +110,16 @@ its payload.
|
||||
Additionally, you can setup queries that are executed periodically.
|
||||
The results are send as a notification message.
|
||||
|
||||
When creating a new notification task, choose first the communication
|
||||
channel.
|
||||
A notification setting needs at least one communication channel, which
|
||||
must be created before.
|
||||
|
||||
"""
|
||||
, webhookInfoText = """Webhooks execute http request upon certain events in docspell.
|
||||
"""
|
||||
, dueItemsInfoText = """Docspell can notify you once the due dates of your items come closer. """
|
||||
, periodicQueryInfoText = "You can define a custom query that gets executed periodically."
|
||||
, channels = "Notification Channels"
|
||||
, channelInfoText = "Channels are used to send notification messages."
|
||||
}
|
||||
|
||||
|
||||
@ -124,6 +133,7 @@ de =
|
||||
, scanMailboxManage = Messages.Comp.ScanMailboxManage.de
|
||||
, notificationHookManage = Messages.Comp.NotificationHookManage.de
|
||||
, periodicQueryTask = Messages.Comp.PeriodicQueryTaskManage.de
|
||||
, channelManage = Messages.Comp.NotificationChannelManage.de
|
||||
, otpSetup = Messages.Comp.OtpSetup.de
|
||||
, userSettings = "Benutzereinstellung"
|
||||
, uiSettings = "Oberfläche"
|
||||
@ -131,6 +141,7 @@ de =
|
||||
, scanMailbox = "E-Mail-Import"
|
||||
, emailSettingSmtp = "E-Mail-Einstellungen (SMTP)"
|
||||
, emailSettingImap = "E-Mail-Einstellungen (IMAP)"
|
||||
, channelSettings = "Benachrichtigungskanäle"
|
||||
, changePassword = "Passwort ändern"
|
||||
, uiSettingsInfo =
|
||||
"Diese Einstellungen sind für die Web-Oberfläche."
|
||||
@ -161,15 +172,16 @@ Es kann aus diesen Versandkanälen gewählt werden:
|
||||
E-Mail. Zusätzlich kann das HTTP request direkt empfangen werden, was
|
||||
alle Details zu einem Ereignis enthält.
|
||||
|
||||
|
||||
Ausserdem können periodische Suchabfragen erstellt werden, dessen
|
||||
Ergebnis dann als Benachrichtigung versendet wird.
|
||||
|
||||
Beim Erstellen eines neuen Auftrags muss zunächst der gewünschte
|
||||
Versandkanal gewählt werden.
|
||||
Für eine Notifikation ist ein Kommunikationskanal notwendig, der zuvor
|
||||
erstellt werden muss.
|
||||
|
||||
"""
|
||||
, webhookInfoText = """Webhooks versenden HTTP Requests wenn bestimmte Ereignisse in Docspell auftreten."""
|
||||
, dueItemsInfoText = """Docspell kann dich benachrichtigen, sobald das Fälligkeitsdatum von Dokumenten näher kommt. """
|
||||
, periodicQueryInfoText = "Hier können beliebige Abfragen definiert werden, welche regelmäßig ausgeführt werden."
|
||||
, channels = "Benachrichtigungskanäle"
|
||||
, channelInfoText = "Über Kanäle werden Notifizierungen versendet."
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import Comp.ChangePasswordForm
|
||||
import Comp.DueItemsTaskManage
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationChannelManage
|
||||
import Comp.NotificationHookManage
|
||||
import Comp.OtpSetup
|
||||
import Comp.PeriodicQueryTaskManage
|
||||
@ -35,6 +36,7 @@ type alias Model =
|
||||
, uiSettingsModel : Comp.UiSettingsManage.Model
|
||||
, otpSetupModel : Comp.OtpSetup.Model
|
||||
, notificationHookModel : Comp.NotificationHookManage.Model
|
||||
, channelModel : Comp.NotificationChannelManage.Model
|
||||
, periodicQueryModel : Comp.PeriodicQueryTaskManage.Model
|
||||
}
|
||||
|
||||
@ -53,6 +55,9 @@ init flags settings =
|
||||
|
||||
( pqm, pqc ) =
|
||||
Comp.PeriodicQueryTaskManage.init flags
|
||||
|
||||
( ncm, ncc ) =
|
||||
Comp.NotificationChannelManage.init flags
|
||||
in
|
||||
( { currentTab = Just UiSettingsTab
|
||||
, changePassModel = Comp.ChangePasswordForm.emptyModel
|
||||
@ -64,12 +69,14 @@ init flags settings =
|
||||
, otpSetupModel = otpm
|
||||
, notificationHookModel = nhm
|
||||
, periodicQueryModel = pqm
|
||||
, channelModel = ncm
|
||||
}
|
||||
, Cmd.batch
|
||||
[ Cmd.map UiSettingsMsg uc
|
||||
, Cmd.map OtpSetupMsg otpc
|
||||
, Cmd.map NotificationHookMsg nhc
|
||||
, Cmd.map PeriodicQueryMsg pqc
|
||||
, Cmd.map ChannelMsg ncc
|
||||
]
|
||||
)
|
||||
|
||||
@ -85,6 +92,7 @@ type Tab
|
||||
| ScanMailboxTab
|
||||
| UiSettingsTab
|
||||
| OtpTab
|
||||
| ChannelTab
|
||||
|
||||
|
||||
type Msg
|
||||
@ -98,5 +106,6 @@ type Msg
|
||||
| OtpSetupMsg Comp.OtpSetup.Msg
|
||||
| NotificationHookMsg Comp.NotificationHookManage.Msg
|
||||
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
|
||||
| ChannelMsg Comp.NotificationChannelManage.Msg
|
||||
| UpdateSettings
|
||||
| ReceiveBrowserSettings StoredUiSettings
|
||||
|
@ -11,6 +11,7 @@ import Comp.ChangePasswordForm
|
||||
import Comp.DueItemsTaskManage
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationChannelManage
|
||||
import Comp.NotificationHookManage
|
||||
import Comp.OtpSetup
|
||||
import Comp.PeriodicQueryTaskManage
|
||||
@ -71,8 +72,12 @@ update flags settings msg model =
|
||||
}
|
||||
|
||||
NotificationWebhookTab ->
|
||||
let
|
||||
( _, nc ) =
|
||||
Comp.NotificationHookManage.init flags
|
||||
in
|
||||
{ model = m
|
||||
, cmd = Cmd.none
|
||||
, cmd = Cmd.map NotificationHookMsg nc
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
@ -107,6 +112,9 @@ update flags settings msg model =
|
||||
OtpTab ->
|
||||
UpdateResult m Cmd.none Sub.none Nothing
|
||||
|
||||
ChannelTab ->
|
||||
UpdateResult m Cmd.none Sub.none Nothing
|
||||
|
||||
ChangePassMsg m ->
|
||||
let
|
||||
( m2, c2 ) =
|
||||
@ -195,6 +203,17 @@ update flags settings msg model =
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
ChannelMsg lm ->
|
||||
let
|
||||
( cm, cc ) =
|
||||
Comp.NotificationChannelManage.update flags lm model.channelModel
|
||||
in
|
||||
{ model = { model | channelModel = cm }
|
||||
, cmd = Cmd.map ChannelMsg cc
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
UpdateSettings ->
|
||||
update flags
|
||||
settings
|
||||
|
@ -11,6 +11,7 @@ import Comp.ChangePasswordForm
|
||||
import Comp.DueItemsTaskManage
|
||||
import Comp.EmailSettingsManage
|
||||
import Comp.ImapSettingsManage
|
||||
import Comp.NotificationChannelManage
|
||||
import Comp.NotificationHookManage
|
||||
import Comp.OtpSetup
|
||||
import Comp.PeriodicQueryTaskManage
|
||||
@ -77,7 +78,7 @@ viewSidebar texts visible _ _ model =
|
||||
, menuEntryActive model NotificationTab
|
||||
, class S.sidebarLink
|
||||
]
|
||||
[ i [ class "fa fa-bullhorn" ] []
|
||||
[ i [ class "fa fa-comment font-thin" ] []
|
||||
, span
|
||||
[ class "ml-3" ]
|
||||
[ text texts.notifications ]
|
||||
@ -121,6 +122,17 @@ viewSidebar texts visible _ _ model =
|
||||
]
|
||||
]
|
||||
]
|
||||
, a
|
||||
[ href "#"
|
||||
, onClick (SetTab ChannelTab)
|
||||
, menuEntryActive model ChannelTab
|
||||
, class S.sidebarLink
|
||||
]
|
||||
[ i [ class "fa fa-bullhorn" ] []
|
||||
, span
|
||||
[ class "ml-3" ]
|
||||
[ text texts.channelSettings ]
|
||||
]
|
||||
, a
|
||||
[ href "#"
|
||||
, onClick (SetTab ScanMailboxTab)
|
||||
@ -217,6 +229,9 @@ viewContent texts flags settings model =
|
||||
Just OtpTab ->
|
||||
viewOtpSetup texts settings model
|
||||
|
||||
Just ChannelTab ->
|
||||
viewChannels texts settings model
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
)
|
||||
@ -235,6 +250,26 @@ menuEntryActive model tab =
|
||||
class ""
|
||||
|
||||
|
||||
viewChannels : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||
viewChannels texts settings model =
|
||||
[ h2
|
||||
[ class S.header1
|
||||
, class "inline-flex items-center"
|
||||
]
|
||||
[ i [ class "fa fa-bell" ] []
|
||||
, div [ class "ml-3" ]
|
||||
[ text texts.channels
|
||||
]
|
||||
]
|
||||
, Markdown.toHtml [ class "opacity-80 text-lg mb-3 markdown-preview" ] texts.channelInfoText
|
||||
, Html.map ChannelMsg
|
||||
(Comp.NotificationChannelManage.view texts.channelManage
|
||||
settings
|
||||
model.channelModel
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewOtpSetup : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||
viewOtpSetup texts _ model =
|
||||
[ h2
|
||||
|
Reference in New Issue
Block a user