mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 02:49:32 +00:00
Merge pull request #1009 from eikek/fixup/347-delete-duration
Use a minimum age of items to remove
This commit is contained in:
commit
50387cd378
@ -242,6 +242,10 @@ val openapiScalaSettings = Seq(
|
|||||||
field =>
|
field =>
|
||||||
field
|
field
|
||||||
.copy(typeDef = TypeDef("SearchMode", Imports("docspell.common.SearchMode")))
|
.copy(typeDef = TypeDef("SearchMode", Imports("docspell.common.SearchMode")))
|
||||||
|
case "duration" =>
|
||||||
|
field =>
|
||||||
|
field
|
||||||
|
.copy(typeDef = TypeDef("Duration", Imports("docspell.common.Duration")))
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ trait OCollective[F[_]] {
|
|||||||
|
|
||||||
def startLearnClassifier(collective: Ident): F[Unit]
|
def startLearnClassifier(collective: Ident): F[Unit]
|
||||||
|
|
||||||
def startEmptyTrash(collective: Ident): F[Unit]
|
def startEmptyTrash(args: EmptyTrashArgs): F[Unit]
|
||||||
|
|
||||||
/** Submits a task that (re)generates the preview images for all
|
/** Submits a task that (re)generates the preview images for all
|
||||||
* attachments of the given collective.
|
* attachments of the given collective.
|
||||||
@ -88,6 +88,8 @@ object OCollective {
|
|||||||
val Settings = RCollective.Settings
|
val Settings = RCollective.Settings
|
||||||
type Classifier = RClassifierSetting.Classifier
|
type Classifier = RClassifierSetting.Classifier
|
||||||
val Classifier = RClassifierSetting.Classifier
|
val Classifier = RClassifierSetting.Classifier
|
||||||
|
type EmptyTrash = REmptyTrashSetting.EmptyTrash
|
||||||
|
val EmptyTrash = REmptyTrashSetting.EmptyTrash
|
||||||
|
|
||||||
sealed trait PassResetResult
|
sealed trait PassResetResult
|
||||||
object PassResetResult {
|
object PassResetResult {
|
||||||
@ -160,51 +162,47 @@ object OCollective {
|
|||||||
id <- Ident.randomId[F]
|
id <- Ident.randomId[F]
|
||||||
on = sett.classifier.map(_.enabled).getOrElse(false)
|
on = sett.classifier.map(_.enabled).getOrElse(false)
|
||||||
timer = sett.classifier.map(_.schedule).getOrElse(CalEvent.unsafe(""))
|
timer = sett.classifier.map(_.schedule).getOrElse(CalEvent.unsafe(""))
|
||||||
|
args = LearnClassifierArgs(coll)
|
||||||
ut = UserTask(
|
ut = UserTask(
|
||||||
id,
|
id,
|
||||||
LearnClassifierArgs.taskName,
|
LearnClassifierArgs.taskName,
|
||||||
on,
|
on,
|
||||||
timer,
|
timer,
|
||||||
None,
|
None,
|
||||||
LearnClassifierArgs(coll)
|
args
|
||||||
)
|
)
|
||||||
_ <- uts.updateOneTask(UserTaskScope(coll), ut)
|
_ <- uts.updateOneTask(UserTaskScope(coll), args.makeSubject.some, ut)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
private def updateEmptyTrashTask(coll: Ident, sett: Settings): F[Unit] =
|
private def updateEmptyTrashTask(coll: Ident, sett: Settings): F[Unit] =
|
||||||
for {
|
for {
|
||||||
id <- Ident.randomId[F]
|
id <- Ident.randomId[F]
|
||||||
timer = sett.emptyTrash.getOrElse(CalEvent.unsafe(""))
|
settings = sett.emptyTrash.getOrElse(EmptyTrash.default)
|
||||||
ut = UserTask(
|
args = EmptyTrashArgs(coll, settings.minAge)
|
||||||
id,
|
ut = UserTask(id, EmptyTrashArgs.taskName, true, settings.schedule, None, args)
|
||||||
EmptyTrashArgs.taskName,
|
_ <- uts.updateOneTask(UserTaskScope(coll), args.makeSubject.some, ut)
|
||||||
true,
|
|
||||||
timer,
|
|
||||||
None,
|
|
||||||
EmptyTrashArgs(coll)
|
|
||||||
)
|
|
||||||
_ <- uts.updateOneTask(UserTaskScope(coll), ut)
|
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def startLearnClassifier(collective: Ident): F[Unit] =
|
def startLearnClassifier(collective: Ident): F[Unit] =
|
||||||
for {
|
for {
|
||||||
id <- Ident.randomId[F]
|
id <- Ident.randomId[F]
|
||||||
|
args = LearnClassifierArgs(collective)
|
||||||
ut <- UserTask(
|
ut <- UserTask(
|
||||||
id,
|
id,
|
||||||
LearnClassifierArgs.taskName,
|
LearnClassifierArgs.taskName,
|
||||||
true,
|
true,
|
||||||
CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All),
|
CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All),
|
||||||
None,
|
None,
|
||||||
LearnClassifierArgs(collective)
|
args
|
||||||
).encode.toPeriodicTask(UserTaskScope(collective))
|
).encode.toPeriodicTask(UserTaskScope(collective), args.makeSubject.some)
|
||||||
job <- ut.toJob
|
job <- ut.toJob
|
||||||
_ <- queue.insert(job)
|
_ <- queue.insert(job)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def startEmptyTrash(collective: Ident): F[Unit] =
|
def startEmptyTrash(args: EmptyTrashArgs): F[Unit] =
|
||||||
for {
|
for {
|
||||||
id <- Ident.randomId[F]
|
id <- Ident.randomId[F]
|
||||||
ut <- UserTask(
|
ut <- UserTask(
|
||||||
@ -213,8 +211,8 @@ object OCollective {
|
|||||||
true,
|
true,
|
||||||
CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All),
|
CalEvent(WeekdayComponent.All, DateEvent.All, TimeEvent.All),
|
||||||
None,
|
None,
|
||||||
EmptyTrashArgs(collective)
|
args
|
||||||
).encode.toPeriodicTask(UserTaskScope(collective))
|
).encode.toPeriodicTask(UserTaskScope(args.collective), args.makeSubject.some)
|
||||||
job <- ut.toJob
|
job <- ut.toJob
|
||||||
_ <- queue.insert(job)
|
_ <- queue.insert(job)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
|
@ -23,7 +23,7 @@ import doobie.implicits._
|
|||||||
trait OItemSearch[F[_]] {
|
trait OItemSearch[F[_]] {
|
||||||
def findItem(id: Ident, collective: Ident): F[Option[ItemData]]
|
def findItem(id: Ident, collective: Ident): F[Option[ItemData]]
|
||||||
|
|
||||||
def findDeleted(collective: Ident, limit: Int): F[Vector[RItem]]
|
def findDeleted(collective: Ident, maxUpdate: Timestamp, limit: Int): F[Vector[RItem]]
|
||||||
|
|
||||||
def findItems(maxNoteLen: Int)(q: Query, batch: Batch): F[Vector[ListItem]]
|
def findItems(maxNoteLen: Int)(q: Query, batch: Batch): F[Vector[ListItem]]
|
||||||
|
|
||||||
@ -147,9 +147,13 @@ object OItemSearch {
|
|||||||
.toVector
|
.toVector
|
||||||
}
|
}
|
||||||
|
|
||||||
def findDeleted(collective: Ident, limit: Int): F[Vector[RItem]] =
|
def findDeleted(
|
||||||
|
collective: Ident,
|
||||||
|
maxUpdate: Timestamp,
|
||||||
|
limit: Int
|
||||||
|
): F[Vector[RItem]] =
|
||||||
store
|
store
|
||||||
.transact(RItem.findDeleted(collective, limit))
|
.transact(RItem.findDeleted(collective, maxUpdate, limit))
|
||||||
.take(limit.toLong)
|
.take(limit.toLong)
|
||||||
.compile
|
.compile
|
||||||
.toVector
|
.toVector
|
||||||
|
@ -33,6 +33,7 @@ trait OUserTask[F[_]] {
|
|||||||
*/
|
*/
|
||||||
def submitScanMailbox(
|
def submitScanMailbox(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
task: UserTask[ScanMailboxArgs]
|
task: UserTask[ScanMailboxArgs]
|
||||||
): F[Unit]
|
): F[Unit]
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ trait OUserTask[F[_]] {
|
|||||||
*/
|
*/
|
||||||
def submitNotifyDueItems(
|
def submitNotifyDueItems(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
task: UserTask[NotifyDueItemsArgs]
|
task: UserTask[NotifyDueItemsArgs]
|
||||||
): F[Unit]
|
): F[Unit]
|
||||||
|
|
||||||
@ -61,8 +63,8 @@ trait OUserTask[F[_]] {
|
|||||||
* executor's queue. It will not update the corresponding periodic
|
* executor's queue. It will not update the corresponding periodic
|
||||||
* task.
|
* task.
|
||||||
*/
|
*/
|
||||||
def executeNow[A](scope: UserTaskScope, task: UserTask[A])(implicit
|
def executeNow[A](scope: UserTaskScope, subject: Option[String], task: UserTask[A])(
|
||||||
E: Encoder[A]
|
implicit E: Encoder[A]
|
||||||
): F[Unit]
|
): F[Unit]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +77,11 @@ object OUserTask {
|
|||||||
): Resource[F, OUserTask[F]] =
|
): Resource[F, OUserTask[F]] =
|
||||||
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
|
Resource.pure[F, OUserTask[F]](new OUserTask[F] {
|
||||||
|
|
||||||
def executeNow[A](scope: UserTaskScope, task: UserTask[A])(implicit
|
def executeNow[A](scope: UserTaskScope, subject: Option[String], task: UserTask[A])(
|
||||||
E: Encoder[A]
|
implicit E: Encoder[A]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
ptask <- task.encode.toPeriodicTask(scope)
|
ptask <- task.encode.toPeriodicTask(scope, subject)
|
||||||
job <- ptask.toJob
|
job <- ptask.toJob
|
||||||
_ <- queue.insert(job)
|
_ <- queue.insert(job)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
@ -103,10 +105,11 @@ object OUserTask {
|
|||||||
|
|
||||||
def submitScanMailbox(
|
def submitScanMailbox(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
task: UserTask[ScanMailboxArgs]
|
task: UserTask[ScanMailboxArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateTask[ScanMailboxArgs](scope, task)
|
_ <- store.updateTask[ScanMailboxArgs](scope, subject, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
@ -124,10 +127,11 @@ object OUserTask {
|
|||||||
|
|
||||||
def submitNotifyDueItems(
|
def submitNotifyDueItems(
|
||||||
scope: UserTaskScope,
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
task: UserTask[NotifyDueItemsArgs]
|
task: UserTask[NotifyDueItemsArgs]
|
||||||
): F[Unit] =
|
): F[Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- store.updateTask[NotifyDueItemsArgs](scope, task)
|
_ <- store.updateTask[NotifyDueItemsArgs](scope, subject, task)
|
||||||
_ <- joex.notifyAllNodes
|
_ <- joex.notifyAllNodes
|
||||||
} yield ()
|
} yield ()
|
||||||
})
|
})
|
||||||
|
@ -18,11 +18,12 @@ import io.circe.generic.semiauto._
|
|||||||
* items. These are items with state `ItemState.Deleted`.
|
* items. These are items with state `ItemState.Deleted`.
|
||||||
*/
|
*/
|
||||||
case class EmptyTrashArgs(
|
case class EmptyTrashArgs(
|
||||||
collective: Ident
|
collective: Ident,
|
||||||
|
minAge: Duration
|
||||||
) {
|
) {
|
||||||
|
|
||||||
def makeSubject: String =
|
def makeSubject: String =
|
||||||
"Empty trash"
|
s"Empty Trash: Remove older than ${minAge.toJava}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +87,11 @@ final class JoexAppImpl[F[_]: Async](
|
|||||||
private def scheduleEmptyTrashTasks: F[Unit] =
|
private def scheduleEmptyTrashTasks: F[Unit] =
|
||||||
store
|
store
|
||||||
.transact(
|
.transact(
|
||||||
REmptyTrashSetting.findForAllCollectives(EmptyTrashArgs.defaultSchedule, 50)
|
REmptyTrashSetting.findForAllCollectives(OCollective.EmptyTrash.default, 50)
|
||||||
|
)
|
||||||
|
.evalMap(es =>
|
||||||
|
EmptyTrashTask.periodicTask(EmptyTrashArgs(es.cid, es.minAge), es.schedule)
|
||||||
)
|
)
|
||||||
.evalMap(es => EmptyTrashTask.periodicTask(es.cid, es.schedule))
|
|
||||||
.evalMap(pstore.insert)
|
.evalMap(pstore.insert)
|
||||||
.compile
|
.compile
|
||||||
.drain
|
.drain
|
||||||
|
@ -26,7 +26,7 @@ object EmptyTrashTask {
|
|||||||
|
|
||||||
private val pageSize = 20
|
private val pageSize = 20
|
||||||
|
|
||||||
def periodicTask[F[_]: Sync](collective: Ident, ce: CalEvent): F[RPeriodicTask] =
|
def periodicTask[F[_]: Sync](args: EmptyTrashArgs, ce: CalEvent): F[RPeriodicTask] =
|
||||||
Ident
|
Ident
|
||||||
.randomId[F]
|
.randomId[F]
|
||||||
.flatMap(id =>
|
.flatMap(id =>
|
||||||
@ -36,8 +36,8 @@ object EmptyTrashTask {
|
|||||||
true,
|
true,
|
||||||
ce,
|
ce,
|
||||||
None,
|
None,
|
||||||
EmptyTrashArgs(collective)
|
args
|
||||||
).encode.toPeriodicTask(UserTaskScope(collective))
|
).encode.toPeriodicTask(UserTaskScope(args.collective), args.makeSubject.some)
|
||||||
)
|
)
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
@ -45,23 +45,27 @@ object EmptyTrashTask {
|
|||||||
itemSearchOps: OItemSearch[F]
|
itemSearchOps: OItemSearch[F]
|
||||||
): Task[F, Args, Unit] =
|
): Task[F, Args, Unit] =
|
||||||
Task { ctx =>
|
Task { ctx =>
|
||||||
val collId = ctx.args.collective
|
|
||||||
for {
|
for {
|
||||||
_ <- ctx.logger.info(s"Starting removing all soft-deleted items")
|
now <- Timestamp.current[F]
|
||||||
nDeleted <- deleteAll(collId, itemOps, itemSearchOps, ctx)
|
maxDate = now.minus(ctx.args.minAge)
|
||||||
|
_ <- ctx.logger.info(
|
||||||
|
s"Starting removing all soft-deleted items older than ${maxDate.asString}"
|
||||||
|
)
|
||||||
|
nDeleted <- deleteAll(ctx.args, maxDate, itemOps, itemSearchOps, ctx)
|
||||||
_ <- ctx.logger.info(s"Finished deleting ${nDeleted} items")
|
_ <- ctx.logger.info(s"Finished deleting ${nDeleted} items")
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def deleteAll[F[_]: Async](
|
private def deleteAll[F[_]: Async](
|
||||||
collective: Ident,
|
args: Args,
|
||||||
|
maxUpdate: Timestamp,
|
||||||
itemOps: OItem[F],
|
itemOps: OItem[F],
|
||||||
itemSearchOps: OItemSearch[F],
|
itemSearchOps: OItemSearch[F],
|
||||||
ctx: Context[F, _]
|
ctx: Context[F, _]
|
||||||
): F[Int] =
|
): F[Int] =
|
||||||
Stream
|
Stream
|
||||||
.eval(itemSearchOps.findDeleted(collective, pageSize))
|
.eval(itemSearchOps.findDeleted(args.collective, maxUpdate, pageSize))
|
||||||
.evalMap(deleteChunk(collective, itemOps, ctx))
|
.evalMap(deleteChunk(args.collective, itemOps, ctx))
|
||||||
.repeat
|
.repeat
|
||||||
.takeWhile(_ > 0)
|
.takeWhile(_ > 0)
|
||||||
.compile
|
.compile
|
||||||
|
@ -1149,6 +1149,11 @@ paths:
|
|||||||
The request is empty, settings are used from the collective.
|
The request is empty, settings are used from the collective.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/EmptyTrashSetting"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Ok
|
description: Ok
|
||||||
@ -5267,7 +5272,7 @@ components:
|
|||||||
- language
|
- language
|
||||||
- integrationEnabled
|
- integrationEnabled
|
||||||
- classifier
|
- classifier
|
||||||
- emptyTrashSchedule
|
- emptyTrash
|
||||||
properties:
|
properties:
|
||||||
language:
|
language:
|
||||||
type: string
|
type: string
|
||||||
@ -5277,11 +5282,24 @@ components:
|
|||||||
description: |
|
description: |
|
||||||
Whether the collective has the integration endpoint
|
Whether the collective has the integration endpoint
|
||||||
enabled.
|
enabled.
|
||||||
emptyTrashSchedule:
|
|
||||||
type: string
|
|
||||||
format: calevent
|
|
||||||
classifier:
|
classifier:
|
||||||
$ref: "#/components/schemas/ClassifierSetting"
|
$ref: "#/components/schemas/ClassifierSetting"
|
||||||
|
emptyTrash:
|
||||||
|
$ref: "#/components/schemas/EmptyTrashSetting"
|
||||||
|
|
||||||
|
EmptyTrashSetting:
|
||||||
|
description: |
|
||||||
|
Settings for clearing the trash of items.
|
||||||
|
required:
|
||||||
|
- schedule
|
||||||
|
- minAge
|
||||||
|
properties:
|
||||||
|
schedule:
|
||||||
|
type: string
|
||||||
|
format: calevent
|
||||||
|
minAge:
|
||||||
|
type: integer
|
||||||
|
format: duration
|
||||||
|
|
||||||
ClassifierSetting:
|
ClassifierSetting:
|
||||||
description: |
|
description: |
|
||||||
|
@ -12,7 +12,8 @@ import cats.implicits._
|
|||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.AuthToken
|
import docspell.backend.auth.AuthToken
|
||||||
import docspell.backend.ops.OCollective
|
import docspell.backend.ops.OCollective
|
||||||
import docspell.common.{EmptyTrashArgs, ListType}
|
import docspell.common.EmptyTrashArgs
|
||||||
|
import docspell.common.ListType
|
||||||
import docspell.restapi.model._
|
import docspell.restapi.model._
|
||||||
import docspell.restserver.conv.Conversions
|
import docspell.restserver.conv.Conversions
|
||||||
import docspell.restserver.http4s._
|
import docspell.restserver.http4s._
|
||||||
@ -56,7 +57,12 @@ object CollectiveRoutes {
|
|||||||
settings.classifier.listType
|
settings.classifier.listType
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Some(settings.emptyTrashSchedule)
|
Some(
|
||||||
|
OCollective.EmptyTrash(
|
||||||
|
settings.emptyTrash.schedule,
|
||||||
|
settings.emptyTrash.minAge
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
res <-
|
res <-
|
||||||
backend.collective
|
backend.collective
|
||||||
@ -67,11 +73,11 @@ object CollectiveRoutes {
|
|||||||
case GET -> Root / "settings" =>
|
case GET -> Root / "settings" =>
|
||||||
for {
|
for {
|
||||||
settDb <- backend.collective.findSettings(user.account.collective)
|
settDb <- backend.collective.findSettings(user.account.collective)
|
||||||
|
trash = settDb.flatMap(_.emptyTrash).getOrElse(OCollective.EmptyTrash.default)
|
||||||
sett = settDb.map(c =>
|
sett = settDb.map(c =>
|
||||||
CollectiveSettings(
|
CollectiveSettings(
|
||||||
c.language,
|
c.language,
|
||||||
c.integrationEnabled,
|
c.integrationEnabled,
|
||||||
c.emptyTrash.getOrElse(EmptyTrashArgs.defaultSchedule),
|
|
||||||
ClassifierSetting(
|
ClassifierSetting(
|
||||||
c.classifier.map(_.itemCount).getOrElse(0),
|
c.classifier.map(_.itemCount).getOrElse(0),
|
||||||
c.classifier
|
c.classifier
|
||||||
@ -79,6 +85,10 @@ object CollectiveRoutes {
|
|||||||
.getOrElse(CalEvent.unsafe("*-1/3-01 01:00:00")),
|
.getOrElse(CalEvent.unsafe("*-1/3-01 01:00:00")),
|
||||||
c.classifier.map(_.categories).getOrElse(Nil),
|
c.classifier.map(_.categories).getOrElse(Nil),
|
||||||
c.classifier.map(_.listType).getOrElse(ListType.whitelist)
|
c.classifier.map(_.listType).getOrElse(ListType.whitelist)
|
||||||
|
),
|
||||||
|
EmptyTrashSetting(
|
||||||
|
trash.schedule,
|
||||||
|
trash.minAge
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -103,9 +113,12 @@ object CollectiveRoutes {
|
|||||||
resp <- Ok(BasicResult(true, "Task submitted"))
|
resp <- Ok(BasicResult(true, "Task submitted"))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case POST -> Root / "emptytrash" / "startonce" =>
|
case req @ POST -> Root / "emptytrash" / "startonce" =>
|
||||||
for {
|
for {
|
||||||
_ <- backend.collective.startEmptyTrash(user.account.collective)
|
data <- req.as[EmptyTrashSetting]
|
||||||
|
_ <- backend.collective.startEmptyTrash(
|
||||||
|
EmptyTrashArgs(user.account.collective, data.minAge)
|
||||||
|
)
|
||||||
resp <- Ok(BasicResult(true, "Task submitted"))
|
resp <- Ok(BasicResult(true, "Task submitted"))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ object NotifyDueItemsRoutes {
|
|||||||
newId <- Ident.randomId[F]
|
newId <- Ident.randomId[F]
|
||||||
task <- makeTask(newId, getBaseUrl(cfg, req), user.account, data)
|
task <- makeTask(newId, getBaseUrl(cfg, req), user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.executeNow(UserTaskScope(user.account), task)
|
ut.executeNow(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Submitted successfully."))
|
.map(Conversions.basicResult(_, "Submitted successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
@ -69,7 +69,7 @@ object NotifyDueItemsRoutes {
|
|||||||
for {
|
for {
|
||||||
task <- makeTask(data.id, getBaseUrl(cfg, req), user.account, data)
|
task <- makeTask(data.id, getBaseUrl(cfg, req), user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.submitNotifyDueItems(UserTaskScope(user.account), task)
|
ut.submitNotifyDueItems(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Saved successfully"))
|
.map(Conversions.basicResult(_, "Saved successfully"))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
@ -87,7 +87,7 @@ object NotifyDueItemsRoutes {
|
|||||||
newId <- Ident.randomId[F]
|
newId <- Ident.randomId[F]
|
||||||
task <- makeTask(newId, getBaseUrl(cfg, req), user.account, data)
|
task <- makeTask(newId, getBaseUrl(cfg, req), user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.submitNotifyDueItems(UserTaskScope(user.account), task)
|
ut.submitNotifyDueItems(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Saved successfully."))
|
.map(Conversions.basicResult(_, "Saved successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
|
@ -46,7 +46,7 @@ object ScanMailboxRoutes {
|
|||||||
newId <- Ident.randomId[F]
|
newId <- Ident.randomId[F]
|
||||||
task <- makeTask(newId, user.account, data)
|
task <- makeTask(newId, user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.executeNow(UserTaskScope(user.account), task)
|
ut.executeNow(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Submitted successfully."))
|
.map(Conversions.basicResult(_, "Submitted successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
@ -66,7 +66,7 @@ object ScanMailboxRoutes {
|
|||||||
for {
|
for {
|
||||||
task <- makeTask(data.id, user.account, data)
|
task <- makeTask(data.id, user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.submitScanMailbox(UserTaskScope(user.account), task)
|
ut.submitScanMailbox(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Saved successfully."))
|
.map(Conversions.basicResult(_, "Saved successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
@ -84,7 +84,7 @@ object ScanMailboxRoutes {
|
|||||||
newId <- Ident.randomId[F]
|
newId <- Ident.randomId[F]
|
||||||
task <- makeTask(newId, user.account, data)
|
task <- makeTask(newId, user.account, data)
|
||||||
res <-
|
res <-
|
||||||
ut.submitScanMailbox(UserTaskScope(user.account), task)
|
ut.submitScanMailbox(UserTaskScope(user.account), None, task)
|
||||||
.attempt
|
.attempt
|
||||||
.map(Conversions.basicResult(_, "Saved successfully."))
|
.map(Conversions.basicResult(_, "Saved successfully."))
|
||||||
resp <- Ok(res)
|
resp <- Ok(res)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE "empty_trash_setting"
|
||||||
|
ADD COLUMN "min_age" bigint;
|
||||||
|
|
||||||
|
UPDATE "empty_trash_setting"
|
||||||
|
SET "min_age" = 604800000;
|
||||||
|
|
||||||
|
ALTER TABLE "empty_trash_setting"
|
||||||
|
ALTER COLUMN "min_age" SET NOT NULL;
|
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE `empty_trash_setting`
|
||||||
|
ADD COLUMN (`min_age` bigint);
|
||||||
|
|
||||||
|
UPDATE `empty_trash_setting`
|
||||||
|
SET `min_age` = 604800000;
|
||||||
|
|
||||||
|
ALTER TABLE `empty_trash_setting`
|
||||||
|
MODIFY `min_age` bigint NOT NULL;
|
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE "empty_trash_setting"
|
||||||
|
ADD COLUMN "min_age" bigint;
|
||||||
|
|
||||||
|
UPDATE "empty_trash_setting"
|
||||||
|
SET "min_age" = 604800000;
|
||||||
|
|
||||||
|
ALTER TABLE "empty_trash_setting"
|
||||||
|
ALTER COLUMN "min_age" SET NOT NULL;
|
@ -34,6 +34,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
|||||||
e.apply(a).noSpaces
|
e.apply(a).noSpaces
|
||||||
)
|
)
|
||||||
|
|
||||||
|
implicit val metaDuration: Meta[Duration] =
|
||||||
|
Meta[Long].imap(Duration.millis)(_.millis)
|
||||||
|
|
||||||
implicit val metaCollectiveState: Meta[CollectiveState] =
|
implicit val metaCollectiveState: Meta[CollectiveState] =
|
||||||
Meta[String].imap(CollectiveState.unsafe)(CollectiveState.asString)
|
Meta[String].imap(CollectiveState.unsafe)(CollectiveState.asString)
|
||||||
|
|
||||||
|
@ -54,15 +54,23 @@ object QUserTask {
|
|||||||
)
|
)
|
||||||
).query[RPeriodicTask].option.map(_.map(makeUserTask))
|
).query[RPeriodicTask].option.map(_.map(makeUserTask))
|
||||||
|
|
||||||
def insert(scope: UserTaskScope, task: UserTask[String]): ConnectionIO[Int] =
|
def insert(
|
||||||
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
|
task: UserTask[String]
|
||||||
|
): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
r <- task.toPeriodicTask[ConnectionIO](scope)
|
r <- task.toPeriodicTask[ConnectionIO](scope, subject)
|
||||||
n <- RPeriodicTask.insert(r)
|
n <- RPeriodicTask.insert(r)
|
||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
def update(scope: UserTaskScope, task: UserTask[String]): ConnectionIO[Int] =
|
def update(
|
||||||
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
|
task: UserTask[String]
|
||||||
|
): ConnectionIO[Int] =
|
||||||
for {
|
for {
|
||||||
r <- task.toPeriodicTask[ConnectionIO](scope)
|
r <- task.toPeriodicTask[ConnectionIO](scope, subject)
|
||||||
n <- RPeriodicTask.update(r)
|
n <- RPeriodicTask.update(r)
|
||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import docspell.common._
|
|||||||
import docspell.store.qb.DSL._
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
import com.github.eikek.calev._
|
|
||||||
import doobie._
|
import doobie._
|
||||||
import doobie.implicits._
|
import doobie.implicits._
|
||||||
|
|
||||||
@ -75,16 +74,15 @@ object RCollective {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
now <- Timestamp.current[ConnectionIO]
|
now <- Timestamp.current[ConnectionIO]
|
||||||
cls = settings.classifier.map(_.toRecord(cid, now))
|
n2 <- settings.classifier match {
|
||||||
n2 <- cls match {
|
case Some(cls) =>
|
||||||
case Some(cr) =>
|
RClassifierSetting.update(cls.toRecord(cid, now))
|
||||||
RClassifierSetting.update(cr)
|
|
||||||
case None =>
|
case None =>
|
||||||
RClassifierSetting.delete(cid)
|
RClassifierSetting.delete(cid)
|
||||||
}
|
}
|
||||||
n3 <- settings.emptyTrash match {
|
n3 <- settings.emptyTrash match {
|
||||||
case Some(trashSchedule) =>
|
case Some(trash) =>
|
||||||
REmptyTrashSetting.update(REmptyTrashSetting(cid, trashSchedule, now))
|
REmptyTrashSetting.update(trash.toRecord(cid, now))
|
||||||
case None =>
|
case None =>
|
||||||
REmptyTrashSetting.delete(cid)
|
REmptyTrashSetting.delete(cid)
|
||||||
}
|
}
|
||||||
@ -114,7 +112,8 @@ object RCollective {
|
|||||||
cs.itemCount.s,
|
cs.itemCount.s,
|
||||||
cs.categories.s,
|
cs.categories.s,
|
||||||
cs.listType.s,
|
cs.listType.s,
|
||||||
es.schedule.s
|
es.schedule.s,
|
||||||
|
es.minAge.s
|
||||||
),
|
),
|
||||||
from(c).leftJoin(cs, cs.cid === c.id).leftJoin(es, es.cid === c.id),
|
from(c).leftJoin(cs, cs.cid === c.id).leftJoin(es, es.cid === c.id),
|
||||||
c.id === coll
|
c.id === coll
|
||||||
@ -168,7 +167,7 @@ object RCollective {
|
|||||||
language: Language,
|
language: Language,
|
||||||
integrationEnabled: Boolean,
|
integrationEnabled: Boolean,
|
||||||
classifier: Option[RClassifierSetting.Classifier],
|
classifier: Option[RClassifierSetting.Classifier],
|
||||||
emptyTrash: Option[CalEvent]
|
emptyTrash: Option[REmptyTrashSetting.EmptyTrash]
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import doobie.implicits._
|
|||||||
final case class REmptyTrashSetting(
|
final case class REmptyTrashSetting(
|
||||||
cid: Ident,
|
cid: Ident,
|
||||||
schedule: CalEvent,
|
schedule: CalEvent,
|
||||||
|
minAge: Duration,
|
||||||
created: Timestamp
|
created: Timestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,8 +32,9 @@ object REmptyTrashSetting {
|
|||||||
|
|
||||||
val cid = Column[Ident]("cid", this)
|
val cid = Column[Ident]("cid", this)
|
||||||
val schedule = Column[CalEvent]("schedule", this)
|
val schedule = Column[CalEvent]("schedule", this)
|
||||||
|
val minAge = Column[Duration]("min_age", this)
|
||||||
val created = Column[Timestamp]("created", this)
|
val created = Column[Timestamp]("created", this)
|
||||||
val all = NonEmptyList.of[Column[_]](cid, schedule, created)
|
val all = NonEmptyList.of[Column[_]](cid, schedule, minAge, created)
|
||||||
}
|
}
|
||||||
|
|
||||||
val T = Table(None)
|
val T = Table(None)
|
||||||
@ -43,7 +45,7 @@ object REmptyTrashSetting {
|
|||||||
DML.insert(
|
DML.insert(
|
||||||
T,
|
T,
|
||||||
T.all,
|
T.all,
|
||||||
fr"${v.cid},${v.schedule},${v.created}"
|
fr"${v.cid},${v.schedule},${v.minAge},${v.created}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(v: REmptyTrashSetting): ConnectionIO[Int] =
|
def update(v: REmptyTrashSetting): ConnectionIO[Int] =
|
||||||
@ -52,7 +54,8 @@ object REmptyTrashSetting {
|
|||||||
T,
|
T,
|
||||||
T.cid === v.cid,
|
T.cid === v.cid,
|
||||||
DML.set(
|
DML.set(
|
||||||
T.schedule.setTo(v.schedule)
|
T.schedule.setTo(v.schedule),
|
||||||
|
T.minAge.setTo(v.minAge)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO]
|
n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO]
|
||||||
@ -64,7 +67,7 @@ object REmptyTrashSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def findForAllCollectives(
|
def findForAllCollectives(
|
||||||
default: CalEvent,
|
default: EmptyTrash,
|
||||||
chunkSize: Int
|
chunkSize: Int
|
||||||
): Stream[ConnectionIO, REmptyTrashSetting] = {
|
): Stream[ConnectionIO, REmptyTrashSetting] = {
|
||||||
val c = RCollective.as("c")
|
val c = RCollective.as("c")
|
||||||
@ -72,7 +75,8 @@ object REmptyTrashSetting {
|
|||||||
val sql = run(
|
val sql = run(
|
||||||
select(
|
select(
|
||||||
c.id.s,
|
c.id.s,
|
||||||
coalesce(e.schedule.s, const(default)).s,
|
coalesce(e.schedule.s, const(default.schedule)).s,
|
||||||
|
coalesce(e.minAge.s, const(default.minAge)).s,
|
||||||
coalesce(e.created.s, c.created.s).s
|
coalesce(e.created.s, c.created.s).s
|
||||||
),
|
),
|
||||||
from(c).leftJoin(e, e.cid === c.id)
|
from(c).leftJoin(e, e.cid === c.id)
|
||||||
@ -83,4 +87,13 @@ object REmptyTrashSetting {
|
|||||||
def delete(coll: Ident): ConnectionIO[Int] =
|
def delete(coll: Ident): ConnectionIO[Int] =
|
||||||
DML.delete(T, T.cid === coll)
|
DML.delete(T, T.cid === coll)
|
||||||
|
|
||||||
|
final case class EmptyTrash(schedule: CalEvent, minAge: Duration) {
|
||||||
|
def toRecord(coll: Ident, created: Timestamp): REmptyTrashSetting =
|
||||||
|
REmptyTrashSetting(coll, schedule, minAge, created)
|
||||||
|
}
|
||||||
|
object EmptyTrash {
|
||||||
|
val default = EmptyTrash(EmptyTrashArgs.defaultSchedule, Duration.days(7))
|
||||||
|
def fromRecord(r: REmptyTrashSetting): EmptyTrash =
|
||||||
|
EmptyTrash(r.schedule, r.minAge)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,8 +389,16 @@ object RItem {
|
|||||||
def findById(itemId: Ident): ConnectionIO[Option[RItem]] =
|
def findById(itemId: Ident): ConnectionIO[Option[RItem]] =
|
||||||
run(select(T.all), from(T), T.id === itemId).query[RItem].option
|
run(select(T.all), from(T), T.id === itemId).query[RItem].option
|
||||||
|
|
||||||
def findDeleted(collective: Ident, chunkSize: Int): Stream[ConnectionIO, RItem] =
|
def findDeleted(
|
||||||
run(select(T.all), from(T), T.cid === collective && T.state === ItemState.deleted)
|
collective: Ident,
|
||||||
|
maxUpdated: Timestamp,
|
||||||
|
chunkSize: Int
|
||||||
|
): Stream[ConnectionIO, RItem] =
|
||||||
|
run(
|
||||||
|
select(T.all),
|
||||||
|
from(T),
|
||||||
|
T.cid === collective && T.state === ItemState.deleted && T.updated < maxUpdated
|
||||||
|
)
|
||||||
.query[RItem]
|
.query[RItem]
|
||||||
.streamWithChunkSize(chunkSize)
|
.streamWithChunkSize(chunkSize)
|
||||||
|
|
||||||
|
@ -43,7 +43,8 @@ object UserTask {
|
|||||||
.map(a => ut.copy(args = a))
|
.map(a => ut.copy(args = a))
|
||||||
|
|
||||||
def toPeriodicTask[F[_]: Sync](
|
def toPeriodicTask[F[_]: Sync](
|
||||||
scope: UserTaskScope
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String]
|
||||||
): F[RPeriodicTask] =
|
): F[RPeriodicTask] =
|
||||||
RPeriodicTask
|
RPeriodicTask
|
||||||
.create[F](
|
.create[F](
|
||||||
@ -51,7 +52,7 @@ object UserTask {
|
|||||||
scope,
|
scope,
|
||||||
ut.name,
|
ut.name,
|
||||||
ut.args,
|
ut.args,
|
||||||
s"${scope.fold(_.user.id, _.id)}: ${ut.name.id}",
|
subject.getOrElse(s"${scope.fold(_.user.id, _.id)}: ${ut.name.id}"),
|
||||||
Priority.Low,
|
Priority.Low,
|
||||||
ut.timer,
|
ut.timer,
|
||||||
ut.summary
|
ut.summary
|
||||||
|
@ -61,7 +61,9 @@ trait UserTaskStore[F[_]] {
|
|||||||
* exists, a new one is created. Otherwise the existing task is
|
* exists, a new one is created. Otherwise the existing task is
|
||||||
* updated.
|
* updated.
|
||||||
*/
|
*/
|
||||||
def updateTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit E: Encoder[A]): F[Int]
|
def updateTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||||
|
implicit E: Encoder[A]
|
||||||
|
): F[Int]
|
||||||
|
|
||||||
/** Delete the task with the given id of the given user.
|
/** Delete the task with the given id of the given user.
|
||||||
*/
|
*/
|
||||||
@ -92,8 +94,8 @@ trait UserTaskStore[F[_]] {
|
|||||||
* the user `account`, they will all be removed and the given task
|
* the user `account`, they will all be removed and the given task
|
||||||
* inserted!
|
* inserted!
|
||||||
*/
|
*/
|
||||||
def updateOneTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
def updateOneTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||||
E: Encoder[A]
|
implicit E: Encoder[A]
|
||||||
): F[UserTask[String]]
|
): F[UserTask[String]]
|
||||||
|
|
||||||
/** Delete all tasks of the given user that have name `name'.
|
/** Delete all tasks of the given user that have name `name'.
|
||||||
@ -123,16 +125,16 @@ object UserTaskStore {
|
|||||||
case Left(err) => Stream.raiseError[F](new Exception(err))
|
case Left(err) => Stream.raiseError[F](new Exception(err))
|
||||||
})
|
})
|
||||||
|
|
||||||
def updateTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
def updateTask[A](scope: UserTaskScope, subject: Option[String], ut: UserTask[A])(
|
||||||
E: Encoder[A]
|
implicit E: Encoder[A]
|
||||||
): F[Int] = {
|
): F[Int] = {
|
||||||
val exists = QUserTask.exists(ut.id)
|
val exists = QUserTask.exists(ut.id)
|
||||||
val insert = QUserTask.insert(scope, ut.encode)
|
val insert = QUserTask.insert(scope, subject, ut.encode)
|
||||||
store.add(insert, exists).flatMap {
|
store.add(insert, exists).flatMap {
|
||||||
case AddResult.Success =>
|
case AddResult.Success =>
|
||||||
1.pure[F]
|
1.pure[F]
|
||||||
case AddResult.EntityExists(_) =>
|
case AddResult.EntityExists(_) =>
|
||||||
store.transact(QUserTask.update(scope, ut.encode))
|
store.transact(QUserTask.update(scope, subject, ut.encode))
|
||||||
case AddResult.Failure(ex) =>
|
case AddResult.Failure(ex) =>
|
||||||
Async[F].raiseError(ex)
|
Async[F].raiseError(ex)
|
||||||
}
|
}
|
||||||
@ -166,21 +168,25 @@ object UserTaskStore {
|
|||||||
case Left(err) => Async[F].raiseError(new Exception(err))
|
case Left(err) => Async[F].raiseError(new Exception(err))
|
||||||
})
|
})
|
||||||
|
|
||||||
def updateOneTask[A](scope: UserTaskScope, ut: UserTask[A])(implicit
|
def updateOneTask[A](
|
||||||
|
scope: UserTaskScope,
|
||||||
|
subject: Option[String],
|
||||||
|
ut: UserTask[A]
|
||||||
|
)(implicit
|
||||||
E: Encoder[A]
|
E: Encoder[A]
|
||||||
): F[UserTask[String]] =
|
): F[UserTask[String]] =
|
||||||
getByNameRaw(scope, ut.name).compile.toList.flatMap {
|
getByNameRaw(scope, ut.name).compile.toList.flatMap {
|
||||||
case a :: rest =>
|
case a :: rest =>
|
||||||
val task = ut.copy(id = a.id).encode
|
val task = ut.copy(id = a.id).encode
|
||||||
for {
|
for {
|
||||||
_ <- store.transact(QUserTask.update(scope, task))
|
_ <- store.transact(QUserTask.update(scope, subject, task))
|
||||||
_ <- store.transact(
|
_ <- store.transact(
|
||||||
rest.traverse(t => QUserTask.delete(scope.toAccountId, t.id))
|
rest.traverse(t => QUserTask.delete(scope.toAccountId, t.id))
|
||||||
)
|
)
|
||||||
} yield task
|
} yield task
|
||||||
case Nil =>
|
case Nil =>
|
||||||
val task = ut.encode
|
val task = ut.encode
|
||||||
store.transact(QUserTask.insert(scope, task)).map(_ => task)
|
store.transact(QUserTask.insert(scope, subject, task)).map(_ => task)
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteAll(scope: UserTaskScope, name: Ident): F[Int] =
|
def deleteAll(scope: UserTaskScope, name: Ident): F[Int] =
|
||||||
|
@ -158,6 +158,7 @@ import Api.Model.CustomFieldValue exposing (CustomFieldValue)
|
|||||||
import Api.Model.DirectionValue exposing (DirectionValue)
|
import Api.Model.DirectionValue exposing (DirectionValue)
|
||||||
import Api.Model.EmailSettings exposing (EmailSettings)
|
import Api.Model.EmailSettings exposing (EmailSettings)
|
||||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||||
|
import Api.Model.EmptyTrashSetting exposing (EmptyTrashSetting)
|
||||||
import Api.Model.Equipment exposing (Equipment)
|
import Api.Model.Equipment exposing (Equipment)
|
||||||
import Api.Model.EquipmentList exposing (EquipmentList)
|
import Api.Model.EquipmentList exposing (EquipmentList)
|
||||||
import Api.Model.FolderDetail exposing (FolderDetail)
|
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||||
@ -999,13 +1000,14 @@ startClassifier flags receive =
|
|||||||
|
|
||||||
startEmptyTrash :
|
startEmptyTrash :
|
||||||
Flags
|
Flags
|
||||||
|
-> EmptyTrashSetting
|
||||||
-> (Result Http.Error BasicResult -> msg)
|
-> (Result Http.Error BasicResult -> msg)
|
||||||
-> Cmd msg
|
-> Cmd msg
|
||||||
startEmptyTrash flags receive =
|
startEmptyTrash flags setting receive =
|
||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/collective/emptytrash/startonce"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/collective/emptytrash/startonce"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.emptyBody
|
, body = Http.jsonBody (Api.Model.EmptyTrashSetting.encode setting)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import Comp.ClassifierSettingsForm
|
|||||||
import Comp.Dropdown
|
import Comp.Dropdown
|
||||||
import Comp.EmptyTrashForm
|
import Comp.EmptyTrashForm
|
||||||
import Comp.MenuBar as MB
|
import Comp.MenuBar as MB
|
||||||
import Data.CalEvent
|
|
||||||
import Data.DropdownStyle as DS
|
import Data.DropdownStyle as DS
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.Language exposing (Language)
|
import Data.Language exposing (Language)
|
||||||
@ -54,11 +53,14 @@ type ClassifierResult
|
|||||||
| ClassifierResultSubmitError String
|
| ClassifierResultSubmitError String
|
||||||
| ClassifierResultOk
|
| ClassifierResultOk
|
||||||
|
|
||||||
|
|
||||||
type EmptyTrashResult
|
type EmptyTrashResult
|
||||||
= EmptyTrashResultInitial
|
= EmptyTrashResultInitial
|
||||||
| EmptyTrashResultHttpError Http.Error
|
| EmptyTrashResultHttpError Http.Error
|
||||||
| EmptyTrashResultSubmitError String
|
| EmptyTrashResultSubmitError String
|
||||||
| EmptyTrashResultOk
|
| EmptyTrashResultOk
|
||||||
|
| EmptyTrashResultInvalidForm
|
||||||
|
|
||||||
|
|
||||||
type FulltextReindexResult
|
type FulltextReindexResult
|
||||||
= FulltextReindexInitial
|
= FulltextReindexInitial
|
||||||
@ -79,7 +81,7 @@ init flags settings =
|
|||||||
Comp.ClassifierSettingsForm.init flags settings.classifier
|
Comp.ClassifierSettingsForm.init flags settings.classifier
|
||||||
|
|
||||||
( em, ec ) =
|
( em, ec ) =
|
||||||
Comp.EmptyTrashForm.init flags settings.emptyTrashSchedule
|
Comp.EmptyTrashForm.init flags settings.emptyTrash
|
||||||
in
|
in
|
||||||
( { langModel =
|
( { langModel =
|
||||||
Comp.Dropdown.makeSingleList
|
Comp.Dropdown.makeSingleList
|
||||||
@ -101,24 +103,21 @@ init flags settings =
|
|||||||
|
|
||||||
getSettings : Model -> Maybe CollectiveSettings
|
getSettings : Model -> Maybe CollectiveSettings
|
||||||
getSettings model =
|
getSettings model =
|
||||||
Maybe.map
|
Maybe.map2
|
||||||
(\cls ->
|
(\cls ->
|
||||||
{ language =
|
\trash ->
|
||||||
Comp.Dropdown.getSelected model.langModel
|
{ language =
|
||||||
|> List.head
|
Comp.Dropdown.getSelected model.langModel
|
||||||
|> Maybe.map Data.Language.toIso3
|
|> List.head
|
||||||
|> Maybe.withDefault model.initSettings.language
|
|> Maybe.map Data.Language.toIso3
|
||||||
, integrationEnabled = model.intEnabled
|
|> Maybe.withDefault model.initSettings.language
|
||||||
, classifier = cls
|
, integrationEnabled = model.intEnabled
|
||||||
, emptyTrashSchedule =
|
, classifier = cls
|
||||||
Comp.EmptyTrashForm.getSettings model.emptyTrashModel
|
, emptyTrash = trash
|
||||||
|> Maybe.withDefault Data.CalEvent.everyMonth
|
}
|
||||||
|> Data.CalEvent.makeEvent
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Comp.ClassifierSettingsForm.getSettings
|
|
||||||
model.classifierModel
|
|
||||||
)
|
)
|
||||||
|
(Comp.ClassifierSettingsForm.getSettings model.classifierModel)
|
||||||
|
(Comp.EmptyTrashForm.getSettings model.emptyTrashModel)
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
@ -233,8 +232,20 @@ update flags msg model =
|
|||||||
( model, Api.startClassifier flags StartClassifierResp, Nothing )
|
( model, Api.startClassifier flags StartClassifierResp, Nothing )
|
||||||
|
|
||||||
StartEmptyTrashTask ->
|
StartEmptyTrashTask ->
|
||||||
( model, Api.startEmptyTrash flags StartEmptyTrashResp, Nothing )
|
case getSettings model of
|
||||||
|
Just settings ->
|
||||||
|
( model
|
||||||
|
, Api.startEmptyTrash flags
|
||||||
|
settings.emptyTrash
|
||||||
|
StartEmptyTrashResp
|
||||||
|
, Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( { model | startEmptyTrashResult = EmptyTrashResultInvalidForm }
|
||||||
|
, Cmd.none
|
||||||
|
, Nothing
|
||||||
|
)
|
||||||
|
|
||||||
StartClassifierResp (Ok br) ->
|
StartClassifierResp (Ok br) ->
|
||||||
( { model
|
( { model
|
||||||
@ -275,6 +286,7 @@ update flags msg model =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- View2
|
--- View2
|
||||||
|
|
||||||
|
|
||||||
@ -471,6 +483,7 @@ renderClassifierResultMessage texts result =
|
|||||||
, ( S.successMessage, isSuccess )
|
, ( S.successMessage, isSuccess )
|
||||||
, ( "hidden", result == ClassifierResultInitial )
|
, ( "hidden", result == ClassifierResultInitial )
|
||||||
]
|
]
|
||||||
|
, class "ml-2"
|
||||||
]
|
]
|
||||||
[ case result of
|
[ case result of
|
||||||
ClassifierResultInitial ->
|
ClassifierResultInitial ->
|
||||||
@ -505,6 +518,7 @@ renderFulltextReindexResultMessage texts result =
|
|||||||
FulltextReindexSubmitError m ->
|
FulltextReindexSubmitError m ->
|
||||||
text m
|
text m
|
||||||
|
|
||||||
|
|
||||||
renderEmptyTrashResultMessage : Texts -> EmptyTrashResult -> Html msg
|
renderEmptyTrashResultMessage : Texts -> EmptyTrashResult -> Html msg
|
||||||
renderEmptyTrashResultMessage texts result =
|
renderEmptyTrashResultMessage texts result =
|
||||||
let
|
let
|
||||||
@ -525,6 +539,7 @@ renderEmptyTrashResultMessage texts result =
|
|||||||
, ( S.successMessage, isSuccess )
|
, ( S.successMessage, isSuccess )
|
||||||
, ( "hidden", result == EmptyTrashResultInitial )
|
, ( "hidden", result == EmptyTrashResultInitial )
|
||||||
]
|
]
|
||||||
|
, class "ml-2"
|
||||||
]
|
]
|
||||||
[ case result of
|
[ case result of
|
||||||
EmptyTrashResultInitial ->
|
EmptyTrashResultInitial ->
|
||||||
@ -538,4 +553,7 @@ renderEmptyTrashResultMessage texts result =
|
|||||||
|
|
||||||
EmptyTrashResultSubmitError m ->
|
EmptyTrashResultSubmitError m ->
|
||||||
text m
|
text m
|
||||||
|
|
||||||
|
EmptyTrashResultInvalidForm ->
|
||||||
|
text texts.emptyTrashStartInvalidForm
|
||||||
]
|
]
|
||||||
|
@ -14,40 +14,36 @@ module Comp.EmptyTrashForm exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
import Api
|
import Api.Model.EmptyTrashSetting exposing (EmptyTrashSetting)
|
||||||
import Comp.CalEventInput
|
import Comp.CalEventInput
|
||||||
import Comp.Dropdown
|
|
||||||
import Comp.FixedDropdown
|
|
||||||
import Comp.IntField
|
import Comp.IntField
|
||||||
import Data.CalEvent exposing (CalEvent)
|
import Data.CalEvent exposing (CalEvent)
|
||||||
import Data.DropdownStyle as DS
|
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.ListType exposing (ListType)
|
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Http
|
|
||||||
import Markdown
|
|
||||||
import Messages.Comp.EmptyTrashForm exposing (Texts)
|
import Messages.Comp.EmptyTrashForm exposing (Texts)
|
||||||
import Styles as S
|
import Styles as S
|
||||||
import Util.Tag
|
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ scheduleModel : Comp.CalEventInput.Model
|
{ scheduleModel : Comp.CalEventInput.Model
|
||||||
, schedule : Maybe CalEvent
|
, schedule : Maybe CalEvent
|
||||||
|
, minAgeModel : Comp.IntField.Model
|
||||||
|
, minAgeDays : Maybe Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= ScheduleMsg Comp.CalEventInput.Msg
|
= ScheduleMsg Comp.CalEventInput.Msg
|
||||||
|
| MinAgeMsg Comp.IntField.Msg
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> String -> ( Model, Cmd Msg )
|
init : Flags -> EmptyTrashSetting -> ( Model, Cmd Msg )
|
||||||
init flags schedule =
|
init flags settings =
|
||||||
let
|
let
|
||||||
newSchedule =
|
newSchedule =
|
||||||
Data.CalEvent.fromEvent schedule
|
Data.CalEvent.fromEvent settings.schedule
|
||||||
|> Maybe.withDefault Data.CalEvent.everyMonth
|
|> Maybe.withDefault Data.CalEvent.everyMonth
|
||||||
|
|
||||||
( cem, cec ) =
|
( cem, cec ) =
|
||||||
@ -55,14 +51,34 @@ init flags schedule =
|
|||||||
in
|
in
|
||||||
( { scheduleModel = cem
|
( { scheduleModel = cem
|
||||||
, schedule = Just newSchedule
|
, schedule = Just newSchedule
|
||||||
|
, minAgeModel = Comp.IntField.init (Just 0) Nothing False
|
||||||
|
, minAgeDays = Just <| millisToDays settings.minAge
|
||||||
}
|
}
|
||||||
, Cmd.map ScheduleMsg cec
|
, Cmd.map ScheduleMsg cec
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
getSettings : Model -> Maybe CalEvent
|
millisToDays : Int -> Int
|
||||||
|
millisToDays millis =
|
||||||
|
round <| toFloat millis / 1000 / 60 / 60 / 24
|
||||||
|
|
||||||
|
|
||||||
|
daysToMillis : Int -> Int
|
||||||
|
daysToMillis days =
|
||||||
|
days * 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
|
||||||
|
getSettings : Model -> Maybe EmptyTrashSetting
|
||||||
getSettings model =
|
getSettings model =
|
||||||
model.schedule
|
Maybe.map2
|
||||||
|
(\sch ->
|
||||||
|
\age ->
|
||||||
|
{ schedule = Data.CalEvent.makeEvent sch
|
||||||
|
, minAge = daysToMillis age
|
||||||
|
}
|
||||||
|
)
|
||||||
|
model.schedule
|
||||||
|
model.minAgeDays
|
||||||
|
|
||||||
|
|
||||||
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
@ -84,6 +100,18 @@ update flags msg model =
|
|||||||
, Cmd.map ScheduleMsg cc
|
, Cmd.map ScheduleMsg cc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MinAgeMsg lmsg ->
|
||||||
|
let
|
||||||
|
( mm, newAge ) =
|
||||||
|
Comp.IntField.update lmsg model.minAgeModel
|
||||||
|
in
|
||||||
|
( { model
|
||||||
|
| minAgeModel = mm
|
||||||
|
, minAgeDays = newAge
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- View2
|
--- View2
|
||||||
@ -103,4 +131,16 @@ view texts _ model =
|
|||||||
model.scheduleModel
|
model.scheduleModel
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
, div [ class "mb-4" ]
|
||||||
|
[ let
|
||||||
|
settings : Comp.IntField.ViewSettings
|
||||||
|
settings =
|
||||||
|
{ number = model.minAgeDays
|
||||||
|
, label = texts.minAge
|
||||||
|
, classes = ""
|
||||||
|
, info = texts.minAgeInfo
|
||||||
|
}
|
||||||
|
in
|
||||||
|
Html.map MinAgeMsg (Comp.IntField.view settings model.minAgeModel)
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
@ -47,13 +47,13 @@ init min max opt =
|
|||||||
tooLow : Model -> Int -> Bool
|
tooLow : Model -> Int -> Bool
|
||||||
tooLow model n =
|
tooLow model n =
|
||||||
Maybe.map ((<) n) model.min
|
Maybe.map ((<) n) model.min
|
||||||
|> Maybe.withDefault (not model.optional)
|
|> Maybe.withDefault False
|
||||||
|
|
||||||
|
|
||||||
tooHigh : Model -> Int -> Bool
|
tooHigh : Model -> Int -> Bool
|
||||||
tooHigh model n =
|
tooHigh model n =
|
||||||
Maybe.map ((>) n) model.max
|
Maybe.map ((>) n) model.max
|
||||||
|> Maybe.withDefault (not model.optional)
|
|> Maybe.withDefault False
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> ( Model, Maybe Int )
|
update : Msg -> Model -> ( Model, Maybe Int )
|
||||||
|
@ -40,6 +40,7 @@ type alias Texts =
|
|||||||
, languageLabel : Language -> String
|
, languageLabel : Language -> String
|
||||||
, classifierTaskStarted : String
|
, classifierTaskStarted : String
|
||||||
, emptyTrashTaskStarted : String
|
, emptyTrashTaskStarted : String
|
||||||
|
, emptyTrashStartInvalidForm : String
|
||||||
, fulltextReindexSubmitted : String
|
, fulltextReindexSubmitted : String
|
||||||
, fulltextReindexOkMissing : String
|
, fulltextReindexOkMissing : String
|
||||||
, emptyTrash : String
|
, emptyTrash : String
|
||||||
@ -71,6 +72,7 @@ gb =
|
|||||||
, languageLabel = Messages.Data.Language.gb
|
, languageLabel = Messages.Data.Language.gb
|
||||||
, classifierTaskStarted = "Classifier task started."
|
, classifierTaskStarted = "Classifier task started."
|
||||||
, emptyTrashTaskStarted = "Empty trash task started."
|
, emptyTrashTaskStarted = "Empty trash task started."
|
||||||
|
, emptyTrashStartInvalidForm = "The empty-trash form contains errors."
|
||||||
, fulltextReindexSubmitted = "Fulltext Re-Index started."
|
, fulltextReindexSubmitted = "Fulltext Re-Index started."
|
||||||
, fulltextReindexOkMissing =
|
, fulltextReindexOkMissing =
|
||||||
"Please type OK in the field if you really want to start re-indexing your data."
|
"Please type OK in the field if you really want to start re-indexing your data."
|
||||||
@ -103,6 +105,7 @@ de =
|
|||||||
, languageLabel = Messages.Data.Language.de
|
, languageLabel = Messages.Data.Language.de
|
||||||
, classifierTaskStarted = "Kategorisierung gestartet."
|
, classifierTaskStarted = "Kategorisierung gestartet."
|
||||||
, emptyTrashTaskStarted = "Papierkorb löschen gestartet."
|
, emptyTrashTaskStarted = "Papierkorb löschen gestartet."
|
||||||
|
, emptyTrashStartInvalidForm = "Das Papierkorb-Löschen Formular ist fehlerhaft!"
|
||||||
, fulltextReindexSubmitted = "Volltext Neu-Indexierung gestartet."
|
, fulltextReindexSubmitted = "Volltext Neu-Indexierung gestartet."
|
||||||
, fulltextReindexOkMissing =
|
, fulltextReindexOkMissing =
|
||||||
"Bitte tippe OK in das Feld ein, wenn Du wirklich den Index neu erzeugen möchtest."
|
"Bitte tippe OK in das Feld ein, wenn Du wirklich den Index neu erzeugen möchtest."
|
||||||
|
@ -19,6 +19,8 @@ type alias Texts =
|
|||||||
{ basics : Messages.Basics.Texts
|
{ basics : Messages.Basics.Texts
|
||||||
, calEventInput : Messages.Comp.CalEventInput.Texts
|
, calEventInput : Messages.Comp.CalEventInput.Texts
|
||||||
, schedule : String
|
, schedule : String
|
||||||
|
, minAge : String
|
||||||
|
, minAgeInfo : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +29,8 @@ gb =
|
|||||||
{ basics = Messages.Basics.gb
|
{ basics = Messages.Basics.gb
|
||||||
, calEventInput = Messages.Comp.CalEventInput.gb
|
, calEventInput = Messages.Comp.CalEventInput.gb
|
||||||
, schedule = "Schedule"
|
, schedule = "Schedule"
|
||||||
|
, minAge = "Minimum Age (Days)"
|
||||||
|
, minAgeInfo = "The minimum age in days of an items to be removed. The last-update time is used."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -35,4 +39,6 @@ de =
|
|||||||
{ basics = Messages.Basics.de
|
{ basics = Messages.Basics.de
|
||||||
, calEventInput = Messages.Comp.CalEventInput.de
|
, calEventInput = Messages.Comp.CalEventInput.de
|
||||||
, schedule = "Zeitplan"
|
, schedule = "Zeitplan"
|
||||||
|
, minAge = "Mindestalter (Tage)"
|
||||||
|
, minAgeInfo = "Das Mindestalter (in Tagen) der Dokumente, die gelöscht werden. Es wird das Datum der letzten Veränderung verwendet."
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import Comp.SearchMenu
|
|||||||
import Comp.SearchStatsView
|
import Comp.SearchStatsView
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.ItemSelection
|
import Data.ItemSelection
|
||||||
|
import Data.SearchMode
|
||||||
import Data.UiSettings exposing (UiSettings)
|
import Data.UiSettings exposing (UiSettings)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -78,6 +79,7 @@ confirmModal texts model =
|
|||||||
texts.basics.yes
|
texts.basics.yes
|
||||||
texts.basics.no
|
texts.basics.no
|
||||||
texts.reallyDeleteQuestion
|
texts.reallyDeleteQuestion
|
||||||
|
|
||||||
ConfirmRestore ->
|
ConfirmRestore ->
|
||||||
Comp.ConfirmModal.defaultSettings
|
Comp.ConfirmModal.defaultSettings
|
||||||
RestoreSelectedConfirmed
|
RestoreSelectedConfirmed
|
||||||
@ -85,7 +87,6 @@ confirmModal texts model =
|
|||||||
texts.basics.yes
|
texts.basics.yes
|
||||||
texts.basics.no
|
texts.basics.no
|
||||||
texts.reallyRestoreQuestion
|
texts.reallyRestoreQuestion
|
||||||
|
|
||||||
in
|
in
|
||||||
case model.viewMode of
|
case model.viewMode of
|
||||||
SelectView svm ->
|
SelectView svm ->
|
||||||
@ -270,6 +271,7 @@ editMenuBar texts model svm =
|
|||||||
, inputClass =
|
, inputClass =
|
||||||
[ ( btnStyle, True )
|
[ ( btnStyle, True )
|
||||||
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == DeleteSelected )
|
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == DeleteSelected )
|
||||||
|
, ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Trashed )
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
, MB.CustomButton
|
, MB.CustomButton
|
||||||
@ -280,6 +282,7 @@ editMenuBar texts model svm =
|
|||||||
, inputClass =
|
, inputClass =
|
||||||
[ ( btnStyle, True )
|
[ ( btnStyle, True )
|
||||||
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == RestoreSelected )
|
, ( "bg-gray-200 dark:bg-bluegray-600", svm.action == RestoreSelected )
|
||||||
|
, ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Normal )
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user