From 8c08bf233d487835cab232e1053b2c3e441ae24a Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Mon, 9 Nov 2020 14:24:28 +0100 Subject: [PATCH] Amend search results with attachment info This uses again another query per item to retrieve some information about each attachment already in the search results. --- .../src/main/resources/docspell-openapi.yml | 38 +++++++++++++++---- .../restserver/conv/Conversions.scala | 8 +++- .../scala/docspell/store/queries/QItem.scala | 35 ++++++++++++++++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 3ef90ff3..fbba6e89 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1291,10 +1291,11 @@ paths: summary: Search for items. description: | Search for items given a search form. The results are grouped - by month and are sorted by item date (newest first). Tags are - *not* resolved. The results will always contain an empty list - for item tags. Use `/searchWithTags` to also retrieve all tags - of an item. + by month and are sorted by item date (newest first). Tags and + attachments are *not* resolved. The results will always + contain an empty list for item tags and attachments. Use + `/searchWithTags` to also retrieve all tags and a list of + attachments of an item. The `fulltext` field can be used to restrict the results by using full-text search in the documents contents. @@ -1318,9 +1319,10 @@ paths: summary: Search for items. description: | Search for items given a search form. The results are grouped - by month by default. For each item, its tags are also - returned. This uses more queries and is therefore slower, but - returns all tags to an item. + by month by default. For each item, its tags and attachments + are also returned. This uses more queries and is therefore + slower, but returns all tags to an item as well as their + attachments with some minor details. The `fulltext` field can be used to restrict the results by using full-text search in the documents contents. @@ -4703,6 +4705,10 @@ components: fileCount: type: integer format: int32 + attachments: + type: array + items: + $ref: "#/components/schemas/AttachmentLight" tags: type: array items: @@ -4720,6 +4726,24 @@ components: type: array items: $ref: "#/components/schemas/HighlightEntry" + AttachmentLight: + description: | + Some little details about an attachment. + required: + - id + - position + properties: + id: + type: string + format: ident + position: + type: integer + format: int32 + name: + type: string + pageCount: + type: integer + format: int32 HighlightEntry: description: | Highlighting information for a single field (maybe attachment 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 3b40c1d8..fc0ccb75 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -22,6 +22,7 @@ import bitpeace.FileMeta import org.http4s.headers.`Content-Type` import org.http4s.multipart.Multipart import org.log4s.Logger +import docspell.store.queries.QItem trait Conversions { @@ -204,6 +205,7 @@ trait Conversions { i.folder.map(mkIdName), i.fileCount, Nil, + Nil, i.notes, Nil ) @@ -215,7 +217,11 @@ trait Conversions { } def mkItemLightWithTags(i: OItemSearch.ListItemWithTags): ItemLight = - mkItemLight(i.item).copy(tags = i.tags.map(mkTag)) + mkItemLight(i.item) + .copy(tags = i.tags.map(mkTag), attachments = i.attachments.map(mkAttachmentLight)) + + private def mkAttachmentLight(qa: QItem.AttachmentLight): AttachmentLight = + AttachmentLight(qa.id, qa.position, qa.name, qa.pageCount) def mkItemLightWithTags(i: OFulltext.FtsItemWithTags): ItemLight = { val il = mkItemLightWithTags(i.item) 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 bce5f836..7f768f93 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -443,7 +443,17 @@ object QItem { from.query[ListItem].stream } - case class ListItemWithTags(item: ListItem, tags: List[RTag]) + case class AttachmentLight( + id: Ident, + position: Int, + name: Option[String], + pageCount: Option[Int] + ) + case class ListItemWithTags( + item: ListItem, + tags: List[RTag], + attachments: List[AttachmentLight] + ) /** Same as `findItems` but resolves the tags for each item. Note that * this is implemented by running an additional query per item. @@ -476,8 +486,29 @@ object QItem { item <- search tagItems <- Stream.eval(RTagItem.findByItem(item.id)) tags <- Stream.eval(tagItems.traverse(ti => findTag(resolvedTags, ti))) + attachs <- Stream.eval(findAttachmentLight(item.id)) ftags = tags.flatten.filter(t => t.collective == collective) - } yield ListItemWithTags(item, ftags.toList.sortBy(_.name)) + } yield ListItemWithTags( + item, + ftags.toList.sortBy(_.name), + attachs.sortBy(_.position) + ) + } + + private def findAttachmentLight(item: Ident): ConnectionIO[List[AttachmentLight]] = { + val aId = RAttachment.Columns.id.prefix("a") + val aItem = RAttachment.Columns.itemId.prefix("a") + val aPos = RAttachment.Columns.position.prefix("a") + val aName = RAttachment.Columns.name.prefix("a") + val mId = RAttachmentMeta.Columns.id.prefix("m") + val mPages = RAttachmentMeta.Columns.pages.prefix("m") + + val cols = Seq(aId, aPos, aName, mPages) + val join = RAttachment.table ++ + fr"a LEFT OUTER JOIN" ++ RAttachmentMeta.table ++ fr"m ON" ++ aId.is(mId) + val cond = aItem.is(item) + + selectSimple(cols, join, cond).query[AttachmentLight].to[List] } def delete[F[_]: Sync](store: Store[F])(itemId: Ident, collective: Ident): F[Int] =