diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala index d9826904..e586bf34 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -144,6 +144,8 @@ trait OItem[F[_]] { def deleteAttachment(id: Ident, collective: Ident): F[Int] + def setDeletedState(items: NonEmptyList[Ident], collective: Ident): F[Int] + def deleteAttachmentMultiple( attachments: NonEmptyList[Ident], collective: Ident @@ -612,6 +614,9 @@ object OItem { n = results.sum } yield n + def setDeletedState(items: NonEmptyList[Ident], collective: Ident): F[Int] = + store.transact(RItem.setState(items, collective, ItemState.Deleted)) + def getProposals(item: Ident, collective: Ident): F[MetaProposalList] = store.transact(QAttachment.getMetaProposals(item, collective)) diff --git a/modules/common/src/main/scala/docspell/common/ItemState.scala b/modules/common/src/main/scala/docspell/common/ItemState.scala index 70ed7e04..05149ad7 100644 --- a/modules/common/src/main/scala/docspell/common/ItemState.scala +++ b/modules/common/src/main/scala/docspell/common/ItemState.scala @@ -28,6 +28,7 @@ object ItemState { case object Processing extends ItemState case object Created extends ItemState case object Confirmed extends ItemState + case object Deleted extends ItemState def premature: ItemState = Premature def processing: ItemState = Processing @@ -40,6 +41,7 @@ object ItemState { case "processing" => Right(Processing) case "created" => Right(Created) case "confirmed" => Right(Confirmed) + case "deleted" => Right(Deleted) case _ => Left(s"Invalid item state: $str") } diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index e0d683bc..f2d8d1d9 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -4569,6 +4569,7 @@ components: required: - incomingCount - outgoingCount + - deletedCount - itemSize - tagCloud properties: @@ -4578,6 +4579,9 @@ components: outgoingCount: type: integer format: int32 + deletedCount: + type: integer + format: int32 itemSize: type: integer format: int64 diff --git a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala index 2506a5ff..fb9323b1 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -63,6 +63,7 @@ trait Conversions { ItemInsights( d.incoming, d.outgoing, + d.deleted, d.bytes, mkTagCloud(d.tags) ) diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala index bbda6df0..2ffadde9 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemMultiRoutes.scala @@ -179,7 +179,7 @@ object ItemMultiRoutes extends MultiIdSupport { for { json <- req.as[IdList] items <- readIds[F](json.ids) - n <- backend.item.deleteItemMultiple(items, user.account.collective) + n <- backend.item.setDeletedState(items, user.account.collective) res = BasicResult( n > 0, if (n > 0) "Item(s) deleted" else "Item deletion failed." diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index f2bec1fc..803fc975 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -393,7 +393,7 @@ object ItemRoutes { case DELETE -> Root / Ident(id) => for { - n <- backend.item.deleteItem(id, user.account.collective) + n <- backend.item.setDeletedState(NonEmptyList.of(id), user.account.collective) res = BasicResult(n > 0, if (n > 0) "Item deleted" else "Item deletion failed.") resp <- Ok(res) } yield resp @@ -440,7 +440,7 @@ object ItemRoutes { } } - def searchItemStats[F[_]: Sync]( + private def searchItemStats[F[_]: Sync]( backend: BackendApp[F], dsl: Http4sDsl[F] )(ftsEnabled: Boolean, fixQuery: Query.Fix, itemQuery: ItemQueryString) = { diff --git a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala index daeb380f..e00d27f4 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala @@ -65,6 +65,7 @@ object QCollective { case class InsightData( incoming: Int, outgoing: Int, + deleted: Int, bytes: Long, tags: List[TagCount] ) @@ -84,6 +85,11 @@ object QCollective { ItemState.validStates ) ).build.query[Int].unique + val q2 = Select( + count(i.id).s, + from(i), + i.cid === coll && i.state === ItemState.Deleted + ).build.query[Int].unique val fileSize = sql""" select sum(length) from ( @@ -106,11 +112,12 @@ object QCollective { ) as t""".query[Option[Long]].unique for { - n0 <- q0 - n1 <- q1 - n2 <- fileSize - n3 <- tagCloud(coll) - } yield InsightData(n0, n1, n2.getOrElse(0L), n3) + incoming <- q0 + outgoing <- q1 + size <- fileSize + tags <- tagCloud(coll) + deleted <- q2 + } yield InsightData(incoming, outgoing, deleted, size.getOrElse(0L), tags) } def tagCloud(coll: Ident): ConnectionIO[List[TagCount]] = { diff --git a/modules/store/src/main/scala/docspell/store/records/RItem.scala b/modules/store/src/main/scala/docspell/store/records/RItem.scala index 1dd977cc..017e1992 100644 --- a/modules/store/src/main/scala/docspell/store/records/RItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RItem.scala @@ -336,6 +336,20 @@ object RItem { def deleteByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Int] = DML.delete(T, T.id === itemId && T.cid === coll) + def setState( + itemIds: NonEmptyList[Ident], + coll: Ident, + state: ItemState + ): ConnectionIO[Int] = + for { + t <- currentTime + n <- DML.update( + T, + T.id.in(itemIds) && T.cid === coll, + DML.set(T.state.setTo(state), T.updated.setTo(t)) + ) + } yield n + def existsById(itemId: Ident): ConnectionIO[Boolean] = Select(count(T.id).s, from(T), T.id === itemId).build.query[Int].unique.map(_ > 0) diff --git a/modules/webapp/src/main/elm/Messages/Basics.elm b/modules/webapp/src/main/elm/Messages/Basics.elm index 4c1b4126..0edba00b 100644 --- a/modules/webapp/src/main/elm/Messages/Basics.elm +++ b/modules/webapp/src/main/elm/Messages/Basics.elm @@ -15,6 +15,7 @@ module Messages.Basics exposing type alias Texts = { incoming : String , outgoing : String + , deleted : String , tags : String , items : String , submit : String @@ -51,6 +52,7 @@ gb : Texts gb = { incoming = "Incoming" , outgoing = "Outgoing" + , deleted = "Deleted" , tags = "Tags" , items = "Items" , submit = "Submit" @@ -92,6 +94,7 @@ de : Texts de = { incoming = "Eingehend" , outgoing = "Ausgehend" + , deleted = "Gelöscht" , tags = "Tags" , items = "Dokumente" , submit = "Speichern" diff --git a/modules/webapp/src/main/elm/Page/CollectiveSettings/View2.elm b/modules/webapp/src/main/elm/Page/CollectiveSettings/View2.elm index b190ec82..84fc7692 100644 --- a/modules/webapp/src/main/elm/Page/CollectiveSettings/View2.elm +++ b/modules/webapp/src/main/elm/Page/CollectiveSettings/View2.elm @@ -171,6 +171,7 @@ viewInsights texts flags model = [ stats (String.fromInt (model.insights.incomingCount + model.insights.outgoingCount)) texts.basics.items , stats (String.fromInt model.insights.incomingCount) texts.basics.incoming , stats (String.fromInt model.insights.outgoingCount) texts.basics.outgoing + , stats (String.fromInt model.insights.deletedCount) texts.basics.deleted ] ] , div