Manage notification channels separately and migrate

It's more convenient to manage notification channels separately, as it
is done with email settings. Notification hook and other forms are
adopted to only select channels. Hooks can now use more than one
channel.
This commit is contained in:
eikek
2022-01-19 21:51:18 +01:00
parent d41490dd88
commit 23cb34a6ff
78 changed files with 2583 additions and 1422 deletions

View File

@ -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)
)
}
)
}
}

View File

@ -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,

View File

@ -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
)
)
}