mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Allow to start a user task once
This commit is contained in:
parent
af5b62c057
commit
bbfd694b45
@ -54,7 +54,7 @@ object BackendApp {
|
|||||||
jobImpl <- OJob(store, joexImpl)
|
jobImpl <- OJob(store, joexImpl)
|
||||||
itemImpl <- OItem(store)
|
itemImpl <- OItem(store)
|
||||||
mailImpl <- OMail(store, JavaMailEmil(blocker))
|
mailImpl <- OMail(store, JavaMailEmil(blocker))
|
||||||
userTaskImpl <- OUserTask(utStore, joexImpl)
|
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
|
||||||
} yield new BackendApp[F] {
|
} yield new BackendApp[F] {
|
||||||
val login: Login[F] = loginImpl
|
val login: Login[F] = loginImpl
|
||||||
val signup: OSignup[F] = signupImpl
|
val signup: OSignup[F] = signupImpl
|
||||||
|
@ -2,26 +2,54 @@ package docspell.backend.ops
|
|||||||
|
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
import com.github.eikek.calev.CalEvent
|
||||||
|
import io.circe.Encoder
|
||||||
|
import docspell.store.queue.JobQueue
|
||||||
import docspell.store.usertask._
|
import docspell.store.usertask._
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import com.github.eikek.calev.CalEvent
|
|
||||||
|
|
||||||
trait OUserTask[F[_]] {
|
trait OUserTask[F[_]] {
|
||||||
|
|
||||||
|
/** Return the settings for the notify-due-items task of the current
|
||||||
|
* user. There is at most one such task per user.
|
||||||
|
*/
|
||||||
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
|
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
|
||||||
|
|
||||||
|
/** Updates the notify-due-items tasks and notifies the joex nodes.
|
||||||
|
*/
|
||||||
def submitNotifyDueItems(
|
def submitNotifyDueItems(
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
task: UserTask[NotifyDueItemsArgs]
|
task: UserTask[NotifyDueItemsArgs]
|
||||||
): F[Unit]
|
): F[Unit]
|
||||||
|
|
||||||
|
/** Discards the schedule and immediately submits the task to the job
|
||||||
|
* executor's queue. It will not update the corresponding periodic
|
||||||
|
* task.
|
||||||
|
*/
|
||||||
|
def executeNow[A](account: AccountId, task: UserTask[A])(
|
||||||
|
implicit E: Encoder[A]
|
||||||
|
): F[Unit]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OUserTask {
|
object OUserTask {
|
||||||
|
|
||||||
def apply[F[_]: Effect](store: UserTaskStore[F], joex: OJoex[F]): Resource[F, OUserTask[F]] =
|
def apply[F[_]: Effect](
|
||||||
|
store: UserTaskStore[F],
|
||||||
|
queue: JobQueue[F],
|
||||||
|
joex: OJoex[F]
|
||||||
|
): Resource[F, OUserTask[F]] =
|
||||||
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
|
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
|
||||||
|
|
||||||
|
def executeNow[A](account: AccountId, task: UserTask[A])(
|
||||||
|
implicit E: Encoder[A]
|
||||||
|
): F[Unit] =
|
||||||
|
for {
|
||||||
|
ptask <- task.encode.toPeriodicTask(account)
|
||||||
|
job <- ptask.toJob
|
||||||
|
_ <- queue.insert(job)
|
||||||
|
_ <- joex.notifyAllNodes
|
||||||
|
} yield ()
|
||||||
|
|
||||||
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] =
|
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]] =
|
||||||
store
|
store
|
||||||
.getOneByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName)
|
.getOneByName[NotifyDueItemsArgs](account, NotifyDueItemsArgs.taskName)
|
||||||
|
@ -1583,7 +1583,7 @@ paths:
|
|||||||
$ref: "#/components/schemas/CalEventCheckResult"
|
$ref: "#/components/schemas/CalEventCheckResult"
|
||||||
/sec/usertask/notifydueitems:
|
/sec/usertask/notifydueitems:
|
||||||
get:
|
get:
|
||||||
tags: [ Notification ]
|
tags: [ User Tasks ]
|
||||||
summary: Get settings for "Notify Due Items" task
|
summary: Get settings for "Notify Due Items" task
|
||||||
description: |
|
description: |
|
||||||
Return the current notification settings of the authenticated
|
Return the current notification settings of the authenticated
|
||||||
@ -1599,7 +1599,7 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/NotificationSettings"
|
$ref: "#/components/schemas/NotificationSettings"
|
||||||
post:
|
post:
|
||||||
tags: [ Notification ]
|
tags: [ User Tasks ]
|
||||||
summary: Change current settings for "Notify Due Items" task
|
summary: Change current settings for "Notify Due Items" task
|
||||||
description: |
|
description: |
|
||||||
Change the current notification settings of the authenticated
|
Change the current notification settings of the authenticated
|
||||||
@ -1618,6 +1618,27 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
/sec/usertask/notifydueitems/startonce:
|
||||||
|
post:
|
||||||
|
tags: [ User Tasks ]
|
||||||
|
summary: Start the "Notify Due Items" task once
|
||||||
|
description: |
|
||||||
|
Starts the notify-due-items task just once, discarding the
|
||||||
|
schedule and not updating the periodic task.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotificationSettings"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
@ -22,6 +22,17 @@ object NotifyDueItemsRoutes {
|
|||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
HttpRoutes.of {
|
HttpRoutes.of {
|
||||||
|
case req @ POST -> Root / "startonce" =>
|
||||||
|
for {
|
||||||
|
data <- req.as[NotificationSettings]
|
||||||
|
task = makeTask(user.account, data)
|
||||||
|
res <- ut
|
||||||
|
.executeNow(user.account, task)
|
||||||
|
.attempt
|
||||||
|
.map(Conversions.basicResult(_, "Submitted successfully."))
|
||||||
|
resp <- Ok(res)
|
||||||
|
} yield resp
|
||||||
|
|
||||||
case GET -> Root =>
|
case GET -> Root =>
|
||||||
for {
|
for {
|
||||||
task <- ut.getNotifyDueItems(user.account)
|
task <- ut.getNotifyDueItems(user.account)
|
||||||
@ -36,7 +47,7 @@ object NotifyDueItemsRoutes {
|
|||||||
res <- ut
|
res <- ut
|
||||||
.submitNotifyDueItems(user.account, task)
|
.submitNotifyDueItems(user.account, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Update ok."))
|
.map(Conversions.basicResult(_, "Saved successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
} yield resp
|
} yield resp
|
||||||
}
|
}
|
||||||
@ -61,7 +72,6 @@ object NotifyDueItemsRoutes {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO this should be inside the backend code and not here
|
|
||||||
def taskToSettings[F[_]: Sync](
|
def taskToSettings[F[_]: Sync](
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
backend: BackendApp[F],
|
backend: BackendApp[F],
|
||||||
|
@ -33,13 +33,13 @@ object QUserTask {
|
|||||||
|
|
||||||
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
r <- makePeriodicTask(account, task)
|
r <- task.toPeriodicTask[ConnectionIO](account)
|
||||||
n <- RPeriodicTask.insert(r)
|
n <- RPeriodicTask.insert(r)
|
||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
def update(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
def update(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
r <- makePeriodicTask(account, task)
|
r <- task.toPeriodicTask[ConnectionIO](account)
|
||||||
n <- RPeriodicTask.update(r)
|
n <- RPeriodicTask.update(r)
|
||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
@ -69,21 +69,4 @@ object QUserTask {
|
|||||||
def makeUserTask(r: RPeriodicTask): UserTask[String] =
|
def makeUserTask(r: RPeriodicTask): UserTask[String] =
|
||||||
UserTask(r.id, r.task, r.enabled, r.timer, r.args)
|
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))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,8 @@ trait PeriodicTaskStore[F[_]] {
|
|||||||
*/
|
*/
|
||||||
def add(task: RPeriodicTask): F[AddResult]
|
def add(task: RPeriodicTask): F[AddResult]
|
||||||
|
|
||||||
|
/** Find all joex nodes as registered in the database.
|
||||||
|
*/
|
||||||
def findJoexNodes: F[Vector[RNode]]
|
def findJoexNodes: F[Vector[RNode]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package docspell.store.usertask
|
package docspell.store.usertask
|
||||||
|
|
||||||
|
import cats.effect._
|
||||||
|
import cats.implicits._
|
||||||
import com.github.eikek.calev.CalEvent
|
import com.github.eikek.calev.CalEvent
|
||||||
import io.circe.Decoder
|
import io.circe.Decoder
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.common.syntax.all._
|
import docspell.common.syntax.all._
|
||||||
|
import docspell.store.records.RPeriodicTask
|
||||||
|
|
||||||
case class UserTask[A](
|
case class UserTask[A](
|
||||||
id: Ident,
|
id: Ident,
|
||||||
@ -16,18 +20,34 @@ 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object UserTask {
|
object UserTask {
|
||||||
|
|
||||||
|
|
||||||
implicit final class UserTaskCodec(ut: UserTask[String]) {
|
implicit final class UserTaskCodec(ut: UserTask[String]) {
|
||||||
|
|
||||||
def decode[A](implicit D: Decoder[A]): Either[String, UserTask[A]] =
|
def decode[A](implicit D: Decoder[A]): Either[String, UserTask[A]] =
|
||||||
ut.args.parseJsonAs[A]
|
ut.args
|
||||||
.left.map(_.getMessage)
|
.parseJsonAs[A]
|
||||||
|
.left
|
||||||
|
.map(_.getMessage)
|
||||||
.map(a => ut.copy(args = a))
|
.map(a => ut.copy(args = a))
|
||||||
|
|
||||||
|
def toPeriodicTask[F[_]: Sync](
|
||||||
|
account: AccountId
|
||||||
|
): F[RPeriodicTask] =
|
||||||
|
RPeriodicTask
|
||||||
|
.create[F](
|
||||||
|
ut.enabled,
|
||||||
|
ut.name,
|
||||||
|
account.collective,
|
||||||
|
ut.args,
|
||||||
|
s"${account.user.id}: ${ut.name.id}",
|
||||||
|
account.user,
|
||||||
|
Priority.Low,
|
||||||
|
ut.timer
|
||||||
|
)
|
||||||
|
.map(r => r.copy(id = ut.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ module Api exposing
|
|||||||
, setItemNotes
|
, setItemNotes
|
||||||
, setTags
|
, setTags
|
||||||
, setUnconfirmed
|
, setUnconfirmed
|
||||||
|
, startOnceNotifyDueItems
|
||||||
, submitNotifyDueItems
|
, submitNotifyDueItems
|
||||||
, upload
|
, upload
|
||||||
, uploadSingle
|
, uploadSingle
|
||||||
@ -123,6 +124,20 @@ import Util.Http as Http2
|
|||||||
--- NotifyDueItems
|
--- NotifyDueItems
|
||||||
|
|
||||||
|
|
||||||
|
startOnceNotifyDueItems :
|
||||||
|
Flags
|
||||||
|
-> NotificationSettings
|
||||||
|
-> (Result Http.Error BasicResult -> msg)
|
||||||
|
-> Cmd msg
|
||||||
|
startOnceNotifyDueItems flags settings receive =
|
||||||
|
Http2.authPost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/usertask/notifydueitems/startonce"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.NotificationSettings.encode settings)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
submitNotifyDueItems :
|
submitNotifyDueItems :
|
||||||
Flags
|
Flags
|
||||||
-> NotificationSettings
|
-> NotificationSettings
|
||||||
|
@ -59,6 +59,7 @@ type Msg
|
|||||||
| CalEventMsg Comp.CalEventInput.Msg
|
| CalEventMsg Comp.CalEventInput.Msg
|
||||||
| SetNotificationSettings (Result Http.Error NotificationSettings)
|
| SetNotificationSettings (Result Http.Error NotificationSettings)
|
||||||
| SubmitResp (Result Http.Error BasicResult)
|
| SubmitResp (Result Http.Error BasicResult)
|
||||||
|
| StartOnce
|
||||||
|
|
||||||
|
|
||||||
initCmd : Flags -> Cmd Msg
|
initCmd : Flags -> Cmd Msg
|
||||||
@ -149,6 +150,27 @@ makeSettings model =
|
|||||||
model.schedule
|
model.schedule
|
||||||
|
|
||||||
|
|
||||||
|
withValidSettings : (NotificationSettings -> Cmd Msg) -> Model -> ( Model, Cmd Msg )
|
||||||
|
withValidSettings mkcmd model =
|
||||||
|
case makeSettings model of
|
||||||
|
Valid set ->
|
||||||
|
( { model | formMsg = Nothing }
|
||||||
|
, mkcmd set
|
||||||
|
)
|
||||||
|
|
||||||
|
Invalid errs _ ->
|
||||||
|
let
|
||||||
|
errMsg =
|
||||||
|
String.join ", " errs
|
||||||
|
in
|
||||||
|
( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none )
|
||||||
|
|
||||||
|
Unknown _ ->
|
||||||
|
( { model | formMsg = Just (BasicResult False "An unknown error occured") }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update flags msg model =
|
update flags msg model =
|
||||||
case msg of
|
case msg of
|
||||||
@ -341,23 +363,14 @@ update flags msg model =
|
|||||||
)
|
)
|
||||||
|
|
||||||
Submit ->
|
Submit ->
|
||||||
case makeSettings model of
|
withValidSettings
|
||||||
Valid set ->
|
(\set -> Api.submitNotifyDueItems flags set SubmitResp)
|
||||||
( { model | formMsg = Nothing }
|
model
|
||||||
, Api.submitNotifyDueItems flags set SubmitResp
|
|
||||||
)
|
|
||||||
|
|
||||||
Invalid errs _ ->
|
StartOnce ->
|
||||||
let
|
withValidSettings
|
||||||
errMsg =
|
(\set -> Api.startOnceNotifyDueItems flags set SubmitResp)
|
||||||
String.join ", " errs
|
model
|
||||||
in
|
|
||||||
( { model | formMsg = Just (BasicResult False errMsg) }, Cmd.none )
|
|
||||||
|
|
||||||
Unknown _ ->
|
|
||||||
( { model | formMsg = Just (BasicResult False "An unknown error occured") }
|
|
||||||
, Cmd.none
|
|
||||||
)
|
|
||||||
|
|
||||||
SubmitResp (Ok res) ->
|
SubmitResp (Ok res) ->
|
||||||
( { model | formMsg = Just res }
|
( { model | formMsg = Just res }
|
||||||
@ -484,18 +497,28 @@ view extraClasses model =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "ui divider" ] []
|
, div [ class "ui divider" ] []
|
||||||
, div [ class "ui error message" ]
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "ui message", True )
|
||||||
|
, ( "success", isFormSuccess model )
|
||||||
|
, ( "error", isFormError model )
|
||||||
|
, ( "hidden", model.formMsg == Nothing )
|
||||||
|
]
|
||||||
|
]
|
||||||
[ Maybe.map .message model.formMsg
|
[ Maybe.map .message model.formMsg
|
||||||
|> Maybe.withDefault ""
|
|> Maybe.withDefault ""
|
||||||
|> text
|
|> text
|
||||||
]
|
]
|
||||||
, div [ class "ui success message" ]
|
|
||||||
[ text "Successfully saved."
|
|
||||||
]
|
|
||||||
, button
|
, button
|
||||||
[ class "ui primary button"
|
[ class "ui primary button"
|
||||||
, onClick Submit
|
, onClick Submit
|
||||||
]
|
]
|
||||||
[ text "Submit"
|
[ text "Submit"
|
||||||
]
|
]
|
||||||
|
, button
|
||||||
|
[ class "ui right floated button"
|
||||||
|
, onClick StartOnce
|
||||||
|
]
|
||||||
|
[ text "Start Once"
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user