mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
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:
@ -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,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,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(""), None, 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
|
||||
}
|
||||
}
|
29
modules/store/src/main/scala/db/migration/data/package.scala
Normal file
29
modules/store/src/main/scala/db/migration/data/package.scala
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration
|
||||
|
||||
import docspell.notification.api._
|
||||
|
||||
import emil.MailAddress
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
package object data {
|
||||
type ChannelOrRef = Either[ChannelRef, Channel]
|
||||
|
||||
object ChannelOrRef {
|
||||
implicit def jsonDecoder(implicit mc: Decoder[MailAddress]): Decoder[ChannelOrRef] =
|
||||
Channel.jsonDecoder.either(ChannelRef.jsonDecoder).map(_.swap)
|
||||
|
||||
implicit def jsonEncoder(implicit mc: Encoder[MailAddress]): Encoder[ChannelOrRef] =
|
||||
Encoder.instance(_.fold(ChannelRef.jsonEncoder.apply, Channel.jsonEncoder.apply))
|
||||
|
||||
implicit class ChannelOrRefOpts(cr: ChannelOrRef) {
|
||||
def channelType: ChannelType =
|
||||
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,10 +7,10 @@
|
||||
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._
|
||||
|
||||
@ -20,6 +20,17 @@ sealed trait RNotificationChannel {
|
||||
|
||||
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,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
@ -93,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] =
|
||||
@ -138,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
|
||||
}
|
||||
|
@ -49,8 +49,12 @@ object RNotificationChannelGotify {
|
||||
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(
|
||||
|
@ -45,8 +45,10 @@ object RNotificationChannelHttp {
|
||||
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.name},${r.url},${r.created}")
|
||||
|
@ -65,8 +65,10 @@ object RNotificationChannelMail {
|
||||
)
|
||||
)
|
||||
|
||||
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")
|
||||
|
@ -77,8 +77,12 @@ object RNotificationChannelMatrix {
|
||||
)
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user