Change custom fields for multiple items

This commit is contained in:
Eike Kettner 2020-11-16 23:27:26 +01:00
parent 93295d63a5
commit 8d35d100d6
7 changed files with 137 additions and 14 deletions

View File

@ -1,6 +1,7 @@
package docspell.backend.ops
import cats.data.EitherT
import cats.data.NonEmptyList
import cats.data.OptionT
import cats.effect._
import cats.implicits._
@ -20,6 +21,7 @@ import docspell.store.records.RCustomFieldValue
import docspell.store.records.RItem
import doobie._
import org.log4s.getLogger
trait OCustomFields[F[_]] {
@ -41,6 +43,8 @@ trait OCustomFields[F[_]] {
/** Sets a value given a field an an item. Existing values are overwritten. */
def setValue(item: Ident, value: SetValue): F[SetValueResult]
def setValueMultiple(items: NonEmptyList[Ident], value: SetValue): F[SetValueResult]
/** Deletes a value for a given field an item. */
def deleteValue(in: RemoveValue): F[UpdateResult]
}
@ -79,7 +83,7 @@ object OCustomFields {
case class RemoveValue(
field: Ident,
item: Ident,
item: NonEmptyList[Ident],
collective: Ident
)
@ -88,8 +92,10 @@ object OCustomFields {
): Resource[F, OCustomFields[F]] =
Resource.pure[F, OCustomFields[F]](new OCustomFields[F] {
private[this] val logger = Logger.log4s[ConnectionIO](getLogger)
def findAll(coll: Ident, nameQuery: Option[String]): F[Vector[CustomFieldData]] =
store.transact(QCustomField.findAllLike(coll, nameQuery))
store.transact(QCustomField.findAllLike(coll, nameQuery.filter(_.nonEmpty)))
def findById(coll: Ident, field: Ident): F[Option[CustomFieldData]] =
store.transact(QCustomField.findById(field, coll))
@ -113,6 +119,7 @@ object OCustomFields {
val update =
for {
field <- OptionT(RCustomField.findByIdOrName(fieldIdOrName, coll))
_ <- OptionT.liftF(logger.info(s"Deleting field: $field"))
n <- OptionT.liftF(RCustomFieldValue.deleteByField(field.id))
k <- OptionT.liftF(RCustomField.deleteById(field.id, coll))
} yield n + k
@ -121,24 +128,32 @@ object OCustomFields {
}
def setValue(item: Ident, value: SetValue): F[SetValueResult] =
setValueMultiple(NonEmptyList.of(item), value)
def setValueMultiple(
items: NonEmptyList[Ident],
value: SetValue
): F[SetValueResult] =
(for {
field <- EitherT.fromOptionF(
store.transact(RCustomField.findByIdOrName(value.field, value.collective)),
SetValueResult.fieldNotFound
)
_ <- EitherT(
store
.transact(RItem.existsByIdAndCollective(item, value.collective))
.map(flag => if (flag) Right(()) else Left(SetValueResult.itemNotFound))
)
fval <- EitherT.fromEither[F](
field.ftype
.parseValue(value.value)
.leftMap(SetValueResult.valueInvalid)
.map(field.ftype.valueString)
)
_ <- EitherT(
store
.transact(RItem.existsByIdsAndCollective(items, value.collective))
.map(flag => if (flag) Right(()) else Left(SetValueResult.itemNotFound))
)
nu <- EitherT.right[SetValueResult](
store.transact(RCustomField.setValue(field, item, fval))
items
.traverse(item => store.transact(RCustomField.setValue(field, item, fval)))
.map(_.toList.sum)
)
} yield nu).fold(identity, _ => SetValueResult.success)

View File

@ -2383,6 +2383,52 @@ paths:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/items/customfield:
put:
tags: [ Item (Multi Edit) ]
summary: Set the value of a custom field for multiple items
description: |
Sets the value for a custom field to multiple given items. If
a value already exists, it is overwritten.
security:
- authTokenHeader: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ItemsAndFieldValue"
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/items/customfieldremove:
post:
tags: [ Item (Multi Edit) ]
summary: Removes the value for a custom field on multiple items
description: |
Removes the value for the given custom field from multiple
items. The field may be specified by its id or name.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/itemId"
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ItemsAndName"
responses:
200:
description: Ok
content:
application/json:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/attachment/{id}:
delete:
@ -3246,7 +3292,7 @@ paths:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/customfields:
/sec/customfield:
get:
tags: [ Custom Fields ]
summary: Get all defined custom fields.
@ -3283,7 +3329,7 @@ paths:
schema:
$ref: "#/components/schemas/BasicResult"
/sec/customfields/{id}:
/sec/customfield/{id}:
parameters:
- $ref: "#/components/parameters/id"
get:
@ -3342,6 +3388,21 @@ paths:
components:
schemas:
ItemsAndFieldValue:
description: |
Holds a list of item ids and a custom field value.
required:
- items
- field
properties:
items:
type: array
items:
type: string
format: ident
field:
$ref: "#/components/schemas/CustomFieldValue"
ItemsAndRefs:
description: |
Holds a list of item ids and a list of ids of some other
@ -3459,6 +3520,12 @@ components:
ftype:
type: string
format: customfieldtype
enum:
- text
- numeric
- date
- bool
- money
CustomField:
description: |
@ -3481,6 +3548,12 @@ components:
ftype:
type: string
format: customfieldtype
enum:
- text
- numeric
- date
- bool
- money
usages:
type: integer
format: int32

View File

@ -56,7 +56,7 @@ object CustomFieldRoutes {
case DELETE -> Root / Ident(id) =>
for {
res <- backend.customFields.delete(id, user.account.collective)
res <- backend.customFields.delete(user.account.collective, id)
resp <- Ok(convertResult(res))
} yield resp
}

View File

@ -8,6 +8,7 @@ import cats.implicits._
import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OCustomFields.{RemoveValue, SetValue}
import docspell.common.{Ident, ItemState}
import docspell.restapi.model._
import docspell.restserver.conv.Conversions
@ -180,6 +181,29 @@ object ItemMultiRoutes {
)
resp <- Ok(res)
} yield resp
case req @ PUT -> Root / "customfield" =>
for {
json <- req.as[ItemsAndFieldValue]
items <- readIds[F](json.items)
res <- backend.customFields.setValueMultiple(
items,
SetValue(json.field.field, json.field.value, user.account.collective)
)
resp <- Ok(Conversions.basicResult(res))
} yield resp
case req @ POST -> Root / "customfieldremove" =>
for {
json <- req.as[ItemsAndName]
items <- readIds[F](json.items)
field <- readId[F](json.name)
res <- backend.customFields.deleteValue(
RemoveValue(field, items, user.account.collective)
)
resp <- Ok(Conversions.basicResult(res, "Custom fields removed."))
} yield resp
}
}

View File

@ -372,7 +372,7 @@ object ItemRoutes {
case DELETE -> Root / Ident(id) / "customfield" / Ident(fieldId) =>
for {
res <- backend.customFields.deleteValue(
RemoveValue(fieldId, id, user.account.collective)
RemoveValue(fieldId, NonEmptyList.of(id), user.account.collective)
)
resp <- Ok(Conversions.basicResult(res, "Custom field value removed."))
} yield resp

View File

@ -1,5 +1,7 @@
package docspell.store.records
import cats.data.NonEmptyList
import docspell.common._
import docspell.store.impl.Column
import docspell.store.impl.Implicits._
@ -57,6 +59,6 @@ object RCustomFieldValue {
def deleteByItem(item: Ident): ConnectionIO[Int] =
deleteFrom(table, Columns.itemId.is(item)).update.run
def deleteValue(fieldId: Ident, item: Ident): ConnectionIO[Int] =
deleteFrom(table, and(Columns.id.is(fieldId), Columns.itemId.is(item))).update.run
def deleteValue(fieldId: Ident, items: NonEmptyList[Ident]): ConnectionIO[Int] =
deleteFrom(table, and(Columns.id.is(fieldId), Columns.itemId.isIn(items))).update.run
}

View File

@ -326,6 +326,15 @@ object RItem {
def existsByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Boolean] =
selectCount(id, table, and(id.is(itemId), cid.is(coll))).query[Int].unique.map(_ > 0)
def existsByIdsAndCollective(
itemIds: NonEmptyList[Ident],
coll: Ident
): ConnectionIO[Boolean] =
selectCount(id, table, and(id.isIn(itemIds), cid.is(coll)))
.query[Int]
.unique
.map(_ == itemIds.size)
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
selectSimple(all, table, and(id.is(itemId), cid.is(coll))).query[RItem].option