Change routes for scan-mailbox task to allow multiple tasks per user

This commit is contained in:
Eike Kettner 2020-05-21 09:00:45 +02:00
parent 743aa9d754
commit 9f9dd6c0fb
6 changed files with 213 additions and 59 deletions

View File

@ -2,8 +2,10 @@ 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
import docspell.store.queue.JobQueue
import docspell.store.usertask._
@ -11,10 +13,15 @@ import docspell.common._
trait OUserTask[F[_]] {
/** Return the settings for the scan-mailbox task of the current user.
* There is at most one such task per user.
/** Return the settings for all scan-mailbox tasks of the current user.
*/
def getScanMailbox(account: AccountId): F[UserTask[ScanMailboxArgs]]
def getScanMailbox(account: AccountId): Stream[F, UserTask[ScanMailboxArgs]]
/** Find a scan-mailbox task by the given id. */
def findScanMailbox(
id: Ident,
account: AccountId
): OptionT[F, UserTask[ScanMailboxArgs]]
/** Updates the scan-mailbox tasks and notifies the joex nodes.
*/
@ -24,7 +31,9 @@ trait OUserTask[F[_]] {
): F[Unit]
/** Return the settings for the notify-due-items task of the current
* user. There is at most one such task per user.
* 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.
*/
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
@ -35,6 +44,9 @@ trait OUserTask[F[_]] {
task: UserTask[NotifyDueItemsArgs]
): F[Unit]
/** Removes a user task with the given id. */
def deleteTask(account: AccountId, id: Ident): F[Unit]
/** Discards the schedule and immediately submits the task to the job
* executor's queue. It will not update the corresponding periodic
* task.
@ -63,17 +75,28 @@ object OUserTask {
_ <- joex.notifyAllNodes
} yield ()
def getScanMailbox(account: AccountId): F[UserTask[ScanMailboxArgs]] =
def getScanMailbox(account: AccountId): Stream[F, UserTask[ScanMailboxArgs]] =
store
.getOneByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName)
.getOrElseF(scanMailboxDefault(account))
.getByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName)
def findScanMailbox(
id: Ident,
account: AccountId
): OptionT[F, UserTask[ScanMailboxArgs]] =
OptionT(getScanMailbox(account).find(_.id == id).compile.last)
def deleteTask(account: AccountId, id: Ident): F[Unit] =
(for {
_ <- store.getByIdRaw(account, id)
_ <- OptionT.liftF(store.deleteTask(account, id))
} yield ()).getOrElse(())
def submitScanMailbox(
account: AccountId,
task: UserTask[ScanMailboxArgs]
): F[Unit] =
for {
_ <- store.updateOneTask[ScanMailboxArgs](account, task)
_ <- store.updateTask[ScanMailboxArgs](account, task)
_ <- joex.notifyAllNodes
} yield ()
@ -113,26 +136,26 @@ object OUserTask {
)
)
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
)
)
// 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
// )
// )
})
}

View File

@ -9,7 +9,13 @@ import cats.effect.Sync
import io.circe.{Decoder, Encoder}
import scodec.bits.ByteVector
case class Ident(id: String) {}
case class Ident(id: String) {
def isEmpty: Boolean =
id.trim.isEmpty
def nonEmpty: Boolean =
!isEmpty
}
object Ident {
implicit val identEq: Eq[Ident] =

View File

@ -1760,9 +1760,10 @@ paths:
tags: [ User Tasks ]
summary: Get settings for "Scan Mailbox" task
description: |
Return the current settings for the scan mailbox task of the
Return the current settings for the scan-mailbox tasks of the
authenticated user. Users can periodically fetch mails to be
imported into docspell.
imported into docspell. It is possible to have multiple of
these tasks.
security:
- authTokenHeader: []
responses:
@ -1771,13 +1772,13 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/ScanMailboxSettings"
$ref: "#/components/schemas/ScanMailboxSettingsList"
post:
tags: [ User Tasks ]
summary: Change current settings for "Scan Mailbox" task
summary: Create settings for "Scan Mailbox" task
description: |
Change the current settings for the scan-mailbox task of the
authenticated user.
Create new settings for a scan-mailbox task. The id field in
the input data is ignored.
security:
- authTokenHeader: []
requestBody:
@ -1792,6 +1793,61 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
put:
tags: [ User Tasks ]
summary: Change current settings for "Scan Mailbox" task
description: |
Change the settings for a scan-mailbox task. The task is
looked up by its id.
security:
- authTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ScanMailboxSettings"
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/usertask/scanmailbox/{id}:
parameters:
- $ref: "#/components/parameters/id"
get:
tags: [ User Tasks ]
summary: Get settings for "Scan Mailbox" task
description: |
Return the current settings for a single scan-mailbox task of
the authenticated user. Users can periodically fetch mails to
be imported into docspell.
security:
- authTokenHeader: []
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/ScanMailboxSettings"
delete:
tags: [ User Tasks ]
summary: Delete a scan-mailbox task.
description: |
Deletes the settings to a scan-mailbox task of the
authenticated user.
security:
- authTokenHeader: []
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/usertask/scanmailbox/startonce:
post:
tags: [ User Tasks ]
@ -1816,6 +1872,16 @@ paths:
components:
schemas:
ScanMailboxSettingsList:
description: |
A list of scan-mailbox tasks.
required:
- items
properties:
items:
type: array
items:
$ref: "#/components/schemas/ScanMailboxSettings"
ScanMailboxSettings:
description: |
Settings for the scan mailbox task.

View File

@ -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._
@ -25,10 +26,18 @@ object ScanMailboxRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root / Ident(id) =>
(for {
task <- ut.findScanMailbox(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[ScanMailboxSettings]
task = makeTask(user.account, data)
data <- req.as[ScanMailboxSettings]
newId <- Ident.randomId[F]
task <- makeTask(newId, user.account, data)
res <-
ut.executeNow(user.account, task)
.attempt
@ -36,43 +45,74 @@ object ScanMailboxRoutes {
resp <- Ok(res)
} yield resp
case GET -> Root =>
case DELETE -> Root / Ident(id) =>
for {
task <- ut.getScanMailbox(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: ScanMailboxSettings) =
for {
task <- makeTask(data.id, user.account, data)
res <-
ut.submitScanMailbox(user.account, task)
.attempt
.map(Conversions.basicResult(_, "Saved successfully."))
resp <- Ok(res)
} yield resp
for {
data <- req.as[ScanMailboxSettings]
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[ScanMailboxSettings]
task = makeTask(user.account, data)
data <- req.as[ScanMailboxSettings]
newId <- Ident.randomId[F]
task <- makeTask(newId, user.account, data)
res <-
ut.submitScanMailbox(user.account, task)
.attempt
.map(Conversions.basicResult(_, "Saved successfully."))
resp <- Ok(res)
} yield resp
case GET -> Root =>
ut.getScanMailbox(user.account)
.evalMap(task => taskToSettings(user.account, backend, task))
.compile
.toVector
.map(v => ScanMailboxSettingsList(v.toList))
.flatMap(Ok(_))
}
}
def makeTask(
def makeTask[F[_]: Sync](
id: Ident,
user: AccountId,
settings: ScanMailboxSettings
): UserTask[ScanMailboxArgs] =
UserTask(
settings.id,
ScanMailboxArgs.taskName,
settings.enabled,
settings.schedule,
ScanMailboxArgs(
user,
settings.imapConnection,
settings.folders,
settings.receivedSinceHours.map(_.toLong).map(Duration.hours),
settings.targetFolder,
settings.deleteMail,
settings.direction
): F[UserTask[ScanMailboxArgs]] =
Sync[F].pure(
UserTask(
id,
ScanMailboxArgs.taskName,
settings.enabled,
settings.schedule,
ScanMailboxArgs(
user,
settings.imapConnection,
settings.folders,
settings.receivedSinceHours.map(_.toLong).map(Duration.hours),
settings.targetFolder,
settings.deleteMail,
settings.direction
)
)
)

View File

@ -31,6 +31,20 @@ object QUserTask {
)
).query[RPeriodicTask].stream.map(makeUserTask)
def findById(
account: AccountId,
id: Ident
): ConnectionIO[Option[UserTask[String]]] =
selectSimple(
RPeriodicTask.Columns.all,
RPeriodicTask.table,
and(
cols.group.is(account.collective),
cols.submitter.is(account.user),
cols.id.is(id)
)
).query[RPeriodicTask].option.map(_.map(makeUserTask))
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
for {
r <- task.toPeriodicTask[ConnectionIO](account)

View File

@ -42,12 +42,14 @@ trait UserTaskStore[F[_]] {
D: Decoder[A]
): Stream[F, UserTask[A]]
/** Return a user-task with the given id. */
def getByIdRaw(account: AccountId, id: Ident): OptionT[F, UserTask[String]]
/** 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.
* updated.
*/
def updateTask[A](account: AccountId, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
@ -100,6 +102,9 @@ object UserTaskStore {
def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]] =
store.transact(QUserTask.findByName(account, name))
def getByIdRaw(account: AccountId, id: Ident): OptionT[F, UserTask[String]] =
OptionT(store.transact(QUserTask.findById(account, id)))
def getByName[A](account: AccountId, name: Ident)(implicit
D: Decoder[A]
): Stream[F, UserTask[A]] =