diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OFulltext.scala b/modules/backend/src/main/scala/docspell/backend/ops/OFulltext.scala index f9efed22..a9fa7716 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OFulltext.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OFulltext.scala @@ -3,12 +3,11 @@ package docspell.backend.ops import cats.effect._ import cats.implicits._ import fs2.Stream - import docspell.backend.JobFactory import docspell.backend.ops.OItemSearch._ import docspell.common._ import docspell.ftsclient._ -import docspell.store.queries.{QFolder, QItem} +import docspell.store.queries.{QFolder, QItem, SelectedItem} import docspell.store.queue.JobQueue import docspell.store.records.RJob import docspell.store.{Store, qb} @@ -112,15 +111,15 @@ object OFulltext { ftsItems = ftsR.results.groupBy(_.itemId) select = ftsItems.values - .map(_.sortBy(-_.score).head) - .map(r => QItem.SelectedItem(r.itemId, r.score)) + .map(_.minBy(-_.score)) + .map(r => SelectedItem(r.itemId, r.score)) .toSet itemsWithTags <- store .transact( QItem.findItemsWithTags( account.collective, - QItem.findSelectedItems(QItem.Query.empty(account), maxNoteLen, select) + QItem.findSelectedItems(Query.empty(account), maxNoteLen, select) ) ) .take(batch.limit.toLong) @@ -227,10 +226,9 @@ object OFulltext { ): PartialFunction[A, (A, FtsData)] = { case a if ftrItems.contains(ItemId[A].itemId(a)) => val ftsDataItems = ftrItems - .get(ItemId[A].itemId(a)) - .getOrElse(Nil) + .getOrElse(ItemId[A].itemId(a), Nil) .map(im => - FtsDataItem(im.score, im.data, ftr.highlight.get(im.id).getOrElse(Nil)) + FtsDataItem(im.score, im.data, ftr.highlight.getOrElse(im.id, Nil)) ) (a, FtsData(ftr.maxScore, ftr.count, ftr.qtime, ftsDataItems)) } 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 13ee91c7..889173d3 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -4,16 +4,14 @@ import cats.data.NonEmptyList import cats.data.OptionT import cats.effect.{Effect, Resource} import cats.implicits._ - import docspell.backend.JobFactory import docspell.common._ import docspell.ftsclient.FtsClient import docspell.store.UpdateResult -import docspell.store.queries.{QAttachment, QItem} +import docspell.store.queries.{QAttachment, QItem, QMoveAttachment} import docspell.store.queue.JobQueue import docspell.store.records._ import docspell.store.{AddResult, Store} - import doobie.implicits._ import org.log4s.getLogger @@ -206,7 +204,7 @@ object OItem { target: Ident ): F[AddResult] = store - .transact(QItem.moveAttachmentBefore(itemId, source, target)) + .transact(QMoveAttachment.moveAttachmentBefore(itemId, source, target)) .attempt .map(AddResult.fromUpdate) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OItemSearch.scala b/modules/backend/src/main/scala/docspell/backend/ops/OItemSearch.scala index 94270e6f..67d9086f 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItemSearch.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItemSearch.scala @@ -9,7 +9,7 @@ import docspell.backend.ops.OItemSearch._ import docspell.common._ import docspell.store.queries.{QAttachment, QItem} import docspell.store.records._ -import docspell.store.{Store, qb} +import docspell.store._ import bitpeace.{FileMeta, RangeDef} import doobie.implicits._ @@ -53,26 +53,26 @@ trait OItemSearch[F[_]] { object OItemSearch { - type CustomValue = QItem.CustomValue - val CustomValue = QItem.CustomValue + type CustomValue = queries.CustomValue + val CustomValue = queries.CustomValue - type Query = QItem.Query - val Query = QItem.Query + type Query = queries.Query + val Query = queries.Query type Batch = qb.Batch val Batch = docspell.store.qb.Batch - type ListItem = QItem.ListItem - val ListItem = QItem.ListItem + type ListItem = queries.ListItem + val ListItem = queries.ListItem - type ListItemWithTags = QItem.ListItemWithTags - val ListItemWithTags = QItem.ListItemWithTags + type ListItemWithTags = queries.ListItemWithTags + val ListItemWithTags = queries.ListItemWithTags - type ItemFieldValue = QItem.ItemFieldValue - val ItemFieldValue = QItem.ItemFieldValue + type ItemFieldValue = queries.ItemFieldValue + val ItemFieldValue = queries.ItemFieldValue - type ItemData = QItem.ItemData - val ItemData = QItem.ItemData + type ItemData = queries.ItemData + val ItemData = queries.ItemData trait BinaryData[F[_]] { def data: Stream[F, Byte] diff --git a/modules/joex/src/main/scala/docspell/joex/notify/MailContext.scala b/modules/joex/src/main/scala/docspell/joex/notify/MailContext.scala index 4f243521..e8097f6a 100644 --- a/modules/joex/src/main/scala/docspell/joex/notify/MailContext.scala +++ b/modules/joex/src/main/scala/docspell/joex/notify/MailContext.scala @@ -2,7 +2,7 @@ package docspell.joex.notify import docspell.common._ import docspell.joex.notify.YamuscaConverter._ -import docspell.store.queries.QItem +import docspell.store.queries.ListItem import yamusca.implicits._ import yamusca.imports._ @@ -19,7 +19,7 @@ case class MailContext( object MailContext { def from( - items: Vector[QItem.ListItem], + items: Vector[ListItem], max: Int, account: AccountId, itemBaseUri: Option[LenientUri], @@ -46,7 +46,7 @@ object MailContext { object ItemData { - def apply(now: Timestamp)(i: QItem.ListItem): ItemData = { + def apply(now: Timestamp)(i: ListItem): ItemData = { val dueIn = i.dueDate.map(dt => Timestamp.daysBetween(now, dt)) val dueInLabel = dueIn.map { case 0 => "**today**" diff --git a/modules/joex/src/main/scala/docspell/joex/notify/NotifyDueItemsTask.scala b/modules/joex/src/main/scala/docspell/joex/notify/NotifyDueItemsTask.scala index b4a59291..1819fac1 100644 --- a/modules/joex/src/main/scala/docspell/joex/notify/NotifyDueItemsTask.scala +++ b/modules/joex/src/main/scala/docspell/joex/notify/NotifyDueItemsTask.scala @@ -3,14 +3,12 @@ package docspell.joex.notify import cats.data.OptionT import cats.effect._ import cats.implicits._ - -import docspell.backend.ops.OItemSearch.Batch +import docspell.backend.ops.OItemSearch.{Batch, ListItem, Query} import docspell.common._ import docspell.joex.mail.EmilHeader import docspell.joex.scheduler.{Context, Task} import docspell.store.queries.QItem import docspell.store.records._ - import emil._ import emil.builder._ import emil.javamail.syntax._ @@ -66,11 +64,11 @@ object NotifyDueItemsTask { mail <- OptionT.liftF(makeMail(sendCfg, cfg, ctx.args, items)) } yield mail - def findItems[F[_]: Sync](ctx: Context[F, Args]): F[Vector[QItem.ListItem]] = + def findItems[F[_]: Sync](ctx: Context[F, Args]): F[Vector[ListItem]] = for { now <- Timestamp.current[F] q = - QItem.Query + Query .empty(ctx.args.account) .copy( states = ItemState.validStates.toList, @@ -91,7 +89,7 @@ object NotifyDueItemsTask { sendCfg: MailSendConfig, cfg: RUserEmail, args: Args, - items: Vector[QItem.ListItem] + items: Vector[ListItem] ): F[Mail[F]] = Timestamp.current[F].map { now => val templateCtx = 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 ece9ae45..f817fb10 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -16,7 +16,7 @@ import docspell.common.syntax.all._ import docspell.ftsclient.FtsResult import docspell.restapi.model._ import docspell.restserver.conv.Conversions._ -import docspell.store.queries.QItem +import docspell.store.queries.{AttachmentLight => QAttachmentLight} import docspell.store.records._ import docspell.store.{AddResult, UpdateResult} @@ -234,7 +234,7 @@ trait Conversions { customfields = i.customfields.map(mkItemFieldValue) ) - private def mkAttachmentLight(qa: QItem.AttachmentLight): AttachmentLight = + private def mkAttachmentLight(qa: QAttachmentLight): AttachmentLight = AttachmentLight(qa.id, qa.position, qa.name, qa.pageCount) def mkItemLightWithTags(i: OFulltext.FtsItemWithTags): ItemLight = { diff --git a/modules/store/src/main/scala/docspell/store/queries/AttachmentLight.scala b/modules/store/src/main/scala/docspell/store/queries/AttachmentLight.scala new file mode 100644 index 00000000..72caee49 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/AttachmentLight.scala @@ -0,0 +1,10 @@ +package docspell.store.queries + +import docspell.common._ + +case class AttachmentLight( + id: Ident, + position: Int, + name: Option[String], + pageCount: Option[Int] +) diff --git a/modules/store/src/main/scala/docspell/store/queries/CustomValue.scala b/modules/store/src/main/scala/docspell/store/queries/CustomValue.scala new file mode 100644 index 00000000..fddaf92f --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/CustomValue.scala @@ -0,0 +1,5 @@ +package docspell.store.queries + +import docspell.common._ + +case class CustomValue(field: Ident, value: String) diff --git a/modules/store/src/main/scala/docspell/store/queries/ItemData.scala b/modules/store/src/main/scala/docspell/store/queries/ItemData.scala new file mode 100644 index 00000000..0774f9cb --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/ItemData.scala @@ -0,0 +1,24 @@ +package docspell.store.queries + +import bitpeace.FileMeta +import docspell.common._ +import docspell.store.records._ + +case class ItemData( + item: RItem, + corrOrg: Option[ROrganization], + corrPerson: Option[RPerson], + concPerson: Option[RPerson], + concEquip: Option[REquipment], + inReplyTo: Option[IdRef], + folder: Option[IdRef], + tags: Vector[RTag], + attachments: Vector[(RAttachment, FileMeta)], + sources: Vector[(RAttachmentSource, FileMeta)], + archives: Vector[(RAttachmentArchive, FileMeta)], + customFields: Vector[ItemFieldValue] +) { + + def filterCollective(coll: Ident): Option[ItemData] = + if (item.cid == coll) Some(this) else None +} diff --git a/modules/store/src/main/scala/docspell/store/queries/ItemFieldValue.scala b/modules/store/src/main/scala/docspell/store/queries/ItemFieldValue.scala new file mode 100644 index 00000000..6814f6d7 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/ItemFieldValue.scala @@ -0,0 +1,11 @@ +package docspell.store.queries + +import docspell.common._ + +case class ItemFieldValue( + fieldId: Ident, + fieldName: Ident, + fieldLabel: Option[String], + fieldType: CustomFieldType, + value: String +) diff --git a/modules/store/src/main/scala/docspell/store/queries/ListItem.scala b/modules/store/src/main/scala/docspell/store/queries/ListItem.scala new file mode 100644 index 00000000..d5a37595 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/ListItem.scala @@ -0,0 +1,21 @@ +package docspell.store.queries + +import docspell.common._ + +case class ListItem( + id: Ident, + name: String, + state: ItemState, + date: Timestamp, + dueDate: Option[Timestamp], + source: String, + direction: Direction, + created: Timestamp, + fileCount: Int, + corrOrg: Option[IdRef], + corrPerson: Option[IdRef], + concPerson: Option[IdRef], + concEquip: Option[IdRef], + folder: Option[IdRef], + notes: Option[String] +) diff --git a/modules/store/src/main/scala/docspell/store/queries/ListItemWithTags.scala b/modules/store/src/main/scala/docspell/store/queries/ListItemWithTags.scala new file mode 100644 index 00000000..5401986d --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/ListItemWithTags.scala @@ -0,0 +1,10 @@ +package docspell.store.queries + +import docspell.store.records.RTag + +case class ListItemWithTags( + item: ListItem, + tags: List[RTag], + attachments: List[AttachmentLight], + customfields: List[ItemFieldValue] +) diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala index feba4947..847a4bba 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -1,6 +1,5 @@ package docspell.store.queries -import cats.data.OptionT import cats.data.{NonEmptyList => Nel} import cats.effect.Sync import cats.effect.concurrent.Ref @@ -14,87 +13,28 @@ import docspell.store.qb.DSL._ import docspell.store.qb._ import docspell.store.records._ -import bitpeace.FileMeta -import doobie._ +import doobie.{Query => _, _} import doobie.implicits._ import org.log4s._ object QItem { private[this] val logger = getLogger - def moveAttachmentBefore( - itemId: Ident, - source: Ident, - target: Ident - ): ConnectionIO[Int] = { - - // rs < rt - def moveBack(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] = - for { - n <- RAttachment.decPositions(itemId, rs.position, rt.position) - k <- RAttachment.updatePosition(rs.id, rt.position) - } yield n + k - - // rs > rt - def moveForward(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] = - for { - n <- RAttachment.incPositions(itemId, rt.position, rs.position) - k <- RAttachment.updatePosition(rs.id, rt.position) - } yield n + k - - (for { - _ <- OptionT.liftF( - if (source == target) - Sync[ConnectionIO].raiseError(new Exception("Attachments are the same!")) - else ().pure[ConnectionIO] - ) - rs <- OptionT(RAttachment.findById(source)).filter(_.itemId == itemId) - rt <- OptionT(RAttachment.findById(target)).filter(_.itemId == itemId) - n <- OptionT.liftF( - if (rs.position == rt.position || rs.position + 1 == rt.position) - 0.pure[ConnectionIO] - else if (rs.position < rt.position) moveBack(rs, rt) - else moveForward(rs, rt) - ) - } yield n).getOrElse(0) - - } - - case class ItemFieldValue( - fieldId: Ident, - fieldName: Ident, - fieldLabel: Option[String], - fieldType: CustomFieldType, - value: String - ) - case class ItemData( - item: RItem, - corrOrg: Option[ROrganization], - corrPerson: Option[RPerson], - concPerson: Option[RPerson], - concEquip: Option[REquipment], - inReplyTo: Option[IdRef], - folder: Option[IdRef], - tags: Vector[RTag], - attachments: Vector[(RAttachment, FileMeta)], - sources: Vector[(RAttachmentSource, FileMeta)], - archives: Vector[(RAttachmentArchive, FileMeta)], - customFields: Vector[ItemFieldValue] - ) { - - def filterCollective(coll: Ident): Option[ItemData] = - if (item.cid == coll) Some(this) else None - } + private val equip = REquipment.as("e") + private val org = ROrganization.as("o") + private val pers0 = RPerson.as("pers0") + private val pers1 = RPerson.as("pers1") + private val f = RFolder.as("f") + private val i = RItem.as("i") + private val cf = RCustomField.as("cf") + private val cv = RCustomFieldValue.as("cvf") + private val a = RAttachment.as("a") + private val m = RAttachmentMeta.as("m") + private val tag = RTag.as("t") + private val ti = RTagItem.as("ti") def findItem(id: Ident): ConnectionIO[Option[ItemData]] = { - val equip = REquipment.as("e") - val org = ROrganization.as("o") - val pers0 = RPerson.as("p0") - val pers1 = RPerson.as("p1") - val f = RFolder.as("f") - val i = RItem.as("i") - val ref = RItem.as("ref") - + val ref = RItem.as("ref") val cq = Select( select(i.all, org.all, pers0.all, pers1.all, equip.all) @@ -146,90 +86,13 @@ object QItem { def findCustomFieldValuesForItem( itemId: Ident - ): ConnectionIO[Vector[ItemFieldValue]] = { - val cf = RCustomField.as("cf") - val cv = RCustomFieldValue.as("cvf") - + ): ConnectionIO[Vector[ItemFieldValue]] = Select( select(cf.id, cf.name, cf.label, cf.ftype, cv.value), from(cv) .innerJoin(cf, cf.id === cv.field), cv.itemId === itemId ).build.query[ItemFieldValue].to[Vector] - } - - case class ListItem( - id: Ident, - name: String, - state: ItemState, - date: Timestamp, - dueDate: Option[Timestamp], - source: String, - direction: Direction, - created: Timestamp, - fileCount: Int, - corrOrg: Option[IdRef], - corrPerson: Option[IdRef], - concPerson: Option[IdRef], - concEquip: Option[IdRef], - folder: Option[IdRef], - notes: Option[String] - ) - - case class CustomValue(field: Ident, value: String) - - case class Query( - account: AccountId, - name: Option[String], - states: Seq[ItemState], - direction: Option[Direction], - corrPerson: Option[Ident], - corrOrg: Option[Ident], - concPerson: Option[Ident], - concEquip: Option[Ident], - folder: Option[Ident], - tagsInclude: List[Ident], - tagsExclude: List[Ident], - tagCategoryIncl: List[String], - tagCategoryExcl: List[String], - dateFrom: Option[Timestamp], - dateTo: Option[Timestamp], - dueDateFrom: Option[Timestamp], - dueDateTo: Option[Timestamp], - allNames: Option[String], - itemIds: Option[Set[Ident]], - customValues: Seq[CustomValue], - source: Option[String], - orderAsc: Option[RItem.Table => docspell.store.qb.Column[_]] - ) - - object Query { - def empty(account: AccountId): Query = - Query( - account, - None, - Seq.empty, - None, - None, - None, - None, - None, - None, - Nil, - Nil, - Nil, - Nil, - None, - None, - None, - None, - None, - None, - Seq.empty, - None, - None - ) - } private def findCustomFieldValuesForColl( coll: Ident, @@ -262,13 +125,6 @@ object QItem { val num = Column[Int]("num", this) val itemId = Column[Ident]("item_id", this) } - val equip = REquipment.as("e1") - val org = ROrganization.as("o0") - val p0 = RPerson.as("p0") - val p1 = RPerson.as("p1") - val f = RFolder.as("f1") - val i = RItem.as("i") - val a = RAttachment.as("a") val coll = q.account.collective @@ -285,10 +141,10 @@ object QItem { coalesce(Attachs.num.s, lit(0)).s, org.oid.s, org.name.s, - p0.pid.s, - p0.name.s, - p1.pid.s, - p1.name.s, + pers0.pid.s, + pers0.name.s, + pers1.pid.s, + pers1.name.s, equip.eid.s, equip.name.s, f.id.s, @@ -311,9 +167,9 @@ object QItem { Attachs.aliasName, //alias, todo improve dsl Attachs.itemId === i.id ) - .leftJoin(p0, p0.pid === i.corrPerson && p0.cid === coll) + .leftJoin(pers0, pers0.pid === i.corrPerson && pers0.cid === coll) .leftJoin(org, org.oid === i.corrOrg && org.cid === coll) - .leftJoin(p1, p1.pid === i.concPerson && p1.cid === coll) + .leftJoin(pers1, pers1.pid === i.concPerson && pers1.cid === coll) .leftJoin(equip, equip.eid === i.concEquipment && equip.cid === coll), where( i.cid === coll &&? Nel.fromList(q.states.toList).map(nel => i.state.in(nel)) && @@ -338,13 +194,6 @@ object QItem { maxNoteLen: Int, batch: Batch ): Stream[ConnectionIO, ListItem] = { - val equip = REquipment.as("e1") - val org = ROrganization.as("o0") - val pers0 = RPerson.as("p0") - val pers1 = RPerson.as("p1") - val f = RFolder.as("f1") - val i = RItem.as("i") - val cond: Condition => Condition = c => c &&? @@ -386,7 +235,6 @@ object QItem { sql.query[ListItem].stream } - case class SelectedItem(itemId: Ident, weight: Double) def findSelectedItems( q: Query, maxNoteLen: Int, @@ -427,19 +275,6 @@ object QItem { from.query[ListItem].stream } - case class AttachmentLight( - id: Ident, - position: Int, - name: Option[String], - pageCount: Option[Int] - ) - case class ListItemWithTags( - item: ListItem, - tags: List[RTag], - attachments: List[AttachmentLight], - customfields: List[ItemFieldValue] - ) - /** Same as `findItems` but resolves the tags for each item. Note that * this is implemented by running an additional query per item. */ @@ -482,17 +317,13 @@ object QItem { ) } - private def findAttachmentLight(item: Ident): ConnectionIO[List[AttachmentLight]] = { - val a = RAttachment.as("a") - val m = RAttachmentMeta.as("m") - + private def findAttachmentLight(item: Ident): ConnectionIO[List[AttachmentLight]] = Select( select(a.id, a.position, a.name, m.pages), from(a) .leftJoin(m, m.id === a.id), a.itemId === item ).build.query[AttachmentLight].to[List] - } def delete[F[_]: Sync](store: Store[F])(itemId: Ident, collective: Ident): F[Int] = for { @@ -602,21 +433,12 @@ object QItem { .streamWithChunkSize(chunkSize) } - case class TagName(id: Ident, name: String) - case class TextAndTag(itemId: Ident, text: String, tag: Option[TagName]) - def resolveTextAndTag( collective: Ident, itemId: Ident, tagCategory: String, pageSep: String ): ConnectionIO[TextAndTag] = { - val tag = RTag.as("t") - val a = RAttachment.as("a") - val am = RAttachmentMeta.as("m") - val ti = RTagItem.as("ti") - val i = RItem.as("i") - val tags = TableDef("tags").as("tt") val tagsItem = Column[Ident]("itemid", tags) val tagsTid = Column[Ident]("tid", tags) @@ -632,12 +454,12 @@ object QItem { ) )( Select( - select(am.content, tagsTid, tagsName), + select(m.content, tagsTid, tagsName), from(i) .innerJoin(a, a.itemId === i.id) - .innerJoin(am, a.id === am.id) + .innerJoin(m, a.id === m.id) .leftJoin(tags, tagsItem === i.id), - i.id === itemId && i.cid === collective && am.content.isNotNull && am.content <> "" + i.id === itemId && i.cid === collective && m.content.isNotNull && m.content <> "" ) ).build @@ -645,7 +467,7 @@ object QItem { _ <- logger.ftrace[ConnectionIO]( s"query: $q (${itemId.id}, ${collective.id}, ${tagCategory})" ) - texts <- q.query[(String, Option[TagName])].to[List] + texts <- q.query[(String, Option[TextAndTag.TagName])].to[List] _ <- logger.ftrace[ConnectionIO]( s"Got ${texts.size} text and tag entries for item ${itemId.id}" ) @@ -653,5 +475,4 @@ object QItem { txt = texts.map(_._1).mkString(pageSep) } yield TextAndTag(itemId, txt, tag) } - } diff --git a/modules/store/src/main/scala/docspell/store/queries/QMoveAttachment.scala b/modules/store/src/main/scala/docspell/store/queries/QMoveAttachment.scala new file mode 100644 index 00000000..c3463cb5 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/QMoveAttachment.scala @@ -0,0 +1,51 @@ +package docspell.store.queries + +import cats.effect._ +import cats.data.OptionT +import cats.implicits._ + +import docspell.common._ +import docspell.store.records._ + +import doobie.{Query => _, _} +import doobie.implicits._ + +object QMoveAttachment { + def moveAttachmentBefore( + itemId: Ident, + source: Ident, + target: Ident + ): ConnectionIO[Int] = { + + // rs < rt + def moveBack(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] = + for { + n <- RAttachment.decPositions(itemId, rs.position, rt.position) + k <- RAttachment.updatePosition(rs.id, rt.position) + } yield n + k + + // rs > rt + def moveForward(rs: RAttachment, rt: RAttachment): ConnectionIO[Int] = + for { + n <- RAttachment.incPositions(itemId, rt.position, rs.position) + k <- RAttachment.updatePosition(rs.id, rt.position) + } yield n + k + + (for { + _ <- OptionT.liftF( + if (source == target) + Sync[ConnectionIO].raiseError(new Exception("Attachments are the same!")) + else ().pure[ConnectionIO] + ) + rs <- OptionT(RAttachment.findById(source)).filter(_.itemId == itemId) + rt <- OptionT(RAttachment.findById(target)).filter(_.itemId == itemId) + n <- OptionT.liftF( + if (rs.position == rt.position || rs.position + 1 == rt.position) + 0.pure[ConnectionIO] + else if (rs.position < rt.position) moveBack(rs, rt) + else moveForward(rs, rt) + ) + } yield n).getOrElse(0) + + } +} diff --git a/modules/store/src/main/scala/docspell/store/queries/Query.scala b/modules/store/src/main/scala/docspell/store/queries/Query.scala new file mode 100644 index 00000000..0d68bdef --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/Query.scala @@ -0,0 +1,57 @@ +package docspell.store.queries + +import docspell.common._ +import docspell.store.records.RItem + +case class Query( + account: AccountId, + name: Option[String], + states: Seq[ItemState], + direction: Option[Direction], + corrPerson: Option[Ident], + corrOrg: Option[Ident], + concPerson: Option[Ident], + concEquip: Option[Ident], + folder: Option[Ident], + tagsInclude: List[Ident], + tagsExclude: List[Ident], + tagCategoryIncl: List[String], + tagCategoryExcl: List[String], + dateFrom: Option[Timestamp], + dateTo: Option[Timestamp], + dueDateFrom: Option[Timestamp], + dueDateTo: Option[Timestamp], + allNames: Option[String], + itemIds: Option[Set[Ident]], + customValues: Seq[CustomValue], + source: Option[String], + orderAsc: Option[RItem.Table => docspell.store.qb.Column[_]] +) + +object Query { + def empty(account: AccountId): Query = + Query( + account, + None, + Seq.empty, + None, + None, + None, + None, + None, + None, + Nil, + Nil, + Nil, + Nil, + None, + None, + None, + None, + None, + None, + Seq.empty, + None, + None + ) +} diff --git a/modules/store/src/main/scala/docspell/store/queries/SelectedItem.scala b/modules/store/src/main/scala/docspell/store/queries/SelectedItem.scala new file mode 100644 index 00000000..9beba7cb --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/SelectedItem.scala @@ -0,0 +1,6 @@ +package docspell.store.queries + +import docspell.common._ + +/** Some preselected item from a fulltext search. */ +case class SelectedItem(itemId: Ident, weight: Double) diff --git a/modules/store/src/main/scala/docspell/store/queries/TextAndTag.scala b/modules/store/src/main/scala/docspell/store/queries/TextAndTag.scala new file mode 100644 index 00000000..ffeb2984 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/queries/TextAndTag.scala @@ -0,0 +1,9 @@ +package docspell.store.queries + +import docspell.common._ + +case class TextAndTag(itemId: Ident, text: String, tag: Option[TextAndTag.TagName]) + +object TextAndTag { + case class TagName(id: Ident, name: String) +}