mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-02 13:32:51 +00:00
Editing tags for multiple items
This commit is contained in:
parent
5735a47199
commit
7ad37c8d26
@ -1,5 +1,6 @@
|
|||||||
package docspell.backend.ops
|
package docspell.backend.ops
|
||||||
|
|
||||||
|
import cats.data.NonEmptyList
|
||||||
import cats.data.OptionT
|
import cats.data.OptionT
|
||||||
import cats.effect.{Effect, Resource}
|
import cats.effect.{Effect, Resource}
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
@ -13,21 +14,38 @@ import docspell.store.queue.JobQueue
|
|||||||
import docspell.store.records._
|
import docspell.store.records._
|
||||||
import docspell.store.{AddResult, Store}
|
import docspell.store.{AddResult, Store}
|
||||||
|
|
||||||
import doobie._
|
|
||||||
import doobie.implicits._
|
import doobie.implicits._
|
||||||
import org.log4s.getLogger
|
import org.log4s.getLogger
|
||||||
|
|
||||||
trait OItem[F[_]] {
|
trait OItem[F[_]] {
|
||||||
|
|
||||||
/** Sets the given tags (removing all existing ones). */
|
/** Sets the given tags (removing all existing ones). */
|
||||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult]
|
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[UpdateResult]
|
||||||
|
|
||||||
|
/** Sets tags for multiple items. The tags of the items will be
|
||||||
|
* replaced with the given ones. Same as `setTags` but for multiple
|
||||||
|
* items.
|
||||||
|
*/
|
||||||
|
def setTagsMultipleItems(
|
||||||
|
items: NonEmptyList[Ident],
|
||||||
|
tags: List[Ident],
|
||||||
|
collective: Ident
|
||||||
|
): F[UpdateResult]
|
||||||
|
|
||||||
/** Create a new tag and add it to the item. */
|
/** Create a new tag and add it to the item. */
|
||||||
def addNewTag(item: Ident, tag: RTag): F[AddResult]
|
def addNewTag(item: Ident, tag: RTag): F[AddResult]
|
||||||
|
|
||||||
/** Apply all tags to the given item. Tags must exist, but can be IDs or names. */
|
/** Apply all tags to the given item. Tags must exist, but can be IDs
|
||||||
|
* or names. Existing tags on the item are left unchanged.
|
||||||
|
*/
|
||||||
def linkTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
def linkTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
||||||
|
|
||||||
|
def linkTagsMultipleItems(
|
||||||
|
items: NonEmptyList[Ident],
|
||||||
|
tags: List[String],
|
||||||
|
collective: Ident
|
||||||
|
): F[UpdateResult]
|
||||||
|
|
||||||
/** Toggles tags of the given item. Tags must exist, but can be IDs or names. */
|
/** Toggles tags of the given item. Tags must exist, but can be IDs or names. */
|
||||||
def toggleTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
def toggleTags(item: Ident, tags: List[String], collective: Ident): F[UpdateResult]
|
||||||
|
|
||||||
@ -55,7 +73,14 @@ trait OItem[F[_]] {
|
|||||||
|
|
||||||
def setName(item: Ident, name: String, collective: Ident): F[AddResult]
|
def setName(item: Ident, name: String, collective: Ident): F[AddResult]
|
||||||
|
|
||||||
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult]
|
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
|
||||||
|
setStates(NonEmptyList.of(item), state, collective)
|
||||||
|
|
||||||
|
def setStates(
|
||||||
|
item: NonEmptyList[Ident],
|
||||||
|
state: ItemState,
|
||||||
|
collective: Ident
|
||||||
|
): F[AddResult]
|
||||||
|
|
||||||
def setItemDate(item: Ident, date: Option[Timestamp], collective: Ident): F[AddResult]
|
def setItemDate(item: Ident, date: Option[Timestamp], collective: Ident): F[AddResult]
|
||||||
|
|
||||||
@ -130,21 +155,30 @@ object OItem {
|
|||||||
item: Ident,
|
item: Ident,
|
||||||
tags: List[String],
|
tags: List[String],
|
||||||
collective: Ident
|
collective: Ident
|
||||||
|
): F[UpdateResult] =
|
||||||
|
linkTagsMultipleItems(NonEmptyList.of(item), tags, collective)
|
||||||
|
|
||||||
|
def linkTagsMultipleItems(
|
||||||
|
items: NonEmptyList[Ident],
|
||||||
|
tags: List[String],
|
||||||
|
collective: Ident
|
||||||
): F[UpdateResult] =
|
): F[UpdateResult] =
|
||||||
tags.distinct match {
|
tags.distinct match {
|
||||||
case Nil => UpdateResult.success.pure[F]
|
case Nil => UpdateResult.success.pure[F]
|
||||||
case kws =>
|
case ws =>
|
||||||
val db =
|
store.transact {
|
||||||
(for {
|
(for {
|
||||||
_ <- OptionT(RItem.checkByIdAndCollective(item, collective))
|
itemIds <- OptionT
|
||||||
given <- OptionT.liftF(RTag.findAllByNameOrId(kws, collective))
|
.liftF(RItem.filterItems(items, collective))
|
||||||
exist <- OptionT.liftF(RTagItem.findAllIn(item, given.map(_.tagId)))
|
.filter(_.nonEmpty)
|
||||||
|
given <- OptionT.liftF(RTag.findAllByNameOrId(ws, collective))
|
||||||
_ <- OptionT.liftF(
|
_ <- OptionT.liftF(
|
||||||
RTagItem.setAllTags(item, given.map(_.tagId).diff(exist.map(_.tagId)))
|
itemIds.traverse(item =>
|
||||||
|
RTagItem.appendTags(item, given.map(_.tagId).toList)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} yield UpdateResult.success).getOrElse(UpdateResult.notFound)
|
} yield UpdateResult.success).getOrElse(UpdateResult.notFound)
|
||||||
|
}
|
||||||
store.transact(db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def toggleTags(
|
def toggleTags(
|
||||||
@ -169,20 +203,23 @@ object OItem {
|
|||||||
store.transact(db)
|
store.transact(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] = {
|
def setTags(
|
||||||
val db = for {
|
item: Ident,
|
||||||
cid <- RItem.getCollective(item)
|
tagIds: List[Ident],
|
||||||
nd <-
|
collective: Ident
|
||||||
if (cid.contains(collective)) RTagItem.deleteItemTags(item)
|
): F[UpdateResult] =
|
||||||
else 0.pure[ConnectionIO]
|
setTagsMultipleItems(NonEmptyList.of(item), tagIds, collective)
|
||||||
ni <-
|
|
||||||
if (tagIds.nonEmpty && cid.contains(collective))
|
|
||||||
RTagItem.insertItemTags(item, tagIds)
|
|
||||||
else 0.pure[ConnectionIO]
|
|
||||||
} yield nd + ni
|
|
||||||
|
|
||||||
store.transact(db).attempt.map(AddResult.fromUpdate)
|
def setTagsMultipleItems(
|
||||||
}
|
items: NonEmptyList[Ident],
|
||||||
|
tags: List[Ident],
|
||||||
|
collective: Ident
|
||||||
|
): F[UpdateResult] =
|
||||||
|
UpdateResult.fromUpdate(store.transact(for {
|
||||||
|
k <- RTagItem.deleteItemTags(items, collective)
|
||||||
|
res <- items.traverse(i => RTagItem.setAllTags(i, tags))
|
||||||
|
n = res.fold
|
||||||
|
} yield k + n))
|
||||||
|
|
||||||
def addNewTag(item: Ident, tag: RTag): F[AddResult] =
|
def addNewTag(item: Ident, tag: RTag): F[AddResult] =
|
||||||
(for {
|
(for {
|
||||||
@ -192,7 +229,7 @@ object OItem {
|
|||||||
_ <- addres match {
|
_ <- addres match {
|
||||||
case AddResult.Success =>
|
case AddResult.Success =>
|
||||||
OptionT.liftF(
|
OptionT.liftF(
|
||||||
store.transact(RTagItem.insertItemTags(item, List(tag.tagId)))
|
store.transact(RTagItem.setAllTags(item, List(tag.tagId)))
|
||||||
)
|
)
|
||||||
case AddResult.EntityExists(_) =>
|
case AddResult.EntityExists(_) =>
|
||||||
OptionT.pure[F](0)
|
OptionT.pure[F](0)
|
||||||
@ -371,9 +408,13 @@ object OItem {
|
|||||||
onSuccessIgnoreError(fts.updateItemName(logger, item, collective, name))
|
onSuccessIgnoreError(fts.updateItemName(logger, item, collective, name))
|
||||||
)
|
)
|
||||||
|
|
||||||
def setState(item: Ident, state: ItemState, collective: Ident): F[AddResult] =
|
def setStates(
|
||||||
|
items: NonEmptyList[Ident],
|
||||||
|
state: ItemState,
|
||||||
|
collective: Ident
|
||||||
|
): F[AddResult] =
|
||||||
store
|
store
|
||||||
.transact(RItem.updateStateForCollective(item, state, collective))
|
.transact(RItem.updateStateForCollective(items, state, collective))
|
||||||
.attempt
|
.attempt
|
||||||
.map(AddResult.fromUpdate)
|
.map(AddResult.fromUpdate)
|
||||||
|
|
||||||
|
@ -1927,7 +1927,10 @@ paths:
|
|||||||
- Item (Multi Edit)
|
- Item (Multi Edit)
|
||||||
summary: Add tags to multiple items
|
summary: Add tags to multiple items
|
||||||
description: |
|
description: |
|
||||||
Add the given tags to all given items.
|
Add the given tags to all given items. The tags that are
|
||||||
|
currently attached to the items are not changed. If there are
|
||||||
|
new tags in the given list, then they are added. Otherwise,
|
||||||
|
the item is left unchanged.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
requestBody:
|
requestBody:
|
||||||
@ -1948,7 +1951,7 @@ paths:
|
|||||||
summary: Sets tags to multiple items
|
summary: Sets tags to multiple items
|
||||||
description: |
|
description: |
|
||||||
Sets the given tags to all given items. If the tag list is
|
Sets the given tags to all given items. If the tag list is
|
||||||
empty, then tags are removed from the items.
|
empty, then all tags are removed from the items.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
requestBody:
|
requestBody:
|
||||||
|
@ -73,6 +73,7 @@ object RestServer {
|
|||||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||||
"item" -> ItemRoutes(cfg, restApp.backend, token),
|
"item" -> ItemRoutes(cfg, restApp.backend, token),
|
||||||
|
"items" -> ItemMultiRoutes(restApp.backend, token),
|
||||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token),
|
||||||
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
"checkfile" -> CheckFileRoutes.secured(restApp.backend, token),
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
package docspell.restserver.routes
|
||||||
|
|
||||||
|
import cats.ApplicativeError
|
||||||
|
import cats.MonadError
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.effect._
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
|
import docspell.backend.BackendApp
|
||||||
|
import docspell.backend.auth.AuthToken
|
||||||
|
import docspell.common.{Ident, ItemState}
|
||||||
|
import docspell.restapi.model._
|
||||||
|
import docspell.restserver.conv.Conversions
|
||||||
|
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import org.http4s.HttpRoutes
|
||||||
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
|
||||||
|
object ItemMultiRoutes {
|
||||||
|
// private[this] val logger = getLogger
|
||||||
|
|
||||||
|
def apply[F[_]: Effect](
|
||||||
|
backend: BackendApp[F],
|
||||||
|
user: AuthToken
|
||||||
|
): HttpRoutes[F] = {
|
||||||
|
val dsl = new Http4sDsl[F] {}
|
||||||
|
import dsl._
|
||||||
|
|
||||||
|
HttpRoutes.of {
|
||||||
|
case req @ PUT -> Root / "confirm" =>
|
||||||
|
for {
|
||||||
|
json <- req.as[IdList]
|
||||||
|
data <- readIds[F](json.ids)
|
||||||
|
res <- backend.item.setStates(
|
||||||
|
data,
|
||||||
|
ItemState.Confirmed,
|
||||||
|
user.account.collective
|
||||||
|
)
|
||||||
|
resp <- Ok(Conversions.basicResult(res, "Item data confirmed"))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case req @ PUT -> Root / "unconfirm" =>
|
||||||
|
for {
|
||||||
|
json <- req.as[IdList]
|
||||||
|
data <- readIds[F](json.ids)
|
||||||
|
res <- backend.item.setStates(
|
||||||
|
data,
|
||||||
|
ItemState.Created,
|
||||||
|
user.account.collective
|
||||||
|
)
|
||||||
|
resp <- Ok(Conversions.basicResult(res, "Item back to created."))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case req @ PUT -> Root / "tags" =>
|
||||||
|
for {
|
||||||
|
json <- req.as[ItemsAndRefs]
|
||||||
|
items <- readIds[F](json.items)
|
||||||
|
tags <- json.refs.traverse(readId[F])
|
||||||
|
res <- backend.item.setTagsMultipleItems(items, tags, user.account.collective)
|
||||||
|
resp <- Ok(Conversions.basicResult(res, "Tags updated"))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case req @ POST -> Root / "tags" =>
|
||||||
|
for {
|
||||||
|
json <- req.as[ItemsAndRefs]
|
||||||
|
items <- readIds[F](json.items)
|
||||||
|
res <- backend.item.linkTagsMultipleItems(
|
||||||
|
items,
|
||||||
|
json.refs,
|
||||||
|
user.account.collective
|
||||||
|
)
|
||||||
|
resp <- Ok(Conversions.basicResult(res, "Tags added."))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "direction" =>
|
||||||
|
// for {
|
||||||
|
// dir <- req.as[DirectionValue]
|
||||||
|
// res <- backend.item.setDirection(id, dir.direction, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Direction updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "folder" =>
|
||||||
|
// for {
|
||||||
|
// idref <- req.as[OptionalId]
|
||||||
|
// res <- backend.item.setFolder(id, idref.id, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Folder updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "corrOrg" =>
|
||||||
|
// for {
|
||||||
|
// idref <- req.as[OptionalId]
|
||||||
|
// res <- backend.item.setCorrOrg(id, idref.id, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "corrPerson" =>
|
||||||
|
// for {
|
||||||
|
// idref <- req.as[OptionalId]
|
||||||
|
// res <- backend.item.setCorrPerson(id, idref.id, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "concPerson" =>
|
||||||
|
// for {
|
||||||
|
// idref <- req.as[OptionalId]
|
||||||
|
// res <- backend.item.setConcPerson(id, idref.id, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Concerned person updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "concEquipment" =>
|
||||||
|
// for {
|
||||||
|
// idref <- req.as[OptionalId]
|
||||||
|
// res <- backend.item.setConcEquip(id, idref.id, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "name" =>
|
||||||
|
// for {
|
||||||
|
// text <- req.as[OptionalText]
|
||||||
|
// res <- backend.item.setName(
|
||||||
|
// id,
|
||||||
|
// text.text.notEmpty.getOrElse(""),
|
||||||
|
// user.account.collective
|
||||||
|
// )
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Name updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "duedate" =>
|
||||||
|
// for {
|
||||||
|
// date <- req.as[OptionalDate]
|
||||||
|
// _ <- logger.fdebug(s"Setting item due date to ${date.date}")
|
||||||
|
// res <- backend.item.setItemDueDate(id, date.date, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ PUT -> Root / "date" =>
|
||||||
|
// for {
|
||||||
|
// date <- req.as[OptionalDate]
|
||||||
|
// _ <- logger.fdebug(s"Setting item date to ${date.date}")
|
||||||
|
// res <- backend.item.setItemDate(id, date.date, user.account.collective)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Item date updated"))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case req @ POST -> Root / "reprocess" =>
|
||||||
|
// for {
|
||||||
|
// data <- req.as[IdList]
|
||||||
|
// ids = data.ids.flatMap(s => Ident.fromString(s).toOption)
|
||||||
|
// _ <- logger.fdebug(s"Re-process item ${id.id}")
|
||||||
|
// res <- backend.item.reprocess(id, ids, user.account, true)
|
||||||
|
// resp <- Ok(Conversions.basicResult(res, "Re-process task submitted."))
|
||||||
|
// } yield resp
|
||||||
|
|
||||||
|
// case POST -> Root / "deleteAll" =>
|
||||||
|
// for {
|
||||||
|
// n <- backend.item.deleteItem(id, user.account.collective)
|
||||||
|
// res = BasicResult(n > 0, if (n > 0) "Item deleted" else "Item deletion failed.")
|
||||||
|
// resp <- Ok(res)
|
||||||
|
// } yield resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit final class OptionString(opt: Option[String]) {
|
||||||
|
def notEmpty: Option[String] =
|
||||||
|
opt.map(_.trim).filter(_.nonEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def readId[F[_]](
|
||||||
|
id: String
|
||||||
|
)(implicit F: ApplicativeError[F, Throwable]): F[Ident] =
|
||||||
|
Ident
|
||||||
|
.fromString(id)
|
||||||
|
.fold(
|
||||||
|
err => F.raiseError(DecodingFailure(err, Nil)),
|
||||||
|
F.pure
|
||||||
|
)
|
||||||
|
|
||||||
|
private def readIds[F[_]](ids: List[String])(implicit
|
||||||
|
F: MonadError[F, Throwable]
|
||||||
|
): F[NonEmptyList[Ident]] =
|
||||||
|
ids.traverse(readId[F]).map(NonEmptyList.fromList).flatMap {
|
||||||
|
case Some(nel) => nel.pure[F]
|
||||||
|
case None =>
|
||||||
|
F.raiseError(
|
||||||
|
DecodingFailure("Empty list found, at least one element required", Nil)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,12 @@ case class Column(name: String, ns: String = "", alias: String = "") {
|
|||||||
f ++ fr"IN (" ++ commas(values) ++ fr")"
|
f ++ fr"IN (" ++ commas(values) ++ fr")"
|
||||||
|
|
||||||
def isIn[A: Put](values: NonEmptyList[A]): Fragment =
|
def isIn[A: Put](values: NonEmptyList[A]): Fragment =
|
||||||
isIn(values.map(a => sql"$a").toList)
|
values.tail match {
|
||||||
|
case Nil =>
|
||||||
|
is(values.head)
|
||||||
|
case _ =>
|
||||||
|
isIn(values.map(a => sql"$a").toList)
|
||||||
|
}
|
||||||
|
|
||||||
def isLowerIn[A: Put](values: NonEmptyList[A]): Fragment =
|
def isLowerIn[A: Put](values: NonEmptyList[A]): Fragment =
|
||||||
fr"lower(" ++ f ++ fr") IN (" ++ commas(values.map(a => sql"$a").toList) ++ fr")"
|
fr"lower(" ++ f ++ fr") IN (" ++ commas(values.map(a => sql"$a").toList) ++ fr")"
|
||||||
|
@ -132,7 +132,7 @@ object RItem {
|
|||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
def updateStateForCollective(
|
def updateStateForCollective(
|
||||||
itemId: Ident,
|
itemIds: NonEmptyList[Ident],
|
||||||
itemState: ItemState,
|
itemState: ItemState,
|
||||||
coll: Ident
|
coll: Ident
|
||||||
): ConnectionIO[Int] =
|
): ConnectionIO[Int] =
|
||||||
@ -140,7 +140,7 @@ object RItem {
|
|||||||
t <- currentTime
|
t <- currentTime
|
||||||
n <- updateRow(
|
n <- updateRow(
|
||||||
table,
|
table,
|
||||||
and(id.is(itemId), cid.is(coll)),
|
and(id.isIn(itemIds), cid.is(coll)),
|
||||||
commas(state.setTo(itemState), updated.setTo(t))
|
commas(state.setTo(itemState), updated.setTo(t))
|
||||||
).update.run
|
).update.run
|
||||||
} yield n
|
} yield n
|
||||||
@ -324,4 +324,10 @@ object RItem {
|
|||||||
val empty: Option[Ident] = None
|
val empty: Option[Ident] = None
|
||||||
updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
|
updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Fragment =
|
||||||
|
selectSimple(Seq(id), table, and(cid.is(coll), id.isIn(items)))
|
||||||
|
|
||||||
|
def filterItems(items: NonEmptyList[Ident], coll: Ident): ConnectionIO[Vector[Ident]] =
|
||||||
|
filterItemsFragment(items, coll).query[Ident].to[Vector]
|
||||||
}
|
}
|
||||||
|
@ -30,18 +30,17 @@ object RTagItem {
|
|||||||
def deleteItemTags(item: Ident): ConnectionIO[Int] =
|
def deleteItemTags(item: Ident): ConnectionIO[Int] =
|
||||||
deleteFrom(table, itemId.is(item)).update.run
|
deleteFrom(table, itemId.is(item)).update.run
|
||||||
|
|
||||||
|
def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = {
|
||||||
|
val itemsFiltered =
|
||||||
|
RItem.filterItemsFragment(items, cid)
|
||||||
|
val sql = fr"DELETE FROM" ++ table ++ fr"WHERE" ++ itemId.isIn(itemsFiltered)
|
||||||
|
|
||||||
|
sql.update.run
|
||||||
|
}
|
||||||
|
|
||||||
def deleteTag(tid: Ident): ConnectionIO[Int] =
|
def deleteTag(tid: Ident): ConnectionIO[Int] =
|
||||||
deleteFrom(table, tagId.is(tid)).update.run
|
deleteFrom(table, tagId.is(tid)).update.run
|
||||||
|
|
||||||
def insertItemTags(item: Ident, tags: Seq[Ident]): ConnectionIO[Int] =
|
|
||||||
for {
|
|
||||||
tagValues <- tags.toList.traverse(id =>
|
|
||||||
Ident.randomId[ConnectionIO].map(rid => RTagItem(rid, item, id))
|
|
||||||
)
|
|
||||||
tagFrag = tagValues.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
|
|
||||||
ins <- insertRows(table, all, tagFrag).update.run
|
|
||||||
} yield ins
|
|
||||||
|
|
||||||
def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
|
def findByItem(item: Ident): ConnectionIO[Vector[RTagItem]] =
|
||||||
selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
|
selectSimple(all, table, itemId.is(item)).query[RTagItem].to[Vector]
|
||||||
|
|
||||||
@ -76,4 +75,12 @@ object RTagItem {
|
|||||||
entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
|
entities.map(v => fr"${v.tagItemId},${v.itemId},${v.tagId}")
|
||||||
).update.run
|
).update.run
|
||||||
} yield n
|
} yield n
|
||||||
|
|
||||||
|
def appendTags(item: Ident, tags: List[Ident]): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
existing <- findByItem(item)
|
||||||
|
toadd = tags.toSet.diff(existing.map(_.tagId).toSet)
|
||||||
|
n <- setAllTags(item, toadd.toSeq)
|
||||||
|
} yield n
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ module Api exposing
|
|||||||
, addCorrPerson
|
, addCorrPerson
|
||||||
, addMember
|
, addMember
|
||||||
, addTag
|
, addTag
|
||||||
|
, addTagsMultiple
|
||||||
, cancelJob
|
, cancelJob
|
||||||
, changeFolderName
|
, changeFolderName
|
||||||
, changePassword
|
, changePassword
|
||||||
@ -88,6 +89,7 @@ module Api exposing
|
|||||||
, setItemNotes
|
, setItemNotes
|
||||||
, setJobPrio
|
, setJobPrio
|
||||||
, setTags
|
, setTags
|
||||||
|
, setTagsMultiple
|
||||||
, setUnconfirmed
|
, setUnconfirmed
|
||||||
, startClassifier
|
, startClassifier
|
||||||
, startOnceNotifyDueItems
|
, startOnceNotifyDueItems
|
||||||
@ -130,6 +132,7 @@ import Api.Model.ItemLightList exposing (ItemLightList)
|
|||||||
import Api.Model.ItemProposals exposing (ItemProposals)
|
import Api.Model.ItemProposals exposing (ItemProposals)
|
||||||
import Api.Model.ItemSearch exposing (ItemSearch)
|
import Api.Model.ItemSearch exposing (ItemSearch)
|
||||||
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
||||||
|
import Api.Model.ItemsAndRefs exposing (ItemsAndRefs)
|
||||||
import Api.Model.JobPriority exposing (JobPriority)
|
import Api.Model.JobPriority exposing (JobPriority)
|
||||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||||
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||||
@ -1262,6 +1265,38 @@ getJobQueueStateTask flags =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Item (Mulit Edit)
|
||||||
|
|
||||||
|
|
||||||
|
setTagsMultiple :
|
||||||
|
Flags
|
||||||
|
-> ItemsAndRefs
|
||||||
|
-> (Result Http.Error BasicResult -> msg)
|
||||||
|
-> Cmd msg
|
||||||
|
setTagsMultiple flags data receive =
|
||||||
|
Http2.authPut
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addTagsMultiple :
|
||||||
|
Flags
|
||||||
|
-> ItemsAndRefs
|
||||||
|
-> (Result Http.Error BasicResult -> msg)
|
||||||
|
-> Cmd msg
|
||||||
|
addTagsMultiple flags data receive =
|
||||||
|
Http2.authPost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/items/tags"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.ItemsAndRefs.encode data)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Item
|
--- Item
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,5 +40,12 @@ multiUpdate flags ids change receive =
|
|||||||
Set.toList ids
|
Set.toList ids
|
||||||
in
|
in
|
||||||
case change of
|
case change of
|
||||||
|
TagChange tags ->
|
||||||
|
let
|
||||||
|
data =
|
||||||
|
ItemsAndRefs items (List.map .id tags.items)
|
||||||
|
in
|
||||||
|
Api.setTagsMultiple flags data receive
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Cmd.none
|
Cmd.none
|
||||||
|
@ -435,7 +435,10 @@ update mId key flags settings msg model =
|
|||||||
res.change
|
res.change
|
||||||
MultiUpdateResp
|
MultiUpdateResp
|
||||||
in
|
in
|
||||||
( { model | viewMode = SelectView svm_ }, Cmd.batch [ cmd_, upCmd ], sub_ )
|
( { model | viewMode = SelectView svm_ }
|
||||||
|
, Cmd.batch [ cmd_, upCmd ]
|
||||||
|
, sub_
|
||||||
|
)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
noSub ( model, Cmd.none )
|
noSub ( model, Cmd.none )
|
||||||
|
Loading…
x
Reference in New Issue
Block a user