mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Add support for more generic notification
This is a start to have different kinds of notifications. It is possible to be notified via e-mail, matrix or gotify. It also extends the current "periodic query" for due items by allowing notification over different channels. A "generic periodic query" variant is added as well.
This commit is contained in:
@ -0,0 +1,62 @@
|
||||
create table "notification_channel_mail" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"conn_id" varchar(254) not null,
|
||||
"recipients" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade,
|
||||
foreign key ("conn_id") references "useremail"("id") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_gotify" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"url" varchar(254) not null,
|
||||
"app_key" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_matrix" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"home_server" varchar(254) not null,
|
||||
"room_id" varchar(254) not null,
|
||||
"access_token" varchar not null,
|
||||
"message_type" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_http" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"url" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_hook" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"enabled" boolean not null,
|
||||
"channel_mail" varchar(254),
|
||||
"channel_gotify" varchar(254),
|
||||
"channel_matrix" varchar(254),
|
||||
"channel_http" varchar(254),
|
||||
"all_events" boolean not null,
|
||||
"event_filter" varchar(500),
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") 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
|
||||
);
|
||||
|
||||
create table "notification_hook_event" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"hook_id" varchar(254) not null,
|
||||
"event_type" varchar(254) not null,
|
||||
foreign key ("hook_id") references "notification_hook"("id") on delete cascade
|
||||
);
|
@ -0,0 +1,62 @@
|
||||
create table `notification_channel_mail` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`conn_id` varchar(254) not null,
|
||||
`recipients` varchar(254) not null,
|
||||
`created` timestamp not null,
|
||||
foreign key (`uid`) references `user_`(`uid`) on delete cascade,
|
||||
foreign key (`conn_id`) references `useremail`(`id`) on delete cascade
|
||||
);
|
||||
|
||||
create table `notification_channel_gotify` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`url` varchar(254) not null,
|
||||
`app_key` varchar(254) not null,
|
||||
`created` timestamp not null,
|
||||
foreign key (`uid`) references `user_`(`uid`) on delete cascade
|
||||
);
|
||||
|
||||
create table `notification_channel_matrix` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`home_server` varchar(254) not null,
|
||||
`room_id` varchar(254) not null,
|
||||
`access_token` text not null,
|
||||
`message_type` varchar(254) not null,
|
||||
`created` timestamp not null,
|
||||
foreign key (`uid`) references `user_`(`uid`) on delete cascade
|
||||
);
|
||||
|
||||
create table `notification_channel_http` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`url` varchar(254) not null,
|
||||
`created` timestamp not null,
|
||||
foreign key (`uid`) references `user_`(`uid`) on delete cascade
|
||||
);
|
||||
|
||||
create table `notification_hook` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`uid` varchar(254) not null,
|
||||
`enabled` boolean not null,
|
||||
`channel_mail` varchar(254),
|
||||
`channel_gotify` varchar(254),
|
||||
`channel_matrix` varchar(254),
|
||||
`channel_http` varchar(254),
|
||||
`all_events` boolean not null,
|
||||
`event_filter` varchar(500),
|
||||
`created` timestamp not null,
|
||||
foreign key (`uid`) references `user_`(`uid`) 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
|
||||
);
|
||||
|
||||
create table `notification_hook_event` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`hook_id` varchar(254) not null,
|
||||
`event_type` varchar(254) not null,
|
||||
foreign key (`hook_id`) references `notification_hook`(`id`) on delete cascade
|
||||
);
|
@ -0,0 +1,62 @@
|
||||
create table "notification_channel_mail" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"conn_id" varchar(254) not null,
|
||||
"recipients" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade,
|
||||
foreign key ("conn_id") references "useremail"("id") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_gotify" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"url" varchar(254) not null,
|
||||
"app_key" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_matrix" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"home_server" varchar(254) not null,
|
||||
"room_id" varchar(254) not null,
|
||||
"access_token" varchar not null,
|
||||
"message_type" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_channel_http" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"url" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") on delete cascade
|
||||
);
|
||||
|
||||
create table "notification_hook" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"uid" varchar(254) not null,
|
||||
"enabled" boolean not null,
|
||||
"channel_mail" varchar(254),
|
||||
"channel_gotify" varchar(254),
|
||||
"channel_matrix" varchar(254),
|
||||
"channel_http" varchar(254),
|
||||
"all_events" boolean not null,
|
||||
"event_filter" varchar(500),
|
||||
"created" timestamp not null,
|
||||
foreign key ("uid") references "user_"("uid") 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
|
||||
);
|
||||
|
||||
create table "notification_hook_event" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"hook_id" varchar(254) not null,
|
||||
"event_type" varchar(254) not null,
|
||||
foreign key ("hook_id") references "notification_hook"("id") on delete cascade
|
||||
);
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package db.migration
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
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 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 org.flywaydb.core.api.migration.Context
|
||||
|
||||
trait MigrationTasks {
|
||||
|
||||
def logger: org.log4s.Logger
|
||||
|
||||
implicit val jsonEncoder: Encoder[MailAddress] =
|
||||
Encoder.encodeString.contramap(_.asUnicodeString)
|
||||
|
||||
def migrateDueItemTasks: ConnectionIO[Unit] =
|
||||
for {
|
||||
tasks <- RPeriodicTask.findByTask(NotifyDueItemsArgs.taskName)
|
||||
_ <- Sync[ConnectionIO].delay(
|
||||
logger.info(s"Starting to migrate ${tasks.size} user tasks")
|
||||
)
|
||||
_ <- tasks.traverse(migrateDueItemTask1)
|
||||
_ <- RPeriodicTask.setEnabledByTask(NotifyDueItemsArgs.taskName, false)
|
||||
} yield ()
|
||||
|
||||
def migrateDueItemTask1(old: RPeriodicTask): ConnectionIO[Int] = {
|
||||
val converted = old.args
|
||||
.parseJsonAs[NotifyDueItemsArgs]
|
||||
.leftMap(_.getMessage())
|
||||
.flatMap(convertArgs)
|
||||
|
||||
converted match {
|
||||
case Right(args) =>
|
||||
Sync[ConnectionIO].delay(logger.info(s"Converting user task: $old")) *>
|
||||
RPeriodicTask.updateTask(
|
||||
old.id,
|
||||
PeriodicDueItemsArgs.taskName,
|
||||
args.asJson.noSpaces
|
||||
)
|
||||
|
||||
case Left(err) =>
|
||||
logger.error(s"Error converting user task: $old. $err")
|
||||
0.pure[ConnectionIO]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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,23 @@
|
||||
/*
|
||||
* 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
|
||||
import org.flywaydb.core.api.migration.Context
|
||||
|
||||
class V1_29_2__MigrateNotifyTask extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migrateDueItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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
|
||||
import org.flywaydb.core.api.migration.Context
|
||||
|
||||
class V1_29_2__MigrateNotifyTask extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migrateDueItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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
|
||||
import org.flywaydb.core.api.migration.Context
|
||||
|
||||
class V1_29_2__MigrateNotifyTask extends BaseJavaMigration with MigrationTasks {
|
||||
val logger = org.log4s.getLogger
|
||||
|
||||
override def migrate(ctx: Context): Unit = {
|
||||
val xa = mkTransactor(ctx)
|
||||
migrateDueItemTasks.transact(xa).unsafeRunSync()
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ import java.time.{Instant, LocalDate}
|
||||
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
import docspell.notification.api.EventType
|
||||
import docspell.query.{ItemQuery, ItemQueryParser}
|
||||
import docspell.totp.Key
|
||||
|
||||
@ -148,6 +150,12 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
Meta[String].timap(s => ItemQueryParser.parseUnsafe(s))(q =>
|
||||
q.raw.getOrElse(ItemQueryParser.unsafeAsString(q.expr))
|
||||
)
|
||||
|
||||
implicit val metaEventType: Meta[EventType] =
|
||||
Meta[String].timap(EventType.unsafeFromString)(_.name)
|
||||
|
||||
implicit val metaJsonMiniQuery: Meta[JsonMiniQuery] =
|
||||
Meta[String].timap(JsonMiniQuery.unsafeParse)(_.unsafeAsString)
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
|
@ -22,12 +22,12 @@ object FlywayMigrate {
|
||||
logger.info("Running db migrations...")
|
||||
val locations = jdbc.dbmsName match {
|
||||
case Some(dbtype) =>
|
||||
List(s"classpath:db/migration/$dbtype")
|
||||
List(s"classpath:db/migration/$dbtype", "classpath:db/migration/common")
|
||||
case None =>
|
||||
logger.warn(
|
||||
s"Cannot read database name from jdbc url: ${jdbc.url}. Go with H2"
|
||||
)
|
||||
List("classpath:db/h2")
|
||||
List("classpath:db/migration/h2", "classpath:db/migration/common")
|
||||
}
|
||||
|
||||
logger.info(s"Using migration locations: $locations")
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.queries
|
||||
|
||||
import cats.data.{NonEmptyList, OptionT}
|
||||
import cats.effect._
|
||||
|
||||
import docspell.notification.api.NotificationChannel
|
||||
import docspell.store.records._
|
||||
|
||||
import doobie.ConnectionIO
|
||||
|
||||
object ChannelMap {
|
||||
|
||||
def readMail(r: RNotificationChannelMail): ConnectionIO[Vector[NotificationChannel]] =
|
||||
(for {
|
||||
em <- OptionT(RUserEmail.getById(r.connection))
|
||||
rec <- OptionT.fromOption[ConnectionIO](NonEmptyList.fromList(r.recipients))
|
||||
ch = NotificationChannel.Email(em.toMailConfig, em.mailFrom, rec)
|
||||
} yield Vector(ch)).getOrElse(Vector.empty)
|
||||
|
||||
def readGotify(
|
||||
r: RNotificationChannelGotify
|
||||
): ConnectionIO[Vector[NotificationChannel]] =
|
||||
pure(NotificationChannel.Gotify(r.url, r.appKey))
|
||||
|
||||
def readMatrix(
|
||||
r: RNotificationChannelMatrix
|
||||
): ConnectionIO[Vector[NotificationChannel]] =
|
||||
pure(NotificationChannel.Matrix(r.homeServer, r.roomId, r.accessToken, r.messageType))
|
||||
|
||||
def readHttp(
|
||||
r: RNotificationChannelHttp
|
||||
): ConnectionIO[Vector[NotificationChannel]] =
|
||||
pure(NotificationChannel.HttpPost(r.url, Map.empty))
|
||||
|
||||
private def pure[A](a: A): ConnectionIO[Vector[A]] =
|
||||
Sync[ConnectionIO].pure(Vector(a))
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.queries
|
||||
|
||||
import cats.Monad
|
||||
import cats.data.OptionT
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb.Select
|
||||
import docspell.store.records._
|
||||
|
||||
import doobie._
|
||||
|
||||
object QNotification {
|
||||
|
||||
private val hook = RNotificationHook.as("nh")
|
||||
private val hevent = RNotificationHookEvent.as("ne")
|
||||
private val user = RUser.as("u")
|
||||
|
||||
def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] =
|
||||
for {
|
||||
hooks <- listHooks(event.account.collective, event.eventType)
|
||||
chs <- hooks.traverse(readHookChannel)
|
||||
} yield chs
|
||||
|
||||
// --
|
||||
|
||||
final case class HookChannel(
|
||||
hook: RNotificationHook,
|
||||
channels: Vector[NotificationChannel]
|
||||
)
|
||||
|
||||
def listHooks(
|
||||
collective: Ident,
|
||||
eventType: EventType
|
||||
): ConnectionIO[Vector[RNotificationHook]] =
|
||||
run(
|
||||
select(hook.all),
|
||||
from(hook).leftJoin(hevent, hevent.hookId === hook.id),
|
||||
hook.enabled === true && (hook.allEvents === true || hevent.eventType === eventType) && hook.uid
|
||||
.in(
|
||||
Select(select(user.uid), from(user), user.cid === collective)
|
||||
)
|
||||
).query[RNotificationHook].to[Vector]
|
||||
|
||||
def readHookChannel(
|
||||
hook: RNotificationHook
|
||||
): ConnectionIO[HookChannel] =
|
||||
for {
|
||||
c1 <- read(hook.channelMail)(RNotificationChannelMail.getById)(
|
||||
ChannelMap.readMail
|
||||
)
|
||||
c2 <- read(hook.channelGotify)(RNotificationChannelGotify.getById)(
|
||||
ChannelMap.readGotify
|
||||
)
|
||||
c3 <- read(hook.channelMatrix)(RNotificationChannelMatrix.getById)(
|
||||
ChannelMap.readMatrix
|
||||
)
|
||||
c4 <- read(hook.channelHttp)(RNotificationChannelHttp.getById)(ChannelMap.readHttp)
|
||||
} yield HookChannel(hook, c1 ++ c2 ++ c3 ++ c4)
|
||||
|
||||
def readChannel(ch: RNotificationChannel): ConnectionIO[Vector[NotificationChannel]] =
|
||||
ch.fold(
|
||||
ChannelMap.readMail,
|
||||
ChannelMap.readGotify,
|
||||
ChannelMap.readMatrix,
|
||||
ChannelMap.readHttp
|
||||
)
|
||||
|
||||
private def read[A, B](channel: Option[Ident])(
|
||||
load: Ident => ConnectionIO[Option[A]]
|
||||
)(
|
||||
m: A => ConnectionIO[Vector[B]]
|
||||
): ConnectionIO[Vector[B]] =
|
||||
channel match {
|
||||
case Some(ch) =>
|
||||
(for {
|
||||
a <- OptionT(load(ch))
|
||||
ch <- OptionT.liftF(m(a))
|
||||
} yield ch).getOrElse(Vector.empty)
|
||||
case None =>
|
||||
Monad[ConnectionIO].pure(Vector.empty)
|
||||
}
|
||||
}
|
@ -410,6 +410,14 @@ object RItem {
|
||||
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
|
||||
run(select(T.all), from(T), T.id === itemId && T.cid === coll).query[RItem].option
|
||||
|
||||
def findAllByIdAndCollective(
|
||||
itemIds: NonEmptyList[Ident],
|
||||
coll: Ident
|
||||
): ConnectionIO[Vector[RItem]] =
|
||||
run(select(T.all), from(T), T.id.in(itemIds) && T.cid === coll)
|
||||
.query[RItem]
|
||||
.to[Vector]
|
||||
|
||||
def findById(itemId: Ident): ConnectionIO[Option[RItem]] =
|
||||
run(select(T.all), from(T), T.id === itemId).query[RItem].option
|
||||
|
||||
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.OptionT
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api.ChannelRef
|
||||
import docspell.notification.api.ChannelType
|
||||
|
||||
import doobie._
|
||||
|
||||
sealed trait RNotificationChannel {
|
||||
|
||||
def id: Ident
|
||||
|
||||
def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A
|
||||
|
||||
}
|
||||
|
||||
object RNotificationChannel {
|
||||
|
||||
final case class Email(r: RNotificationChannelMail) extends RNotificationChannel {
|
||||
|
||||
override def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f1(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Gotify(r: RNotificationChannelGotify) extends RNotificationChannel {
|
||||
override def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f2(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Matrix(r: RNotificationChannelMatrix) extends RNotificationChannel {
|
||||
override def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f3(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
final case class Http(r: RNotificationChannelHttp) extends RNotificationChannel {
|
||||
override def fold[A](
|
||||
f1: RNotificationChannelMail => A,
|
||||
f2: RNotificationChannelGotify => A,
|
||||
f3: RNotificationChannelMatrix => A,
|
||||
f4: RNotificationChannelHttp => A
|
||||
): A = f4(r)
|
||||
|
||||
val id = r.id
|
||||
}
|
||||
|
||||
def insert(r: RNotificationChannel): ConnectionIO[Int] =
|
||||
r.fold(
|
||||
RNotificationChannelMail.insert,
|
||||
RNotificationChannelGotify.insert,
|
||||
RNotificationChannelMatrix.insert,
|
||||
RNotificationChannelHttp.insert
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannel): ConnectionIO[Int] =
|
||||
r.fold(
|
||||
RNotificationChannelMail.update,
|
||||
RNotificationChannelGotify.update,
|
||||
RNotificationChannelMatrix.update,
|
||||
RNotificationChannelHttp.update
|
||||
)
|
||||
|
||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannel]] =
|
||||
for {
|
||||
mail <- RNotificationChannelMail.getByAccount(account)
|
||||
gotify <- RNotificationChannelGotify.getByAccount(account)
|
||||
matrix <- RNotificationChannelMatrix.getByAccount(account)
|
||||
http <- RNotificationChannelHttp.getByAccount(account)
|
||||
} yield mail.map(Email.apply) ++ gotify.map(Gotify.apply) ++ matrix.map(
|
||||
Matrix.apply
|
||||
) ++ http.map(Http.apply)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Vector[RNotificationChannel]] =
|
||||
for {
|
||||
mail <- RNotificationChannelMail.getById(id)
|
||||
gotify <- RNotificationChannelGotify.getById(id)
|
||||
matrix <- RNotificationChannelMatrix.getById(id)
|
||||
http <- RNotificationChannelHttp.getById(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]] =
|
||||
ref.channelType match {
|
||||
case ChannelType.Mail =>
|
||||
RNotificationChannelMail.getById(ref.id).map(_.map(Email.apply))
|
||||
case ChannelType.Matrix =>
|
||||
RNotificationChannelMatrix.getById(ref.id).map(_.map(Matrix.apply))
|
||||
case ChannelType.Gotify =>
|
||||
RNotificationChannelGotify.getById(ref.id).map(_.map(Gotify.apply))
|
||||
case ChannelType.Http =>
|
||||
RNotificationChannelHttp.getById(ref.id).map(_.map(Http.apply))
|
||||
}
|
||||
|
||||
def getByHook(r: 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 deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] =
|
||||
for {
|
||||
n1 <- RNotificationChannelMail.deleteByAccount(id, account)
|
||||
n2 <- RNotificationChannelGotify.deleteByAccount(id, account)
|
||||
n3 <- RNotificationChannelMatrix.deleteByAccount(id, account)
|
||||
n4 <- RNotificationChannelHttp.deleteByAccount(id, account)
|
||||
} yield n1 + n2 + n3 + n4
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RNotificationChannelGotify(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
url: LenientUri,
|
||||
appKey: Password,
|
||||
created: Timestamp
|
||||
) {
|
||||
def vary: RNotificationChannel =
|
||||
RNotificationChannel.Gotify(this)
|
||||
}
|
||||
|
||||
object RNotificationChannelGotify {
|
||||
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_channel_gotify"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val url = Column[LenientUri]("url", this)
|
||||
val appKey = Column[Password]("app_key", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, url, appKey, 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 insert(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||
DML.insert(T, T.all, sql"${r.id},${r.uid},${r.url},${r.appKey},${r.created}")
|
||||
|
||||
def update(r: RNotificationChannelGotify): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(
|
||||
T.url.setTo(r.url),
|
||||
T.appKey.setTo(r.appKey)
|
||||
)
|
||||
)
|
||||
|
||||
def getByAccount(
|
||||
account: AccountId
|
||||
): ConnectionIO[Vector[RNotificationChannelGotify]] = {
|
||||
val user = RUser.as("u")
|
||||
val gotify = as("c")
|
||||
Select(
|
||||
select(gotify.all),
|
||||
from(gotify).innerJoin(user, user.uid === gotify.uid),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
).build.query[RNotificationChannelGotify].to[Vector]
|
||||
}
|
||||
|
||||
def deleteById(id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
val u = RUser.as("u")
|
||||
DML.delete(
|
||||
T,
|
||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RNotificationChannelHttp(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
url: LenientUri,
|
||||
created: Timestamp
|
||||
) {
|
||||
def vary: RNotificationChannel =
|
||||
RNotificationChannel.Http(this)
|
||||
}
|
||||
|
||||
object RNotificationChannelHttp {
|
||||
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_channel_http"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val url = Column[LenientUri]("url", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, 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 insert(r: RNotificationChannelHttp): ConnectionIO[Int] =
|
||||
DML.insert(T, T.all, sql"${r.id},${r.uid},${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)))
|
||||
|
||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = {
|
||||
val user = RUser.as("u")
|
||||
val http = as("c")
|
||||
Select(
|
||||
select(http.all),
|
||||
from(http).innerJoin(user, user.uid === http.uid),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
).build.query[RNotificationChannelHttp].to[Vector]
|
||||
}
|
||||
|
||||
def deleteById(id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
val u = RUser.as("u")
|
||||
DML.delete(
|
||||
T,
|
||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import emil.MailAddress
|
||||
|
||||
final case class RNotificationChannelMail(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
connection: Ident,
|
||||
recipients: List[MailAddress],
|
||||
created: Timestamp
|
||||
) {
|
||||
def vary: RNotificationChannel =
|
||||
RNotificationChannel.Email(this)
|
||||
}
|
||||
|
||||
object RNotificationChannelMail {
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
|
||||
val tableName = "notification_channel_mail"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", 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)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
|
||||
def insert(r: RNotificationChannelMail): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.connection},${r.recipients},${r.created}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannelMail): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(
|
||||
T.connection.setTo(r.connection),
|
||||
T.recipients.setTo(r.recipients.toList)
|
||||
)
|
||||
)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMail]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMail].option
|
||||
|
||||
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = {
|
||||
val user = RUser.as("u")
|
||||
val gotify = as("c")
|
||||
Select(
|
||||
select(gotify.all),
|
||||
from(gotify).innerJoin(user, user.uid === gotify.uid),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
).build.query[RNotificationChannelMail].to[Vector]
|
||||
}
|
||||
|
||||
def deleteById(id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
val u = RUser.as("u")
|
||||
DML.delete(
|
||||
T,
|
||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RNotificationChannelMatrix(
|
||||
id: Ident,
|
||||
uid: Ident,
|
||||
homeServer: LenientUri,
|
||||
roomId: String,
|
||||
accessToken: Password,
|
||||
messageType: String,
|
||||
created: Timestamp
|
||||
) {
|
||||
def vary: RNotificationChannel =
|
||||
RNotificationChannel.Matrix(this)
|
||||
}
|
||||
|
||||
object RNotificationChannelMatrix {
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_channel_matrix"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val uid = Column[Ident]("uid", this)
|
||||
val homeServer = Column[LenientUri]("home_server", this)
|
||||
val roomId = Column[String]("room_id", this)
|
||||
val accessToken = Column[Password]("access_token", this)
|
||||
val messageType = Column[String]("message_type", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, uid, homeServer, roomId, accessToken, messageType, created)
|
||||
}
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
|
||||
def insert(r: RNotificationChannelMatrix): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.uid},${r.homeServer},${r.roomId},${r.accessToken},${r.messageType},${r.created}"
|
||||
)
|
||||
|
||||
def update(r: RNotificationChannelMatrix): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.id === r.id && T.uid === r.uid,
|
||||
DML.set(
|
||||
T.homeServer.setTo(r.homeServer),
|
||||
T.roomId.setTo(r.roomId),
|
||||
T.accessToken.setTo(r.accessToken),
|
||||
T.messageType.setTo(r.messageType)
|
||||
)
|
||||
)
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RNotificationChannelMatrix]] =
|
||||
run(select(T.all), from(T), T.id === id).query[RNotificationChannelMatrix].option
|
||||
|
||||
def getByAccount(
|
||||
account: AccountId
|
||||
): ConnectionIO[Vector[RNotificationChannelMatrix]] = {
|
||||
val user = RUser.as("u")
|
||||
val gotify = as("c")
|
||||
Select(
|
||||
select(gotify.all),
|
||||
from(gotify).innerJoin(user, user.uid === gotify.uid),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
).build.query[RNotificationChannelMatrix].to[Vector]
|
||||
}
|
||||
|
||||
def deleteById(id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
val u = RUser.as("u")
|
||||
DML.delete(
|
||||
T,
|
||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.jsonminiq.JsonMiniQuery
|
||||
import docspell.notification.api.EventType
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
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)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(
|
||||
id,
|
||||
uid,
|
||||
enabled,
|
||||
channelMail,
|
||||
channelGotify,
|
||||
channelMatrix,
|
||||
channelHttp,
|
||||
allEvents,
|
||||
eventFilter,
|
||||
created
|
||||
)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
|
||||
def insert(r: RNotificationHook): ConnectionIO[Int] =
|
||||
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}"
|
||||
)
|
||||
|
||||
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = {
|
||||
val u = RUser.as("u")
|
||||
DML.delete(
|
||||
T,
|
||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||
)
|
||||
}
|
||||
|
||||
def update(r: RNotificationHook): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
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)
|
||||
)
|
||||
)
|
||||
|
||||
def findByAccount(account: AccountId): ConnectionIO[Vector[RNotificationHook]] =
|
||||
Select(
|
||||
select(T.all),
|
||||
from(T),
|
||||
T.uid.in(Select(select(RUser.T.uid), from(RUser.T), RUser.T.isAccount(account)))
|
||||
).build.query[RNotificationHook].to[Vector]
|
||||
|
||||
def getById(id: Ident, userId: Ident): ConnectionIO[Option[RNotificationHook]] =
|
||||
Select(
|
||||
select(T.all),
|
||||
from(T),
|
||||
T.id === id && T.uid === userId
|
||||
).build.query[RNotificationHook].option
|
||||
|
||||
def findAllByAccount(
|
||||
account: AccountId
|
||||
): ConnectionIO[Vector[(RNotificationHook, List[EventType])]] = {
|
||||
val h = RNotificationHook.as("h")
|
||||
val e = RNotificationHookEvent.as("e")
|
||||
val userSelect =
|
||||
Select(select(RUser.T.uid), from(RUser.T), RUser.T.isAccount(account))
|
||||
|
||||
val withEvents = Select(
|
||||
select(h.all :+ e.eventType),
|
||||
from(h).innerJoin(e, e.hookId === h.id),
|
||||
h.uid.in(userSelect)
|
||||
).orderBy(h.id)
|
||||
.build
|
||||
.query[(RNotificationHook, EventType)]
|
||||
.to[Vector]
|
||||
.map(_.groupBy(_._1).view.mapValues(_.map(_._2).toList).toVector)
|
||||
|
||||
val withoutEvents =
|
||||
Select(
|
||||
select(h.all),
|
||||
from(h),
|
||||
h.id.notIn(Select(select(e.hookId), from(e))) && h.uid.in(userSelect)
|
||||
).build
|
||||
.query[RNotificationHook]
|
||||
.to[Vector]
|
||||
.map(list => list.map(h => (h, Nil: List[EventType])))
|
||||
|
||||
for {
|
||||
sel1 <- withEvents
|
||||
sel2 <- withoutEvents
|
||||
} yield sel1 ++ sel2
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.notification.api.EventType
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
final case class RNotificationHookEvent(
|
||||
id: Ident,
|
||||
hookId: Ident,
|
||||
eventType: EventType
|
||||
)
|
||||
|
||||
object RNotificationHookEvent {
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "notification_hook_event"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val hookId = Column[Ident]("hook_id", this)
|
||||
val eventType = Column[EventType]("event_type", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(
|
||||
id,
|
||||
hookId,
|
||||
eventType
|
||||
)
|
||||
}
|
||||
|
||||
val T: Table = Table(None)
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
|
||||
def insert(r: RNotificationHookEvent): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.hookId},${r.eventType}"
|
||||
)
|
||||
|
||||
def insertAll(hookId: Ident, events: List[EventType]): ConnectionIO[Int] =
|
||||
events
|
||||
.traverse(et =>
|
||||
Ident
|
||||
.randomId[ConnectionIO]
|
||||
.flatMap(id => insert(RNotificationHookEvent(id, hookId, et)))
|
||||
)
|
||||
.map(_.sum)
|
||||
|
||||
def updateAll(hookId: Ident, events: List[EventType]): ConnectionIO[Int] =
|
||||
deleteByHook(hookId) *> insertAll(hookId, events)
|
||||
|
||||
def deleteByHook(hookId: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.hookId === hookId)
|
||||
|
||||
def update(r: RNotificationHookEvent): ConnectionIO[Int] =
|
||||
DML.update(T, T.id === r.id, DML.set(T.eventType.setTo(r.eventType)))
|
||||
}
|
@ -164,6 +164,19 @@ object RPeriodicTask {
|
||||
def as(alias: String): Table =
|
||||
Table(Some(alias))
|
||||
|
||||
def findByTask(taskName: Ident): ConnectionIO[Vector[RPeriodicTask]] =
|
||||
Select(
|
||||
select(T.all),
|
||||
from(T),
|
||||
T.task === taskName
|
||||
).build.query[RPeriodicTask].to[Vector]
|
||||
|
||||
def updateTask(id: Ident, taskName: Ident, args: String): ConnectionIO[Int] =
|
||||
DML.update(T, T.id === id, DML.set(T.task.setTo(taskName), T.args.setTo(args)))
|
||||
|
||||
def setEnabledByTask(taskName: Ident, enabled: Boolean): ConnectionIO[Int] =
|
||||
DML.update(T, T.task === taskName, DML.set(T.enabled.setTo(enabled)))
|
||||
|
||||
def insert(v: RPeriodicTask): ConnectionIO[Int] =
|
||||
DML.insert(
|
||||
T,
|
||||
|
@ -95,11 +95,11 @@ object RTagItem {
|
||||
)
|
||||
} yield n
|
||||
|
||||
def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] =
|
||||
def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Set[Ident]] =
|
||||
for {
|
||||
existing <- findByItem(item)
|
||||
toadd = tags.toSet.diff(existing.map(_.tagId).toSet)
|
||||
n <- setAllTags(item, toadd.toSeq)
|
||||
} yield n
|
||||
_ <- setAllTags(item, toadd.toSeq)
|
||||
} yield toadd
|
||||
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ object RUser {
|
||||
val lastLogin = Column[Timestamp]("lastlogin", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
def isAccount(aid: AccountId) =
|
||||
cid === aid.collective && login === aid.user
|
||||
|
||||
val all =
|
||||
NonEmptyList.of[Column[_]](
|
||||
uid,
|
||||
|
@ -176,6 +176,13 @@ object RUserEmail {
|
||||
run(select(t.all), from(t), t.uid === userId).query[RUserEmail].to[Vector]
|
||||
}
|
||||
|
||||
def getByUser(userId: Ident, name: Ident): ConnectionIO[Option[RUserEmail]] = {
|
||||
val t = Table(None)
|
||||
run(select(t.all), from(t), t.uid === userId && t.name === name)
|
||||
.query[RUserEmail]
|
||||
.option
|
||||
}
|
||||
|
||||
private def findByAccount0(
|
||||
accId: AccountId,
|
||||
nameQ: Option[String],
|
||||
@ -206,6 +213,11 @@ object RUserEmail {
|
||||
def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserEmail]] =
|
||||
findByAccount0(accId, Some(name.id), true).option
|
||||
|
||||
def getById(id: Ident): ConnectionIO[Option[RUserEmail]] = {
|
||||
val t = Table(None)
|
||||
run(select(t.all), from(t), t.id === id).query[RUserEmail].option
|
||||
}
|
||||
|
||||
def delete(accId: AccountId, connName: Ident): ConnectionIO[Int] = {
|
||||
val user = RUser.as("u")
|
||||
|
||||
|
Reference in New Issue
Block a user