mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-02 13:32:51 +00:00
Server-side stub impl for notify-due-items
This commit is contained in:
parent
e97e0db45c
commit
ad772c0c25
@ -7,6 +7,7 @@ import docspell.backend.signup.OSignup
|
||||
import docspell.store.Store
|
||||
import docspell.store.ops.ONode
|
||||
import docspell.store.queue.JobQueue
|
||||
import docspell.store.usertask.UserTaskStore
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
import emil.javamail.JavaMailEmil
|
||||
@ -26,6 +27,7 @@ trait BackendApp[F[_]] {
|
||||
def item: OItem[F]
|
||||
def mail: OMail[F]
|
||||
def joex: OJoex[F]
|
||||
def userTask: OUserTask[F]
|
||||
}
|
||||
|
||||
object BackendApp {
|
||||
@ -37,20 +39,22 @@ object BackendApp {
|
||||
blocker: Blocker
|
||||
): Resource[F, BackendApp[F]] =
|
||||
for {
|
||||
queue <- JobQueue(store)
|
||||
loginImpl <- Login[F](store)
|
||||
signupImpl <- OSignup[F](store)
|
||||
collImpl <- OCollective[F](store)
|
||||
sourceImpl <- OSource[F](store)
|
||||
tagImpl <- OTag[F](store)
|
||||
equipImpl <- OEquipment[F](store)
|
||||
orgImpl <- OOrganization(store)
|
||||
joexImpl <- OJoex.create(httpClientEc, store)
|
||||
uploadImpl <- OUpload(store, queue, cfg, joexImpl)
|
||||
nodeImpl <- ONode(store)
|
||||
jobImpl <- OJob(store, joexImpl)
|
||||
itemImpl <- OItem(store)
|
||||
mailImpl <- OMail(store, JavaMailEmil(blocker))
|
||||
utStore <- UserTaskStore(store)
|
||||
queue <- JobQueue(store)
|
||||
loginImpl <- Login[F](store)
|
||||
signupImpl <- OSignup[F](store)
|
||||
collImpl <- OCollective[F](store)
|
||||
sourceImpl <- OSource[F](store)
|
||||
tagImpl <- OTag[F](store)
|
||||
equipImpl <- OEquipment[F](store)
|
||||
orgImpl <- OOrganization(store)
|
||||
joexImpl <- OJoex.create(httpClientEc, store)
|
||||
uploadImpl <- OUpload(store, queue, cfg, joexImpl)
|
||||
nodeImpl <- ONode(store)
|
||||
jobImpl <- OJob(store, joexImpl)
|
||||
itemImpl <- OItem(store)
|
||||
mailImpl <- OMail(store, JavaMailEmil(blocker))
|
||||
userTaskImpl <- OUserTask(utStore, joexImpl)
|
||||
} yield new BackendApp[F] {
|
||||
val login: Login[F] = loginImpl
|
||||
val signup: OSignup[F] = signupImpl
|
||||
@ -65,6 +69,7 @@ object BackendApp {
|
||||
val item = itemImpl
|
||||
val mail = mailImpl
|
||||
val joex = joexImpl
|
||||
val userTask = userTaskImpl
|
||||
}
|
||||
|
||||
def apply[F[_]: ConcurrentEffect: ContextShift](
|
||||
|
@ -0,0 +1,60 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import cats.implicits._
|
||||
import cats.effect._
|
||||
import docspell.store.usertask._
|
||||
import docspell.common._
|
||||
import com.github.eikek.calev.CalEvent
|
||||
|
||||
trait OUserTask[F[_]] {
|
||||
|
||||
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
|
||||
|
||||
def submitNotifyDueItems(
|
||||
account: AccountId,
|
||||
task: UserTask[NotifyDueItemsArgs]
|
||||
): F[Unit]
|
||||
|
||||
}
|
||||
|
||||
object OUserTask {
|
||||
|
||||
def apply[F[_]: Effect](store: UserTaskStore[F], joex: OJoex[F]): Resource[F, OUserTask[F]] =
|
||||
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
|
||||
|
||||
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] =
|
||||
store
|
||||
.getOneByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName)
|
||||
.getOrElseF(notifyDueItemsDefault(account))
|
||||
|
||||
def submitNotifyDueItems(
|
||||
account: AccountId,
|
||||
task: UserTask[NotifyDueItemsArgs]
|
||||
): F[Unit] =
|
||||
for {
|
||||
_ <- store.updateOneTask[NotifyDueItemsArgs](account, task)
|
||||
_ <- joex.notifyAllNodes
|
||||
} yield ()
|
||||
|
||||
private def notifyDueItemsDefault(
|
||||
account: AccountId
|
||||
): F[UserTask[NotifyDueItemsArgs]] =
|
||||
for {
|
||||
id <- Ident.randomId[F]
|
||||
} yield UserTask(
|
||||
id,
|
||||
NotifyDueItemsArgs.taskName,
|
||||
false,
|
||||
CalEvent.unsafe("*-*-1/7 12:00"),
|
||||
NotifyDueItemsArgs(
|
||||
account,
|
||||
Ident.unsafe("none"),
|
||||
Nil,
|
||||
5,
|
||||
Nil,
|
||||
Nil
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package docspell.common
|
||||
|
||||
import io.circe._
|
||||
|
||||
case class AccountId(collective: Ident, user: Ident) {
|
||||
|
||||
def asString =
|
||||
@ -32,4 +34,9 @@ object AccountId {
|
||||
|
||||
separated.orElse(Ident.fromString(str).map(id => AccountId(id, id)))
|
||||
}
|
||||
|
||||
implicit val jsonDecoder: Decoder[AccountId] =
|
||||
Decoder.decodeString.emap(parse)
|
||||
implicit val jsonEncoder: Encoder[AccountId] =
|
||||
Encoder.encodeString.contramap(_.asString)
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package docspell.common
|
||||
|
||||
import io.circe._, io.circe.generic.semiauto._
|
||||
import docspell.common.syntax.all._
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
case class NotifyDueItemsArgs(
|
||||
account: AccountId,
|
||||
smtpConnection: Ident,
|
||||
recipients: List[String],
|
||||
remindDays: Int,
|
||||
tagsInclude: List[Ident],
|
||||
tagsExclude: List[Ident]
|
||||
) {}
|
||||
|
||||
object NotifyDueItemsArgs {
|
||||
|
||||
val taskName = Ident.unsafe("notify-due-items")
|
||||
|
||||
implicit val jsonEncoder: Encoder[NotifyDueItemsArgs] =
|
||||
deriveEncoder[NotifyDueItemsArgs]
|
||||
implicit val jsonDecoder: Decoder[NotifyDueItemsArgs] =
|
||||
deriveDecoder[NotifyDueItemsArgs]
|
||||
|
||||
def parse(str: String): Either[Throwable, NotifyDueItemsArgs] =
|
||||
str.parseJsonAs[NotifyDueItemsArgs]
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ package docspell.joex
|
||||
|
||||
import cats.implicits._
|
||||
import cats.effect._
|
||||
import docspell.common.{Ident, NodeType, ProcessItemArgs}
|
||||
import docspell.common._
|
||||
import docspell.joex.hk._
|
||||
import docspell.joex.process.ItemHandler
|
||||
import docspell.joex.scheduler._
|
||||
@ -75,6 +75,13 @@ object JoexAppImpl {
|
||||
ItemHandler.onCancel[F]
|
||||
)
|
||||
)
|
||||
.withTask(
|
||||
JobTask.json(
|
||||
NotifyDueItemsArgs.taskName,
|
||||
NotifyDueItemsTask[F],
|
||||
NotifyDueItemsTask.onCancel[F]
|
||||
)
|
||||
)
|
||||
.withTask(
|
||||
JobTask.json(
|
||||
HouseKeepingTask.taskName,
|
||||
|
@ -17,12 +17,12 @@ object HouseKeepingTask {
|
||||
|
||||
def apply[F[_]: Sync](cfg: Config): Task[F, Unit, Unit] =
|
||||
Task
|
||||
.log[F](_.info(s"Running house-keeping task now"))
|
||||
.log[F, Unit](_.info(s"Running house-keeping task now"))
|
||||
.flatMap(_ => CleanupInvitesTask(cfg.houseKeeping.cleanupInvites))
|
||||
.flatMap(_ => CleanupJobsTask(cfg.houseKeeping.cleanupJobs))
|
||||
|
||||
def onCancel[F[_]: Sync]: Task[F, Unit, Unit] =
|
||||
Task.log(_.warn("Cancelling house-keeping task"))
|
||||
Task.log[F, Unit](_.warn("Cancelling house-keeping task"))
|
||||
|
||||
def periodicTask[F[_]: Sync](ce: CalEvent): F[RPeriodicTask] =
|
||||
RPeriodicTask
|
||||
|
@ -0,0 +1,23 @@
|
||||
package docspell.joex.hk
|
||||
|
||||
import cats.implicits._
|
||||
import cats.effect._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.joex.scheduler.Task
|
||||
|
||||
object NotifyDueItemsTask {
|
||||
|
||||
def apply[F[_]: Sync](): Task[F, NotifyDueItemsArgs, Unit] =
|
||||
Task { ctx =>
|
||||
for {
|
||||
now <- Timestamp.current[F]
|
||||
_ <- ctx.logger.info(s" $now")
|
||||
_ <- ctx.logger.info(s"Removed $ctx")
|
||||
} yield ()
|
||||
}
|
||||
|
||||
def onCancel[F[_]: Sync]: Task[F, NotifyDueItemsArgs, Unit] =
|
||||
Task.log(_.warn("Cancelling notify-due-items task"))
|
||||
|
||||
}
|
@ -55,6 +55,6 @@ object Task {
|
||||
def setProgress[F[_]: Sync, A, B](n: Int)(data: B): Task[F, A, B] =
|
||||
Task(_.setProgress(n).map(_ => data))
|
||||
|
||||
def log[F[_]](f: Logger[F] => F[Unit]): Task[F, Unit, Unit] =
|
||||
def log[F[_], A](f: Logger[F] => F[Unit]): Task[F, A, Unit] =
|
||||
Task(ctx => f(ctx.logger))
|
||||
}
|
||||
|
@ -1560,10 +1560,10 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/notification:
|
||||
/sec/usertask/notifydueitems:
|
||||
get:
|
||||
tags: [ Notification ]
|
||||
summary: Get current notification settings
|
||||
summary: Get settings for "Notify Due Items" task
|
||||
description: |
|
||||
Return the current notification settings of the authenticated
|
||||
user. Users can be notified on due items via e-mail. This is
|
||||
@ -1579,7 +1579,7 @@ paths:
|
||||
$ref: "#/components/schemas/NotificationData"
|
||||
post:
|
||||
tags: [ Notification ]
|
||||
summary: Change current notification settings
|
||||
summary: Change current settings for "Notify Due Items" task
|
||||
description: |
|
||||
Change the current notification settings of the authenticated
|
||||
user.
|
||||
@ -1607,6 +1607,7 @@ components:
|
||||
- id
|
||||
- enabled
|
||||
- smtpConnection
|
||||
- recipients
|
||||
- schedule
|
||||
- remindDays
|
||||
- tagsInclude
|
||||
@ -1620,6 +1621,11 @@ components:
|
||||
smtpConnection:
|
||||
type: string
|
||||
format: ident
|
||||
recipients:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: ident
|
||||
schedule:
|
||||
type: string
|
||||
format: calevent
|
||||
|
@ -60,22 +60,23 @@ object RestServer {
|
||||
token: AuthToken
|
||||
): HttpRoutes[F] =
|
||||
Router(
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg),
|
||||
"tag" -> TagRoutes(restApp.backend, token),
|
||||
"equipment" -> EquipmentRoutes(restApp.backend, token),
|
||||
"organization" -> OrganizationRoutes(restApp.backend, token),
|
||||
"person" -> PersonRoutes(restApp.backend, token),
|
||||
"source" -> SourceRoutes(restApp.backend, token),
|
||||
"user" -> UserRoutes(restApp.backend, token),
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
"item" -> ItemRoutes(restApp.backend, token),
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
||||
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
||||
"email/send" -> MailSendRoutes(restApp.backend, token),
|
||||
"email/settings" -> MailSettingsRoutes(restApp.backend, token),
|
||||
"email/sent" -> SentMailRoutes(restApp.backend, token)
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg),
|
||||
"tag" -> TagRoutes(restApp.backend, token),
|
||||
"equipment" -> EquipmentRoutes(restApp.backend, token),
|
||||
"organization" -> OrganizationRoutes(restApp.backend, token),
|
||||
"person" -> PersonRoutes(restApp.backend, token),
|
||||
"source" -> SourceRoutes(restApp.backend, token),
|
||||
"user" -> UserRoutes(restApp.backend, token),
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
"item" -> ItemRoutes(restApp.backend, token),
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
||||
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
||||
"email/send" -> MailSendRoutes(restApp.backend, token),
|
||||
"email/settings" -> MailSettingsRoutes(restApp.backend, token),
|
||||
"email/sent" -> SentMailRoutes(restApp.backend, token),
|
||||
"usertask/notifydueitems" -> NotifyDueItemsRoutes(restApp.backend, token)
|
||||
)
|
||||
|
||||
def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
|
||||
|
@ -472,6 +472,12 @@ trait Conversions {
|
||||
case PassChangeResult.UserNotFound => BasicResult(false, "User not found.")
|
||||
}
|
||||
|
||||
def basicResult(e: Either[Throwable, _], successMsg: String): BasicResult =
|
||||
e match {
|
||||
case Right(_) => BasicResult(true, successMsg)
|
||||
case Left(ex) => BasicResult(false, ex.getMessage)
|
||||
}
|
||||
|
||||
// MIME Type
|
||||
|
||||
def fromContentType(header: `Content-Type`): MimeType =
|
||||
|
@ -0,0 +1,77 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
import org.http4s._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
import docspell.store.usertask._
|
||||
import docspell.restserver.conv.Conversions
|
||||
|
||||
object NotifyDueItemsRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
val ut = backend.userTask
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root =>
|
||||
for {
|
||||
task <- ut.getNotifyDueItems(user.account)
|
||||
resp <- Ok(convert(task))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root =>
|
||||
for {
|
||||
data <- req.as[NotificationSettings]
|
||||
task = makeTask(user.account, data)
|
||||
res <- ut
|
||||
.submitNotifyDueItems(user.account, task)
|
||||
.attempt
|
||||
.map(Conversions.basicResult(_, "Update ok."))
|
||||
resp <- Ok(res)
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
def convert(task: UserTask[NotifyDueItemsArgs]): NotificationData =
|
||||
NotificationData(taskToSettings(task), None, None)
|
||||
|
||||
def makeTask(
|
||||
user: AccountId,
|
||||
settings: NotificationSettings
|
||||
): UserTask[NotifyDueItemsArgs] =
|
||||
UserTask(
|
||||
settings.id,
|
||||
NotifyDueItemsArgs.taskName,
|
||||
settings.enabled,
|
||||
settings.schedule,
|
||||
NotifyDueItemsArgs(
|
||||
user,
|
||||
settings.smtpConnection,
|
||||
settings.recipients,
|
||||
settings.remindDays,
|
||||
settings.tagsInclude.map(Ident.unsafe),
|
||||
settings.tagsExclude.map(Ident.unsafe)
|
||||
)
|
||||
)
|
||||
|
||||
def taskToSettings(task: UserTask[NotifyDueItemsArgs]): NotificationSettings =
|
||||
NotificationSettings(
|
||||
task.id,
|
||||
task.enabled,
|
||||
task.args.smtpConnection,
|
||||
task.args.recipients,
|
||||
task.timer,
|
||||
task.args.remindDays,
|
||||
task.args.tagsInclude.map(_.id),
|
||||
task.args.tagsExclude.map(_.id)
|
||||
)
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package docspell.store.queries
|
||||
|
||||
import fs2._
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Implicits._
|
||||
import docspell.store.records._
|
||||
import docspell.store.usertask.UserTask
|
||||
import doobie._
|
||||
|
||||
object QUserTask {
|
||||
private val cols = RPeriodicTask.Columns
|
||||
|
||||
def findAll(account: AccountId): Stream[ConnectionIO, UserTask[String]] =
|
||||
selectSimple(
|
||||
RPeriodicTask.Columns.all,
|
||||
RPeriodicTask.table,
|
||||
and(cols.group.is(account.collective), cols.submitter.is(account.user))
|
||||
).query[RPeriodicTask].stream.map(makeUserTask)
|
||||
|
||||
def findByName(
|
||||
account: AccountId,
|
||||
name: Ident
|
||||
): Stream[ConnectionIO, UserTask[String]] =
|
||||
selectSimple(
|
||||
RPeriodicTask.Columns.all,
|
||||
RPeriodicTask.table,
|
||||
and(
|
||||
cols.group.is(account.collective),
|
||||
cols.submitter.is(account.user),
|
||||
cols.task.is(name)
|
||||
)
|
||||
).query[RPeriodicTask].stream.map(makeUserTask)
|
||||
|
||||
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
||||
for {
|
||||
r <- makePeriodicTask(account, task)
|
||||
n <- RPeriodicTask.insert(r)
|
||||
} yield n
|
||||
|
||||
def update(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
||||
for {
|
||||
r <- makePeriodicTask(account, task)
|
||||
n <- RPeriodicTask.update(r)
|
||||
} yield n
|
||||
|
||||
def exists(id: Ident): ConnectionIO[Boolean] =
|
||||
RPeriodicTask.exists(id)
|
||||
|
||||
def delete(account: AccountId, id: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(
|
||||
RPeriodicTask.table,
|
||||
and(
|
||||
cols.group.is(account.collective),
|
||||
cols.submitter.is(account.user),
|
||||
cols.id.is(id)
|
||||
)
|
||||
).update.run
|
||||
|
||||
def deleteAll(account: AccountId, name: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(
|
||||
RPeriodicTask.table,
|
||||
and(
|
||||
cols.group.is(account.collective),
|
||||
cols.submitter.is(account.user),
|
||||
cols.task.is(name)
|
||||
)
|
||||
).update.run
|
||||
|
||||
def makeUserTask(r: RPeriodicTask): UserTask[String] =
|
||||
UserTask(r.id, r.task, r.enabled, r.timer, r.args)
|
||||
|
||||
def makePeriodicTask(
|
||||
account: AccountId,
|
||||
t: UserTask[String]
|
||||
): ConnectionIO[RPeriodicTask] =
|
||||
RPeriodicTask
|
||||
.create[ConnectionIO](
|
||||
t.enabled,
|
||||
t.name,
|
||||
account.collective,
|
||||
t.args,
|
||||
s"${account.user.id}: ${t.name.id}",
|
||||
account.user,
|
||||
Priority.Low,
|
||||
t.timer
|
||||
)
|
||||
.map(r => r.copy(id = t.id))
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package docspell.store.usertask
|
||||
|
||||
import com.github.eikek.calev.CalEvent
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
|
||||
case class UserTask[A](
|
||||
id: Ident,
|
||||
name: Ident,
|
||||
enabled: Boolean,
|
||||
timer: CalEvent,
|
||||
args: A
|
||||
) {
|
||||
|
||||
def encode(implicit E: Encoder[A]): UserTask[String] =
|
||||
copy(args = E(args).noSpaces)
|
||||
}
|
||||
|
||||
object UserTask {
|
||||
|
||||
|
||||
implicit final class UserTaskCodec(ut: UserTask[String]) {
|
||||
|
||||
def decode[A](implicit D: Decoder[A]): Either[String, UserTask[A]] =
|
||||
ut.args.parseJsonAs[A]
|
||||
.left.map(_.getMessage)
|
||||
.map(a => ut.copy(args = a))
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package docspell.store.usertask
|
||||
|
||||
import fs2._
|
||||
import cats.implicits._
|
||||
import cats.effect._
|
||||
import cats.data.OptionT
|
||||
import _root_.io.circe._
|
||||
import docspell.common._
|
||||
import docspell.store.{AddResult, Store}
|
||||
import docspell.store.queries.QUserTask
|
||||
|
||||
/** User tasks are `RPeriodicTask`s that can be managed by the user.
|
||||
* The user can change arguments, enable/disable it or run it just
|
||||
* once.
|
||||
*
|
||||
* This class defines methods at a higher level, dealing with
|
||||
* `UserTask` and `AccountId` instead of directly using
|
||||
* `RPeriodicTask`. A user task is associated to a specific user (not
|
||||
* just the collective).
|
||||
*
|
||||
* @implNote: The mapping is as follows: The collective is the task
|
||||
* group. The submitter property contains the username. Once a task
|
||||
* is saved to the database, it can only be refernced uniquely by its
|
||||
* id. A user may submit multiple same tasks (with different
|
||||
* properties).
|
||||
*/
|
||||
trait UserTaskStore[F[_]] {
|
||||
|
||||
/** Return all tasks of the given user.
|
||||
*/
|
||||
def getAll(account: AccountId): Stream[F, UserTask[String]]
|
||||
|
||||
/** Return all tasks of the given name and user. The task's arguments
|
||||
* are returned as stored in the database.
|
||||
*/
|
||||
def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]]
|
||||
|
||||
/** Return all tasks of the given name and user. The task's arguments
|
||||
* are decoded using the given json decoder.
|
||||
*/
|
||||
def getByName[A](account: AccountId, name: Ident)(
|
||||
implicit D: Decoder[A]
|
||||
): Stream[F, UserTask[A]]
|
||||
|
||||
/** Updates or inserts the given task.
|
||||
*
|
||||
* The task is identified by its id. If no task with this id
|
||||
* exists, a new one is created. Otherwise the existing task is
|
||||
* updated. The job executors are notified if a task has been
|
||||
* enabled.
|
||||
*/
|
||||
def updateTask[A](account: AccountId, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
|
||||
|
||||
/** Delete the task with the given id of the given user.
|
||||
*/
|
||||
def deleteTask(account: AccountId, id: Ident): F[Int]
|
||||
|
||||
/** Return the task of the given user and name. If multiple exists, an
|
||||
* error is returned. The task's arguments are returned as stored
|
||||
* in the database.
|
||||
*/
|
||||
def getOneByNameRaw(account: AccountId, name: Ident): OptionT[F, UserTask[String]]
|
||||
|
||||
/** Return the task of the given user and name. If multiple exists, an
|
||||
* error is returned. The task's arguments are decoded using the
|
||||
* given json decoder.
|
||||
*/
|
||||
def getOneByName[A](account: AccountId, name: Ident)(
|
||||
implicit D: Decoder[A]
|
||||
): OptionT[F, UserTask[A]]
|
||||
|
||||
/** Updates or inserts the given task.
|
||||
*
|
||||
* Unlike `updateTask`, this ensures that there is at most one task
|
||||
* of some name in the db. Multiple same tasks (task with same
|
||||
* name) may not be allowed to run, dependening on what they do.
|
||||
* This is not ensured by the database, though.
|
||||
*
|
||||
* If there are currently mutliple tasks with same name as `ut` for
|
||||
* the user `account`, they will all be removed and the given task
|
||||
* inserted!
|
||||
*/
|
||||
def updateOneTask[A](account: AccountId, ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[UserTask[String]]
|
||||
|
||||
/** Delete all tasks of the given user that have name `name'.
|
||||
*/
|
||||
def deleteAll(account: AccountId, name: Ident): F[Int]
|
||||
}
|
||||
|
||||
object UserTaskStore {
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, UserTaskStore[F]] =
|
||||
Resource.pure[F, UserTaskStore[F]](new UserTaskStore[F] {
|
||||
|
||||
def getAll(account: AccountId): Stream[F, UserTask[String]] =
|
||||
store.transact(QUserTask.findAll(account))
|
||||
|
||||
def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]] =
|
||||
store.transact(QUserTask.findByName(account, name))
|
||||
|
||||
def getByName[A](account: AccountId, name: Ident)(
|
||||
implicit D: Decoder[A]
|
||||
): Stream[F, UserTask[A]] =
|
||||
getByNameRaw(account, name).flatMap(_.decode match {
|
||||
case Right(ua) => Stream.emit(ua)
|
||||
case Left(err) => Stream.raiseError[F](new Exception(err))
|
||||
})
|
||||
|
||||
def updateTask[A](account: AccountId, ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[Int] = {
|
||||
val exists = QUserTask.exists(ut.id)
|
||||
val insert = QUserTask.insert(account, ut.encode)
|
||||
store.add(insert, exists).flatMap {
|
||||
case AddResult.Success =>
|
||||
1.pure[F]
|
||||
case AddResult.EntityExists(_) =>
|
||||
store.transact(QUserTask.update(account, ut.encode))
|
||||
case AddResult.Failure(ex) =>
|
||||
Effect[F].raiseError(ex)
|
||||
}
|
||||
}
|
||||
|
||||
def deleteTask(account: AccountId, id: Ident): F[Int] =
|
||||
store.transact(QUserTask.delete(account, id))
|
||||
|
||||
def getOneByNameRaw(
|
||||
account: AccountId,
|
||||
name: Ident
|
||||
): OptionT[F, UserTask[String]] =
|
||||
OptionT(
|
||||
getByNameRaw(account, name)
|
||||
.take(2)
|
||||
.compile
|
||||
.toList
|
||||
.flatMap {
|
||||
case Nil => (None: Option[UserTask[String]]).pure[F]
|
||||
case ut :: Nil => ut.some.pure[F]
|
||||
case _ => Effect[F].raiseError(new Exception("More than one result found"))
|
||||
}
|
||||
)
|
||||
|
||||
def getOneByName[A](account: AccountId, name: Ident)(
|
||||
implicit D: Decoder[A]
|
||||
): OptionT[F, UserTask[A]] =
|
||||
getOneByNameRaw(account, name)
|
||||
.semiflatMap(_.decode match {
|
||||
case Right(ua) => ua.pure[F]
|
||||
case Left(err) => Effect[F].raiseError(new Exception(err))
|
||||
})
|
||||
|
||||
def updateOneTask[A](account: AccountId, ut: UserTask[A])(
|
||||
implicit E: Encoder[A]
|
||||
): F[UserTask[String]] =
|
||||
getByNameRaw(account, ut.name).compile.toList.flatMap {
|
||||
case a :: rest =>
|
||||
val task = ut.copy(id = a.id).encode
|
||||
for {
|
||||
_ <- store.transact(QUserTask.update(account, task))
|
||||
_ <- store.transact(rest.traverse(t => QUserTask.delete(account, t.id)))
|
||||
} yield task
|
||||
case Nil =>
|
||||
val task = ut.encode
|
||||
store.transact(QUserTask.insert(account, task)).map(_ => task)
|
||||
}
|
||||
|
||||
def deleteAll(account: AccountId, name: Ident): F[Int] =
|
||||
store.transact(QUserTask.deleteAll(account, name))
|
||||
})
|
||||
|
||||
}
|
@ -88,6 +88,9 @@ object Dependencies {
|
||||
)
|
||||
) ++ jclOverSlf4j
|
||||
|
||||
val emilCommon = Seq(
|
||||
"com.github.eikek" %% "emil-common" % EmilVersion,
|
||||
)
|
||||
val emil = Seq(
|
||||
"com.github.eikek" %% "emil-common" % EmilVersion,
|
||||
"com.github.eikek" %% "emil-javamail" % EmilVersion
|
||||
|
Loading…
x
Reference in New Issue
Block a user