mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Update channel names when retrieving user tasks
Usertasks hold their arguments in an opaque json structure that is not connected to the other data at the db level. When loading tasks that holds references (like channels) they could have changed in the meantime. This is now a hacky way around that updates the channels when loading. Should they be deleted, the tasks fails when running.
This commit is contained in:
@ -81,7 +81,7 @@ object BackendApp {
|
|||||||
itemSearchImpl <- OItemSearch(store)
|
itemSearchImpl <- OItemSearch(store)
|
||||||
fulltextImpl <- OFulltext(itemSearchImpl, ftsClient, store, queue, joexImpl)
|
fulltextImpl <- OFulltext(itemSearchImpl, ftsClient, store, queue, joexImpl)
|
||||||
mailImpl <- OMail(store, javaEmil)
|
mailImpl <- OMail(store, javaEmil)
|
||||||
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
|
userTaskImpl <- OUserTask(utStore, store, queue, joexImpl)
|
||||||
folderImpl <- OFolder(store)
|
folderImpl <- OFolder(store)
|
||||||
customFieldsImpl <- OCustomFields(store)
|
customFieldsImpl <- OCustomFields(store)
|
||||||
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
||||||
|
@ -6,15 +6,16 @@
|
|||||||
|
|
||||||
package docspell.backend.ops
|
package docspell.backend.ops
|
||||||
|
|
||||||
import cats.data.OptionT
|
import cats.data.{NonEmptyList, OptionT}
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import fs2.Stream
|
import fs2.Stream
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.PeriodicDueItemsArgs
|
import docspell.notification.api.{ChannelRef, PeriodicDueItemsArgs, PeriodicQueryArgs}
|
||||||
import docspell.notification.api.PeriodicQueryArgs
|
import docspell.store.Store
|
||||||
import docspell.store.queue.JobQueue
|
import docspell.store.queue.JobQueue
|
||||||
|
import docspell.store.records.RNotificationChannel
|
||||||
import docspell.store.usertask._
|
import docspell.store.usertask._
|
||||||
|
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
@ -83,7 +84,8 @@ trait OUserTask[F[_]] {
|
|||||||
object OUserTask {
|
object OUserTask {
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
store: UserTaskStore[F],
|
taskStore: UserTaskStore[F],
|
||||||
|
store: Store[F],
|
||||||
queue: JobQueue[F],
|
queue: JobQueue[F],
|
||||||
joex: OJoex[F]
|
joex: OJoex[F]
|
||||||
): Resource[F, OUserTask[F]] =
|
): Resource[F, OUserTask[F]] =
|
||||||
@ -100,7 +102,7 @@ object OUserTask {
|
|||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def getScanMailbox(scope: UserTaskScope): Stream[F, UserTask[ScanMailboxArgs]] =
|
def getScanMailbox(scope: UserTaskScope): Stream[F, UserTask[ScanMailboxArgs]] =
|
||||||
store
|
taskStore
|
||||||
.getByName[ScanMailboxArgs](scope, ScanMailboxArgs.taskName)
|
.getByName[ScanMailboxArgs](scope, ScanMailboxArgs.taskName)
|
||||||
|
|
||||||
def findScanMailbox(
|
def findScanMailbox(
|
||||||
@ -111,8 +113,8 @@ object OUserTask {
|
|||||||
|
|
||||||
def deleteTask(scope: UserTaskScope, id: Ident): F[Unit] =
|
def deleteTask(scope: UserTaskScope, id: Ident): F[Unit] =
|
||||||
(for {
|
(for {
|
||||||
_ <- store.getByIdRaw(scope, id)
|
_ <- taskStore.getByIdRaw(scope, id)
|
||||||
_ <- OptionT.liftF(store.deleteTask(scope, id))
|
_ <- OptionT.liftF(taskStore.deleteTask(scope, id))
|
||||||
} yield ()).getOrElse(())
|
} yield ()).getOrElse(())
|
||||||
|
|
||||||
def submitScanMailbox(
|
def submitScanMailbox(
|
||||||
@ -121,21 +123,28 @@ object OUserTask {
|
|||||||
task: UserTask[ScanMailboxArgs]
|
task: UserTask[ScanMailboxArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateTask[ScanMailboxArgs](scope, subject, task)
|
_ <- taskStore.updateTask[ScanMailboxArgs](scope, subject, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def getNotifyDueItems(
|
def getNotifyDueItems(
|
||||||
scope: UserTaskScope
|
scope: UserTaskScope
|
||||||
): Stream[F, UserTask[PeriodicDueItemsArgs]] =
|
): Stream[F, UserTask[PeriodicDueItemsArgs]] =
|
||||||
store
|
taskStore
|
||||||
.getByName[PeriodicDueItemsArgs](scope, PeriodicDueItemsArgs.taskName)
|
.getByName[PeriodicDueItemsArgs](scope, PeriodicDueItemsArgs.taskName)
|
||||||
|
.evalMap(ut =>
|
||||||
|
resolveChannels(ut.args.channels)
|
||||||
|
.map(chs => ut.mapArgs(_.copy(channels = chs)))
|
||||||
|
)
|
||||||
|
|
||||||
def findNotifyDueItems(
|
def findNotifyDueItems(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
scope: UserTaskScope
|
scope: UserTaskScope
|
||||||
): OptionT[F, UserTask[PeriodicDueItemsArgs]] =
|
): OptionT[F, UserTask[PeriodicDueItemsArgs]] =
|
||||||
OptionT(getNotifyDueItems(scope).find(_.id == id).compile.last)
|
OptionT(getNotifyDueItems(scope).find(_.id == id).compile.last)
|
||||||
|
.semiflatMap(ut =>
|
||||||
|
resolveChannels(ut.args.channels).map(ch => ut.mapArgs(_.copy(channels = ch)))
|
||||||
|
)
|
||||||
|
|
||||||
def submitNotifyDueItems(
|
def submitNotifyDueItems(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
@ -143,18 +152,26 @@ object OUserTask {
|
|||||||
task: UserTask[PeriodicDueItemsArgs]
|
task: UserTask[PeriodicDueItemsArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateTask[PeriodicDueItemsArgs](scope, subject, task)
|
_ <- taskStore.updateTask[PeriodicDueItemsArgs](scope, subject, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def getPeriodicQuery(scope: UserTaskScope): Stream[F, UserTask[PeriodicQueryArgs]] =
|
def getPeriodicQuery(scope: UserTaskScope): Stream[F, UserTask[PeriodicQueryArgs]] =
|
||||||
store.getByName[PeriodicQueryArgs](scope, PeriodicQueryArgs.taskName)
|
taskStore
|
||||||
|
.getByName[PeriodicQueryArgs](scope, PeriodicQueryArgs.taskName)
|
||||||
|
.evalMap(ut =>
|
||||||
|
resolveChannels(ut.args.channels)
|
||||||
|
.map(chs => ut.mapArgs(_.copy(channels = chs)))
|
||||||
|
)
|
||||||
|
|
||||||
def findPeriodicQuery(
|
def findPeriodicQuery(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
scope: UserTaskScope
|
scope: UserTaskScope
|
||||||
): OptionT[F, UserTask[PeriodicQueryArgs]] =
|
): OptionT[F, UserTask[PeriodicQueryArgs]] =
|
||||||
OptionT(getPeriodicQuery(scope).find(_.id == id).compile.last)
|
OptionT(getPeriodicQuery(scope).find(_.id == id).compile.last)
|
||||||
|
.semiflatMap(ut =>
|
||||||
|
resolveChannels(ut.args.channels).map(ch => ut.mapArgs(_.copy(channels = ch)))
|
||||||
|
)
|
||||||
|
|
||||||
def submitPeriodicQuery(
|
def submitPeriodicQuery(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
@ -162,9 +179,18 @@ object OUserTask {
|
|||||||
task: UserTask[PeriodicQueryArgs]
|
task: UserTask[PeriodicQueryArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateTask[PeriodicQueryArgs](scope, subject, task)
|
_ <- taskStore.updateTask[PeriodicQueryArgs](scope, subject, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
})
|
|
||||||
|
|
||||||
|
// When retrieving arguments containing channel references, we must update
|
||||||
|
// details because they could have changed in the db. There are no separate
|
||||||
|
// database models for each user task, so rather a hacky compromise
|
||||||
|
private def resolveChannels(
|
||||||
|
refs: NonEmptyList[ChannelRef]
|
||||||
|
): F[NonEmptyList[ChannelRef]] =
|
||||||
|
store.transact(RNotificationChannel.resolveRefs(refs)).map { resolved =>
|
||||||
|
NonEmptyList.fromList(resolved).getOrElse(refs)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package docspell.notification.api
|
package docspell.notification.api
|
||||||
|
|
||||||
|
import cats.Order
|
||||||
import cats.data.{NonEmptyList => Nel}
|
import cats.data.{NonEmptyList => Nel}
|
||||||
|
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
@ -43,4 +44,7 @@ object ChannelType {
|
|||||||
Decoder.decodeString.emap(fromString)
|
Decoder.decodeString.emap(fromString)
|
||||||
implicit val jsonEncoder: Encoder[ChannelType] =
|
implicit val jsonEncoder: Encoder[ChannelType] =
|
||||||
Encoder.encodeString.contramap(_.name)
|
Encoder.encodeString.contramap(_.name)
|
||||||
|
|
||||||
|
implicit val order: Order[ChannelType] =
|
||||||
|
Order.by(_.name)
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,9 @@ trait DSL extends DoobieMeta {
|
|||||||
def union(s1: Select, sn: Select*): Select =
|
def union(s1: Select, sn: Select*): Select =
|
||||||
Select.Union(s1, sn.toVector)
|
Select.Union(s1, sn.toVector)
|
||||||
|
|
||||||
|
def union(selects: Nel[Select]): Select =
|
||||||
|
Select.Union(selects.head, selects.tail.toVector)
|
||||||
|
|
||||||
def intersect(s1: Select, sn: Select*): Select =
|
def intersect(s1: Select, sn: Select*): Select =
|
||||||
Select.Intersect(s1, sn.toVector)
|
Select.Intersect(s1, sn.toVector)
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
|
|
||||||
package docspell.store.records
|
package docspell.store.records
|
||||||
|
|
||||||
import cats.data.OptionT
|
import cats.data.{NonEmptyList => Nel, OptionT}
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.notification.api.{Channel, ChannelRef, ChannelType}
|
import docspell.notification.api.{Channel, ChannelRef, ChannelType}
|
||||||
|
import docspell.store.qb.DSL._
|
||||||
|
|
||||||
import doobie._
|
import doobie._
|
||||||
|
|
||||||
@ -130,6 +131,22 @@ object RNotificationChannel {
|
|||||||
RNotificationChannelHttp.getById(userId)(ref.id).map(_.map(Http.apply))
|
RNotificationChannelHttp.getById(userId)(ref.id).map(_.map(Http.apply))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def resolveRefs(refs: Nel[ChannelRef]): ConnectionIO[List[ChannelRef]] = {
|
||||||
|
val byType = refs.groupByNem(_.channelType)
|
||||||
|
val queries = byType.toNel
|
||||||
|
.map {
|
||||||
|
case (ChannelType.Mail, refs) =>
|
||||||
|
RNotificationChannelMail.findRefs(refs.map(_.id))
|
||||||
|
case (ChannelType.Matrix, refs) =>
|
||||||
|
RNotificationChannelMatrix.findRefs(refs.map(_.id))
|
||||||
|
case (ChannelType.Gotify, refs) =>
|
||||||
|
RNotificationChannelGotify.findRefs(refs.map(_.id))
|
||||||
|
case (ChannelType.Http, refs) =>
|
||||||
|
RNotificationChannelHttp.findRefs(refs.map(_.id))
|
||||||
|
}
|
||||||
|
union(queries).build.query[ChannelRef].to[List]
|
||||||
|
}
|
||||||
|
|
||||||
def getByHook(hook: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
def getByHook(hook: RNotificationHook): ConnectionIO[Vector[RNotificationChannel]] = {
|
||||||
def opt(id: Option[Ident]): OptionT[ConnectionIO, Ident] =
|
def opt(id: Option[Ident]): OptionT[ConnectionIO, Ident] =
|
||||||
OptionT.fromOption(id)
|
OptionT.fromOption(id)
|
||||||
|
@ -9,6 +9,7 @@ package docspell.store.records
|
|||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.notification.api.ChannelType
|
||||||
import docspell.store.qb.DSL._
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
@ -97,4 +98,11 @@ object RNotificationChannelGotify {
|
|||||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findRefs(ids: NonEmptyList[Ident]): Select =
|
||||||
|
Select(
|
||||||
|
select(T.id.s, const(ChannelType.Gotify.name), T.name.s),
|
||||||
|
from(T),
|
||||||
|
T.id.in(ids)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package docspell.store.records
|
|||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.notification.api.ChannelType
|
||||||
import docspell.store.qb.DSL._
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
@ -80,4 +81,11 @@ object RNotificationChannelHttp {
|
|||||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findRefs(ids: NonEmptyList[Ident]): Select =
|
||||||
|
Select(
|
||||||
|
select(T.id.s, const(ChannelType.Http.name), T.name.s),
|
||||||
|
from(T),
|
||||||
|
T.id.in(ids)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package docspell.store.records
|
|||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.notification.api.ChannelType
|
||||||
import docspell.store.qb.DSL._
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
@ -90,4 +91,11 @@ object RNotificationChannelMail {
|
|||||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findRefs(ids: NonEmptyList[Ident]): Select =
|
||||||
|
Select(
|
||||||
|
select(T.id.s, const(ChannelType.Mail.name), T.name.s),
|
||||||
|
from(T),
|
||||||
|
T.id.in(ids)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package docspell.store.records
|
|||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
|
import docspell.notification.api.ChannelType
|
||||||
import docspell.store.qb.DSL._
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
@ -106,4 +107,11 @@ object RNotificationChannelMatrix {
|
|||||||
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findRefs(ids: NonEmptyList[Ident]): Select =
|
||||||
|
Select(
|
||||||
|
select(T.id.s, const(ChannelType.Matrix.name), T.name.s),
|
||||||
|
from(T),
|
||||||
|
T.id.in(ids)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,11 @@ case class UserTask[A](
|
|||||||
def encode(implicit E: Encoder[A]): UserTask[String] =
|
def encode(implicit E: Encoder[A]): UserTask[String] =
|
||||||
copy(args = E(args).noSpaces)
|
copy(args = E(args).noSpaces)
|
||||||
|
|
||||||
|
def withArgs[B](newArgs: B): UserTask[B] =
|
||||||
|
copy(args = newArgs)
|
||||||
|
|
||||||
|
def mapArgs[B](f: A => B): UserTask[B] =
|
||||||
|
withArgs(f(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
object UserTask {
|
object UserTask {
|
||||||
|
Reference in New Issue
Block a user