mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-06 15:15:58 +00:00
Change routes for scan-mailbox task to allow multiple tasks per user
This commit is contained in:
parent
743aa9d754
commit
9f9dd6c0fb
@ -2,8 +2,10 @@ package docspell.backend.ops
|
|||||||
|
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
import cats.data.OptionT
|
||||||
import com.github.eikek.calev.CalEvent
|
import com.github.eikek.calev.CalEvent
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
|
import fs2.Stream
|
||||||
|
|
||||||
import docspell.store.queue.JobQueue
|
import docspell.store.queue.JobQueue
|
||||||
import docspell.store.usertask._
|
import docspell.store.usertask._
|
||||||
@ -11,10 +13,15 @@ import docspell.common._
|
|||||||
|
|
||||||
trait OUserTask[F[_]] {
|
trait OUserTask[F[_]] {
|
||||||
|
|
||||||
/** Return the settings for the scan-mailbox task of the current user.
|
/** Return the settings for all scan-mailbox tasks of the current user.
|
||||||
* There is at most one such task per 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.
|
/** Updates the scan-mailbox tasks and notifies the joex nodes.
|
||||||
*/
|
*/
|
||||||
@ -24,7 +31,9 @@ trait OUserTask[F[_]] {
|
|||||||
): F[Unit]
|
): F[Unit]
|
||||||
|
|
||||||
/** Return the settings for the notify-due-items task of the current
|
/** 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]]
|
def getNotifyDueItems(account: AccountId): F[UserTask[NotifyDueItemsArgs]]
|
||||||
|
|
||||||
@ -35,6 +44,9 @@ trait OUserTask[F[_]] {
|
|||||||
task: UserTask[NotifyDueItemsArgs]
|
task: UserTask[NotifyDueItemsArgs]
|
||||||
): F[Unit]
|
): 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
|
/** Discards the schedule and immediately submits the task to the job
|
||||||
* executor's queue. It will not update the corresponding periodic
|
* executor's queue. It will not update the corresponding periodic
|
||||||
* task.
|
* task.
|
||||||
@ -63,17 +75,28 @@ object OUserTask {
|
|||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def getScanMailbox(account: AccountId): F[UserTask[ScanMailboxArgs]] =
|
def getScanMailbox(account: AccountId): Stream[F, UserTask[ScanMailboxArgs]] =
|
||||||
store
|
store
|
||||||
.getOneByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName)
|
.getByName[ScanMailboxArgs](account, ScanMailboxArgs.taskName)
|
||||||
.getOrElseF(scanMailboxDefault(account))
|
|
||||||
|
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(
|
def submitScanMailbox(
|
||||||
account: AccountId,
|
account: AccountId,
|
||||||
task: UserTask[ScanMailboxArgs]
|
task: UserTask[ScanMailboxArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateOneTask[ScanMailboxArgs](account, task)
|
_ <- store.updateTask[ScanMailboxArgs](account, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
@ -113,26 +136,26 @@ object OUserTask {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private def scanMailboxDefault(
|
// private def scanMailboxDefault(
|
||||||
account: AccountId
|
// account: AccountId
|
||||||
): F[UserTask[ScanMailboxArgs]] =
|
// ): F[UserTask[ScanMailboxArgs]] =
|
||||||
for {
|
// for {
|
||||||
id <- Ident.randomId[F]
|
// id <- Ident.randomId[F]
|
||||||
} yield UserTask(
|
// } yield UserTask(
|
||||||
id,
|
// id,
|
||||||
ScanMailboxArgs.taskName,
|
// ScanMailboxArgs.taskName,
|
||||||
false,
|
// false,
|
||||||
CalEvent.unsafe("*-*-* 0,12:00"),
|
// CalEvent.unsafe("*-*-* 0,12:00"),
|
||||||
ScanMailboxArgs(
|
// ScanMailboxArgs(
|
||||||
account,
|
// account,
|
||||||
Ident.unsafe(""),
|
// Ident.unsafe(""),
|
||||||
Nil,
|
// Nil,
|
||||||
Some(Duration.hours(12)),
|
// Some(Duration.hours(12)),
|
||||||
None,
|
// None,
|
||||||
false,
|
// false,
|
||||||
None
|
// None
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,13 @@ import cats.effect.Sync
|
|||||||
import io.circe.{Decoder, Encoder}
|
import io.circe.{Decoder, Encoder}
|
||||||
import scodec.bits.ByteVector
|
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 {
|
object Ident {
|
||||||
implicit val identEq: Eq[Ident] =
|
implicit val identEq: Eq[Ident] =
|
||||||
|
@ -1760,9 +1760,10 @@ paths:
|
|||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
summary: Get settings for "Scan Mailbox" task
|
summary: Get settings for "Scan Mailbox" task
|
||||||
description: |
|
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
|
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:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
responses:
|
responses:
|
||||||
@ -1771,13 +1772,13 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ScanMailboxSettings"
|
$ref: "#/components/schemas/ScanMailboxSettingsList"
|
||||||
post:
|
post:
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
summary: Change current settings for "Scan Mailbox" task
|
summary: Create settings for "Scan Mailbox" task
|
||||||
description: |
|
description: |
|
||||||
Change the current settings for the scan-mailbox task of the
|
Create new settings for a scan-mailbox task. The id field in
|
||||||
authenticated user.
|
the input data is ignored.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -1792,6 +1793,61 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$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:
|
/sec/usertask/scanmailbox/startonce:
|
||||||
post:
|
post:
|
||||||
tags: [ User Tasks ]
|
tags: [ User Tasks ]
|
||||||
@ -1816,6 +1872,16 @@ paths:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
ScanMailboxSettingsList:
|
||||||
|
description: |
|
||||||
|
A list of scan-mailbox tasks.
|
||||||
|
required:
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ScanMailboxSettings"
|
||||||
ScanMailboxSettings:
|
ScanMailboxSettings:
|
||||||
description: |
|
description: |
|
||||||
Settings for the scan mailbox task.
|
Settings for the scan mailbox task.
|
||||||
|
@ -2,6 +2,7 @@ package docspell.restserver.routes
|
|||||||
|
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
import cats.data.OptionT
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
import org.http4s.circe.CirceEntityEncoder._
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
@ -25,10 +26,18 @@ object ScanMailboxRoutes {
|
|||||||
import dsl._
|
import dsl._
|
||||||
|
|
||||||
HttpRoutes.of {
|
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" =>
|
case req @ POST -> Root / "startonce" =>
|
||||||
for {
|
for {
|
||||||
data <- req.as[ScanMailboxSettings]
|
data <- req.as[ScanMailboxSettings]
|
||||||
task = makeTask(user.account, data)
|
newId <- Ident.randomId[F]
|
||||||
|
task <- makeTask(newId, user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.executeNow(user.account, task)
|
ut.executeNow(user.account, task)
|
||||||
.attempt
|
.attempt
|
||||||
@ -36,43 +45,74 @@ object ScanMailboxRoutes {
|
|||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case GET -> Root =>
|
case DELETE -> Root / Ident(id) =>
|
||||||
for {
|
for {
|
||||||
task <- ut.getScanMailbox(user.account)
|
res <-
|
||||||
res <- taskToSettings(user.account, backend, task)
|
ut.deleteTask(user.account, id)
|
||||||
|
.attempt
|
||||||
|
.map(Conversions.basicResult(_, "Deleted successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
} yield resp
|
} 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 =>
|
case req @ POST -> Root =>
|
||||||
for {
|
for {
|
||||||
data <- req.as[ScanMailboxSettings]
|
data <- req.as[ScanMailboxSettings]
|
||||||
task = makeTask(user.account, data)
|
newId <- Ident.randomId[F]
|
||||||
|
task <- makeTask(newId, user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.submitScanMailbox(user.account, task)
|
ut.submitScanMailbox(user.account, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Saved successfully."))
|
.map(Conversions.basicResult(_, "Saved successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
} yield resp
|
} 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,
|
user: AccountId,
|
||||||
settings: ScanMailboxSettings
|
settings: ScanMailboxSettings
|
||||||
): UserTask[ScanMailboxArgs] =
|
): F[UserTask[ScanMailboxArgs]] =
|
||||||
UserTask(
|
Sync[F].pure(
|
||||||
settings.id,
|
UserTask(
|
||||||
ScanMailboxArgs.taskName,
|
id,
|
||||||
settings.enabled,
|
ScanMailboxArgs.taskName,
|
||||||
settings.schedule,
|
settings.enabled,
|
||||||
ScanMailboxArgs(
|
settings.schedule,
|
||||||
user,
|
ScanMailboxArgs(
|
||||||
settings.imapConnection,
|
user,
|
||||||
settings.folders,
|
settings.imapConnection,
|
||||||
settings.receivedSinceHours.map(_.toLong).map(Duration.hours),
|
settings.folders,
|
||||||
settings.targetFolder,
|
settings.receivedSinceHours.map(_.toLong).map(Duration.hours),
|
||||||
settings.deleteMail,
|
settings.targetFolder,
|
||||||
settings.direction
|
settings.deleteMail,
|
||||||
|
settings.direction
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,6 +31,20 @@ object QUserTask {
|
|||||||
)
|
)
|
||||||
).query[RPeriodicTask].stream.map(makeUserTask)
|
).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] =
|
def insert(account: AccountId, task: UserTask[String]): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
r <- task.toPeriodicTask[ConnectionIO](account)
|
r <- task.toPeriodicTask[ConnectionIO](account)
|
||||||
|
@ -42,12 +42,14 @@ trait UserTaskStore[F[_]] {
|
|||||||
D: Decoder[A]
|
D: Decoder[A]
|
||||||
): Stream[F, UserTask[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.
|
/** Updates or inserts the given task.
|
||||||
*
|
*
|
||||||
* The task is identified by its id. If no task with this id
|
* The task is identified by its id. If no task with this id
|
||||||
* exists, a new one is created. Otherwise the existing task is
|
* exists, a new one is created. Otherwise the existing task is
|
||||||
* updated. The job executors are notified if a task has been
|
* updated.
|
||||||
* enabled.
|
|
||||||
*/
|
*/
|
||||||
def updateTask[A](account: AccountId, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
|
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]] =
|
def getByNameRaw(account: AccountId, name: Ident): Stream[F, UserTask[String]] =
|
||||||
store.transact(QUserTask.findByName(account, name))
|
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
|
def getByName[A](account: AccountId, name: Ident)(implicit
|
||||||
D: Decoder[A]
|
D: Decoder[A]
|
||||||
): Stream[F, UserTask[A]] =
|
): Stream[F, UserTask[A]] =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user