diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OUserTask.scala b/modules/backend/src/main/scala/docspell/backend/ops/OUserTask.scala index 21b916ab..3582240d 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OUserTask.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OUserTask.scala @@ -3,7 +3,6 @@ package docspell.backend.ops import cats.implicits._ import cats.effect._ import cats.data.OptionT -import com.github.eikek.calev.CalEvent import io.circe.Encoder import fs2.Stream @@ -30,12 +29,16 @@ trait OUserTask[F[_]] { task: UserTask[ScanMailboxArgs] ): F[Unit] - /** Return the settings for the notify-due-items task of the current - * user. There is at most one such task per user. If no task has - * been created/submitted a new one with default values is - * returned. + /** Return the settings for all the notify-due-items task of the + * current user. */ - def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] + def getNotifyDueItems(account: AccountId): Stream[F, UserTask[NotifyDueItemsArgs]] + + /** Find a notify-due-items task by the given id. */ + def findNotifyDueItems( + id: Ident, + account: AccountId + ): OptionT[F, UserTask[NotifyDueItemsArgs]] /** Updates the notify-due-items tasks and notifies the joex nodes. */ @@ -100,62 +103,24 @@ object OUserTask { _ <- joex.notifyAllNodes } yield () - def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] = + def getNotifyDueItems(account: AccountId): Stream[F, UserTask[NotifyDueItemsArgs]] = store - .getOneByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName) - .getOrElseF(notifyDueItemsDefault(account)) + .getByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName) + + def findNotifyDueItems( + id: Ident, + account: AccountId + ): OptionT[F, UserTask[NotifyDueItemsArgs]] = + OptionT(getNotifyDueItems(account).find(_.id == id).compile.last) def submitNotifyDueItems( account: AccountId, task: UserTask[NotifyDueItemsArgs] ): F[Unit] = for { - _ <- store.updateOneTask[NotifyDueItemsArgs](account, task) + _ <- store.updateTask[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(""), - Nil, - None, - 5, - None, - Nil, - Nil - ) - ) - - // private def scanMailboxDefault( - // account: AccountId - // ): F[UserTask[ScanMailboxArgs]] = - // for { - // id <- Ident.randomId[F] - // } yield UserTask( - // id, - // ScanMailboxArgs.taskName, - // false, - // CalEvent.unsafe("*-*-* 0,12:00"), - // ScanMailboxArgs( - // account, - // Ident.unsafe(""), - // Nil, - // Some(Duration.hours(12)), - // None, - // false, - // None - // ) - // ) }) } diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 4c3fb7f0..b9aff661 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -2008,7 +2008,8 @@ paths: description: | Return the current notification settings of the authenticated user. Users can be notified on due items via e-mail. This is - done by periodically querying items. + done by periodically querying items. It is possible to have + multiple tasks. security: - authTokenHeader: [] responses: @@ -2017,13 +2018,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/NotificationSettings" + $ref: "#/components/schemas/NotificationSettingsList" post: tags: [ User Tasks ] - summary: Change current settings for "Notify Due Items" task + summary: Create settings for "Notify Due Items" task description: | - Change the current notification settings of the authenticated - user. + Create a new notification settings task of the authenticated + user. The id field in the input is ignored. security: - authTokenHeader: [] requestBody: @@ -2038,6 +2039,58 @@ paths: application/json: schema: $ref: "#/components/schemas/BasicResult" + put: + tags: [ User Tasks ] + summary: Change settings for "Notify Due Items" task + description: | + Change the settings for a notify-due-items task. The task is + looked up by its id. + security: + - authTokenHeader: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/NotificationSettings" + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" + /sec/usertask/notifydueitems/{id}: + parameters: + - $ref: "#/components/parameters/id" + get: + tags: [ User Tasks ] + description: | + Return the current settings for a single notify-due-items task + of the authenticated user. + security: + - authTokenHeader: [] + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/NotificationSettings" + delete: + tags: [ User Tasks ] + description: | + Delete the settings to a notify-due-items task of the + authenticated user. + security: + - authTokenHeader: [] + responses: + 200: + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/BasicResult" + /sec/usertask/notifydueitems/startonce: post: tags: [ User Tasks ] @@ -2100,7 +2153,7 @@ paths: $ref: "#/components/schemas/BasicResult" put: tags: [ User Tasks ] - summary: Change current settings for "Scan Mailbox" task + summary: Change settings for a "Scan Mailbox" task description: | Change the settings for a scan-mailbox task. The task is looked up by its id. @@ -2312,6 +2365,16 @@ components: properties: event: type: string + NotificationSettingsList: + description: | + A list of notification settings. + required: + - items + properties: + items: + type: array + items: + $ref: "#/components/schemas/NotificationSettings" NotificationSettings: description: | Settings for notifying about due items. diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/NotifyDueItemsRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/NotifyDueItemsRoutes.scala index 88c7a67f..6e9b9cde 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/NotifyDueItemsRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/NotifyDueItemsRoutes.scala @@ -2,6 +2,7 @@ package docspell.restserver.routes import cats.effect._ import cats.implicits._ +import cats.data.OptionT import org.http4s._ import org.http4s.dsl.Http4sDsl import org.http4s.circe.CirceEntityEncoder._ @@ -27,10 +28,18 @@ object NotifyDueItemsRoutes { import dsl._ HttpRoutes.of { + case GET -> Root / Ident(id) => + (for { + task <- ut.findNotifyDueItems(id, user.account) + res <- OptionT.liftF(taskToSettings(user.account, backend, task)) + resp <- OptionT.liftF(Ok(res)) + } yield resp).getOrElseF(NotFound()) + case req @ POST -> Root / "startonce" => for { - data <- req.as[NotificationSettings] - task = makeTask(cfg, user.account, data) + data <- req.as[NotificationSettings] + newId <- Ident.randomId[F] + task <- makeTask(newId, cfg, user.account, data) res <- ut.executeNow(user.account, task) .attempt @@ -38,46 +47,77 @@ object NotifyDueItemsRoutes { resp <- Ok(res) } yield resp - case GET -> Root => + case DELETE -> Root / Ident(id) => for { - task <- ut.getNotifyDueItems(user.account) - res <- taskToSettings(user.account, backend, task) + res <- + ut.deleteTask(user.account, id) + .attempt + .map(Conversions.basicResult(_, "Deleted successfully")) resp <- Ok(res) } yield resp + case req @ PUT -> Root => + def run(data: NotificationSettings) = + for { + task <- makeTask(data.id, cfg, user.account, data) + res <- + ut.submitNotifyDueItems(user.account, task) + .attempt + .map(Conversions.basicResult(_, "Saved successfully")) + resp <- Ok(res) + } yield resp + for { + data <- req.as[NotificationSettings] + resp <- + if (data.id.isEmpty) Ok(BasicResult(false, "Empty id is not allowed")) + else run(data) + } yield resp + case req @ POST -> Root => for { - data <- req.as[NotificationSettings] - task = makeTask(cfg, user.account, data) + data <- req.as[NotificationSettings] + newId <- Ident.randomId[F] + task <- makeTask(newId, cfg, user.account, data) res <- ut.submitNotifyDueItems(user.account, task) .attempt .map(Conversions.basicResult(_, "Saved successfully.")) resp <- Ok(res) } yield resp + + case GET -> Root => + ut.getNotifyDueItems(user.account) + .evalMap(task => taskToSettings(user.account, backend, task)) + .compile + .toVector + .map(v => NotificationSettingsList(v.toList)) + .flatMap(Ok(_)) } } - def makeTask( + def makeTask[F[_]: Sync]( + id: Ident, cfg: Config, user: AccountId, settings: NotificationSettings - ): UserTask[NotifyDueItemsArgs] = - UserTask( - settings.id, - NotifyDueItemsArgs.taskName, - settings.enabled, - settings.schedule, - NotifyDueItemsArgs( - user, - settings.smtpConnection, - settings.recipients, - Some(cfg.baseUrl / "app" / "item"), - settings.remindDays, - if (settings.capOverdue) Some(settings.remindDays) - else None, - settings.tagsInclude.map(_.id), - settings.tagsExclude.map(_.id) + ): F[UserTask[NotifyDueItemsArgs]] = + Sync[F].pure( + UserTask( + id, + NotifyDueItemsArgs.taskName, + settings.enabled, + settings.schedule, + NotifyDueItemsArgs( + user, + settings.smtpConnection, + settings.recipients, + Some(cfg.baseUrl / "app" / "item"), + settings.remindDays, + if (settings.capOverdue) Some(settings.remindDays) + else None, + settings.tagsInclude.map(_.id), + settings.tagsExclude.map(_.id) + ) ) )