Adopt modules to new collective table

This commit is contained in:
eikek
2022-07-13 23:37:46 +02:00
parent 77f22bb5ea
commit 26d7c91266
11 changed files with 113 additions and 64 deletions

View File

@ -26,7 +26,7 @@ trait EventContext {
"account" -> Json.obj( "account" -> Json.obj(
"collective" -> event.account.collective.asJson, "collective" -> event.account.collective.asJson,
"user" -> event.account.login.asJson, "user" -> event.account.login.asJson,
"login" -> event.account.asJson "login" -> event.account.asAccountId.asJson
), ),
"content" -> content "content" -> content
) )

View File

@ -70,7 +70,7 @@ object BasicData {
def find( def find(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
account: AccountId, account: AccountInfo,
now: Timestamp now: Timestamp
): ConnectionIO[Vector[Item]] = { ): ConnectionIO[Vector[Item]] = {
import ItemQueryDsl._ import ItemQueryDsl._

View File

@ -46,7 +46,7 @@ object DeleteFieldValueCtx {
for { for {
now <- OptionT.liftF(Timestamp.current[ConnectionIO]) now <- OptionT.liftF(Timestamp.current[ConnectionIO])
items <- OptionT.liftF(Item.find(ev.items, ev.account, now)) items <- OptionT.liftF(Item.find(ev.items, ev.account, now))
field <- OptionT(RCustomField.findById(ev.field, ev.account.collective)) field <- OptionT(RCustomField.findById(ev.field, ev.account.collectiveId))
msg = DeleteFieldValueCtx( msg = DeleteFieldValueCtx(
ev, ev,
Data( Data(
@ -71,7 +71,7 @@ object DeleteFieldValueCtx {
) )
final case class Data( final case class Data(
account: AccountId, account: AccountInfo,
items: List[Item], items: List[Item],
field: Field, field: Field,
itemUrl: Option[String] itemUrl: Option[String]

View File

@ -61,7 +61,7 @@ object ItemSelectionCtx {
items.toList, items.toList,
ev.itemUrl, ev.itemUrl,
ev.more, ev.more,
ev.account.user.id ev.account.login.id
) )
) )
} yield msg } yield msg
@ -73,12 +73,12 @@ object ItemSelectionCtx {
items <- ev.items.traverse(Item.sample[F]) items <- ev.items.traverse(Item.sample[F])
} yield ItemSelectionCtx( } yield ItemSelectionCtx(
ev, ev,
Data(ev.account, items.toList, ev.itemUrl, ev.more, ev.account.user.id) Data(ev.account, items.toList, ev.itemUrl, ev.more, ev.account.login.id)
) )
) )
final case class Data( final case class Data(
account: AccountId, account: AccountInfo,
items: List[Item], items: List[Item],
itemUrl: Option[String], itemUrl: Option[String],
more: Boolean, more: Boolean,
@ -89,7 +89,7 @@ object ItemSelectionCtx {
io.circe.generic.semiauto.deriveEncoder io.circe.generic.semiauto.deriveEncoder
def create( def create(
account: AccountId, account: AccountInfo,
items: Vector[ListItem], items: Vector[ListItem],
baseUrl: Option[LenientUri], baseUrl: Option[LenientUri],
more: Boolean, more: Boolean,
@ -100,7 +100,7 @@ object ItemSelectionCtx {
items.map(Item(now)).toList, items.map(Item(now)).toList,
baseUrl.map(_.asString), baseUrl.map(_.asString),
more, more,
account.user.id account.login.id
) )
} }

View File

@ -44,7 +44,7 @@ object SetFieldValueCtx {
for { for {
now <- OptionT.liftF(Timestamp.current[ConnectionIO]) now <- OptionT.liftF(Timestamp.current[ConnectionIO])
items <- OptionT.liftF(Item.find(ev.items, ev.account, now)) items <- OptionT.liftF(Item.find(ev.items, ev.account, now))
field <- OptionT(RCustomField.findById(ev.field, ev.account.collective)) field <- OptionT(RCustomField.findById(ev.field, ev.account.collectiveId))
msg = SetFieldValueCtx( msg = SetFieldValueCtx(
ev, ev,
Data( Data(
@ -70,7 +70,7 @@ object SetFieldValueCtx {
) )
final case class Data( final case class Data(
account: AccountId, account: AccountInfo,
items: List[Item], items: List[Item],
field: Field, field: Field,
value: String, value: String,

View File

@ -39,8 +39,8 @@ object TagsChangedCtx {
def apply: Factory = def apply: Factory =
EventContext.factory(ev => EventContext.factory(ev =>
for { for {
tagsAdded <- RTag.findAllByNameOrId(ev.added, ev.account.collective) tagsAdded <- RTag.findAllByNameOrId(ev.added, ev.account.collectiveId)
tagsRemov <- RTag.findAllByNameOrId(ev.removed, ev.account.collective) tagsRemov <- RTag.findAllByNameOrId(ev.removed, ev.account.collectiveId)
now <- Timestamp.current[ConnectionIO] now <- Timestamp.current[ConnectionIO]
items <- Item.find(ev.items, ev.account, now) items <- Item.find(ev.items, ev.account, now)
msg = TagsChangedCtx( msg = TagsChangedCtx(
@ -69,7 +69,7 @@ object TagsChangedCtx {
) )
final case class Data( final case class Data(
account: AccountId, account: AccountInfo,
items: List[Item], items: List[Item],
added: List[Tag], added: List[Tag],
removed: List[Tag], removed: List[Tag],

View File

@ -0,0 +1,19 @@
package docspell.scheduler
import cats.Applicative
import docspell.common.AccountInfo
/** Strategy to find the user that submitted the job. This is used to emit events about
* starting/finishing jobs.
*
* If an account cannot be determined, no events can be send.
*/
trait FindJobOwner[F[_]] {
def apply(job: Job[_]): F[Option[AccountInfo]]
}
object FindJobOwner {
def none[F[_]: Applicative]: FindJobOwner[F] =
(_: Job[_]) => Applicative[F].pure(None)
}

View File

@ -17,7 +17,8 @@ import docspell.store.Store
case class JobStoreModuleBuilder[F[_]: Async]( case class JobStoreModuleBuilder[F[_]: Async](
store: Store[F], store: Store[F],
pubsub: PubSubT[F], pubsub: PubSubT[F],
eventSink: EventSink[F] eventSink: EventSink[F],
findJobOwner: FindJobOwner[F]
) { ) {
def withPubsub(ps: PubSubT[F]): JobStoreModuleBuilder[F] = def withPubsub(ps: PubSubT[F]): JobStoreModuleBuilder[F] =
copy(pubsub = ps) copy(pubsub = ps)
@ -25,8 +26,11 @@ case class JobStoreModuleBuilder[F[_]: Async](
def withEventSink(es: EventSink[F]): JobStoreModuleBuilder[F] = def withEventSink(es: EventSink[F]): JobStoreModuleBuilder[F] =
copy(eventSink = es) copy(eventSink = es)
def withFindJobOwner(f: FindJobOwner[F]): JobStoreModuleBuilder[F] =
copy(findJobOwner = f)
def build: JobStoreModuleBuilder.Module[F] = { def build: JobStoreModuleBuilder.Module[F] = {
val jobStore = JobStorePublish(store, pubsub, eventSink) val jobStore = JobStorePublish(store, pubsub, eventSink, findJobOwner)
val periodicTaskStore = PeriodicTaskStore(store, jobStore) val periodicTaskStore = PeriodicTaskStore(store, jobStore)
val userTaskStore = UserTaskStoreImpl(store, periodicTaskStore) val userTaskStore = UserTaskStoreImpl(store, periodicTaskStore)
new JobStoreModuleBuilder.Module( new JobStoreModuleBuilder.Module(
@ -35,7 +39,8 @@ case class JobStoreModuleBuilder[F[_]: Async](
jobStore, jobStore,
store, store,
eventSink, eventSink,
pubsub pubsub,
findJobOwner
) )
} }
} }
@ -43,7 +48,12 @@ case class JobStoreModuleBuilder[F[_]: Async](
object JobStoreModuleBuilder { object JobStoreModuleBuilder {
def apply[F[_]: Async](store: Store[F]): JobStoreModuleBuilder[F] = def apply[F[_]: Async](store: Store[F]): JobStoreModuleBuilder[F] =
JobStoreModuleBuilder(store, PubSubT.noop[F], EventSink.silent[F]) JobStoreModuleBuilder(
store,
PubSubT.noop[F],
EventSink.silent[F],
FindJobOwner.none[F]
)
final class Module[F[_]]( final class Module[F[_]](
val userTasks: UserTaskStore[F], val userTasks: UserTaskStore[F],
@ -51,6 +61,7 @@ object JobStoreModuleBuilder {
val jobs: JobStore[F], val jobs: JobStore[F],
val store: Store[F], val store: Store[F],
val eventSink: EventSink[F], val eventSink: EventSink[F],
val pubSubT: PubSubT[F] val pubSubT: PubSubT[F],
val findJobOwner: FindJobOwner[F]
) extends JobStoreModule[F] {} ) extends JobStoreModule[F] {}
} }

View File

@ -6,9 +6,9 @@
package docspell.scheduler.impl package docspell.scheduler.impl
import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.common.{Ident, JobState} import docspell.common.{Ident, JobState}
import docspell.notification.api.{Event, EventSink} import docspell.notification.api.{Event, EventSink}
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
@ -19,14 +19,18 @@ import docspell.store.Store
final class JobStorePublish[F[_]: Sync]( final class JobStorePublish[F[_]: Sync](
delegate: JobStore[F], delegate: JobStore[F],
pubsub: PubSubT[F], pubsub: PubSubT[F],
eventSink: EventSink[F] eventSink: EventSink[F],
findJobOwner: FindJobOwner[F]
) extends JobStore[F] { ) extends JobStore[F] {
private def msg(job: Job[String]): JobSubmitted = private def msg(job: Job[String]): JobSubmitted =
JobSubmitted(job.id, job.group, job.task, job.args) JobSubmitted(job.id, job.group, job.task, job.args)
private def event(job: Job[String]): Event.JobSubmitted = private def event(job: Job[String]): OptionT[F, Event.JobSubmitted] =
OptionT(findJobOwner(job))
.map(
Event.JobSubmitted( Event.JobSubmitted(
_,
job.id, job.id,
job.group, job.group,
job.task, job.task,
@ -35,10 +39,11 @@ final class JobStorePublish[F[_]: Sync](
job.subject, job.subject,
job.submitter job.submitter
) )
)
private def publish(job: Job[String]): F[Unit] = private def publish(job: Job[String]): F[Unit] =
pubsub.publish1(JobSubmitted.topic, msg(job)).as(()) *> pubsub.publish1(JobSubmitted.topic, msg(job)).as(()) *>
eventSink.offer(event(job)) event(job).semiflatMap(eventSink.offer).value.void
private def notifyJoex: F[Unit] = private def notifyJoex: F[Unit] =
pubsub.publish1IgnoreErrors(JobsNotify(), ()).void pubsub.publish1IgnoreErrors(JobsNotify(), ()).void
@ -82,7 +87,8 @@ object JobStorePublish {
def apply[F[_]: Async]( def apply[F[_]: Async](
store: Store[F], store: Store[F],
pubSub: PubSubT[F], pubSub: PubSubT[F],
eventSink: EventSink[F] eventSink: EventSink[F],
findJobOwner: FindJobOwner[F]
): JobStore[F] = ): JobStore[F] =
new JobStorePublish[F](JobStoreImpl(store), pubSub, eventSink) new JobStorePublish[F](JobStoreImpl(store), pubSub, eventSink, findJobOwner)
} }

View File

@ -23,7 +23,8 @@ case class SchedulerBuilder[F[_]: Async](
queue: JobQueue[F], queue: JobQueue[F],
logSink: LogSink[F], logSink: LogSink[F],
pubSub: PubSubT[F], pubSub: PubSubT[F],
eventSink: EventSink[F] eventSink: EventSink[F],
findJobOwner: FindJobOwner[F]
) { ) {
def withConfig(cfg: SchedulerConfig): SchedulerBuilder[F] = def withConfig(cfg: SchedulerConfig): SchedulerBuilder[F] =
@ -32,7 +33,7 @@ case class SchedulerBuilder[F[_]: Async](
def withTaskRegistry(reg: JobTaskRegistry[F]): SchedulerBuilder[F] = def withTaskRegistry(reg: JobTaskRegistry[F]): SchedulerBuilder[F] =
copy(tasks = reg) copy(tasks = reg)
def withTask[A](task: JobTask[F]): SchedulerBuilder[F] = def withTask(task: JobTask[F]): SchedulerBuilder[F] =
withTaskRegistry(tasks.withTask(task)) withTaskRegistry(tasks.withTask(task))
def withLogSink(sink: LogSink[F]): SchedulerBuilder[F] = def withLogSink(sink: LogSink[F]): SchedulerBuilder[F] =
@ -47,6 +48,9 @@ case class SchedulerBuilder[F[_]: Async](
def withEventSink(sink: EventSink[F]): SchedulerBuilder[F] = def withEventSink(sink: EventSink[F]): SchedulerBuilder[F] =
copy(eventSink = sink) copy(eventSink = sink)
def withFindJobOwner(f: FindJobOwner[F]): SchedulerBuilder[F] =
copy(findJobOwner = f)
def serve: Resource[F, Scheduler[F]] = def serve: Resource[F, Scheduler[F]] =
resource.evalMap(sch => Async[F].start(sch.start.compile.drain).map(_ => sch)) resource.evalMap(sch => Async[F].start(sch.start.compile.drain).map(_ => sch))
@ -60,6 +64,7 @@ case class SchedulerBuilder[F[_]: Async](
queue, queue,
pubSub, pubSub,
eventSink, eventSink,
findJobOwner,
tasks, tasks,
store, store,
logSink, logSink,
@ -86,6 +91,7 @@ object SchedulerBuilder {
JobQueue(store), JobQueue(store),
LogSink.db[F](store), LogSink.db[F](store),
PubSubT.noop[F], PubSubT.noop[F],
EventSink.silent[F] EventSink.silent[F],
FindJobOwner.none[F]
) )
} }

View File

@ -30,6 +30,7 @@ final class SchedulerImpl[F[_]: Async](
queue: JobQueue[F], queue: JobQueue[F],
pubSub: PubSubT[F], pubSub: PubSubT[F],
eventSink: EventSink[F], eventSink: EventSink[F],
findJobOwner: FindJobOwner[F],
tasks: JobTaskRegistry[F], tasks: JobTaskRegistry[F],
store: Store[F], store: Store[F],
logSink: LogSink[F], logSink: LogSink[F],
@ -68,8 +69,9 @@ final class SchedulerImpl[F[_]: Async](
def getRunning: F[Vector[Job[String]]] = def getRunning: F[Vector[Job[String]]] =
state.get state.get
.flatMap(s => QJob.findAll(s.getRunning, store)) .flatMap(s => QJob.findAll(s.getRunning, store))
.map( .map(_.map(convertJob))
_.map(rj =>
private def convertJob(rj: RJob): Job[String] =
Job( Job(
rj.id, rj.id,
rj.task, rj.task,
@ -80,8 +82,6 @@ final class SchedulerImpl[F[_]: Async](
rj.priority, rj.priority,
rj.tracker rj.tracker
) )
)
)
def requestCancel(jobId: Ident): F[Boolean] = def requestCancel(jobId: Ident): F[Boolean] =
logger.info(s"Scheduler requested to cancel job: ${jobId.id}") *> logger.info(s"Scheduler requested to cancel job: ${jobId.id}") *>
@ -235,8 +235,17 @@ final class SchedulerImpl[F[_]: Async](
) )
) )
_ <- Sync[F].whenA(JobState.isDone(finishState))( _ <- Sync[F].whenA(JobState.isDone(finishState))(
eventSink.offer( makeJobDoneEvent(job, result)
Event.JobDone( .semiflatMap(eventSink.offer)
.value
)
} yield ()
private def makeJobDoneEvent(job: RJob, result: JobTaskResult) =
for {
acc <- OptionT(findJobOwner(convertJob(job)))
ev = Event.JobDone(
acc,
job.id, job.id,
job.group, job.group,
job.task, job.task,
@ -247,9 +256,7 @@ final class SchedulerImpl[F[_]: Async](
result.json.getOrElse(Json.Null), result.json.getOrElse(Json.Null),
result.message result.message
) )
) } yield ev
)
} yield ()
def onStart(job: RJob): F[Unit] = def onStart(job: RJob): F[Unit] =
QJob.setRunning( QJob.setRunning(