Add a route to get the item preview

This is the first available preview of an attachment wrt position. If
all attachments have a preview image, the preview of the first
attachment is returned.
This commit is contained in:
Eike Kettner 2020-11-08 14:22:33 +01:00
parent 8cc89fd3b7
commit 757ad31165
5 changed files with 123 additions and 0 deletions

View File

@ -41,6 +41,8 @@ trait OItemSearch[F[_]] {
collective: Ident
): F[Option[AttachmentPreviewData[F]]]
def findItemPreview(item: Ident, collective: Ident): F[Option[AttachmentPreviewData[F]]]
def findAttachmentMeta(id: Ident, collective: Ident): F[Option[RAttachmentMeta]]
def findByFileCollective(checksum: String, collective: Ident): F[Vector[RItem]]
@ -188,6 +190,26 @@ object OItemSearch {
(None: Option[AttachmentPreviewData[F]]).pure[F]
})
def findItemPreview(
item: Ident,
collective: Ident
): F[Option[AttachmentPreviewData[F]]] =
store
.transact(RAttachmentPreview.findByItemAndCollective(item, collective))
.flatMap({
case Some(ra) =>
makeBinaryData(ra.fileId) { m =>
AttachmentPreviewData[F](
ra,
m,
store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m))
)
}
case None =>
(None: Option[AttachmentPreviewData[F]]).pure[F]
})
def findAttachmentArchive(
id: Ident,
collective: Ident

View File

@ -1847,6 +1847,47 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ItemProposals"
/sec/item/{id}/preview:
head:
tags: [ Attachment ]
summary: Get a preview image of an attachment file.
description: |
Checks if an image file showing a preview of the item is
available. If not available, a 404 is returned. The preview is
currently the an image of the first page of the first
attachment.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/id"
responses:
200:
description: Ok
404:
description: NotFound
get:
tags: [ Attachment ]
summary: Get a preview image of an attachment file.
description: |
Gets a image file showing a preview of the item. Usually it is
a small image of the first page of the first attachment. If
not available, a 404 is returned. However, if the query
parameter `withFallback` is `true`, a fallback preview image
is returned. You can also use the `HEAD` method to check for
existence.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/id"
- $ref: "#/components/parameters/withFallback"
responses:
200:
description: Ok
content:
application/octet-stream:
schema:
type: string
format: binary
/sec/item/{itemId}/reprocess:
post:

View File

@ -1,5 +1,6 @@
package docspell.restserver.http4s
import cats.data.NonEmptyList
import fs2.text.utf8Encode
import fs2.{Pure, Stream}
@ -27,4 +28,12 @@ object Responses {
def unauthorized[F[_]]: Response[F] =
pureUnauthorized.copy(body = pureUnauthorized.body.covary[F])
def noCache[F[_]](r: Response[F]): Response[F] =
r.withHeaders(
`Cache-Control`(
NonEmptyList.of(CacheDirective.`no-cache`(), CacheDirective.`private`())
)
)
}

View File

@ -13,11 +13,14 @@ import docspell.common.{Ident, ItemState}
import docspell.restapi.model._
import docspell.restserver.Config
import docspell.restserver.conv.Conversions
import docspell.restserver.http4s.BinaryUtil
import docspell.restserver.http4s.Responses
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.http4s.headers._
import org.log4s._
object ItemRoutes {
@ -315,6 +318,29 @@ object ItemRoutes {
resp <- Ok(Conversions.basicResult(res, "Attachment moved."))
} yield resp
case req @ GET -> Root / Ident(id) / "preview" =>
for {
preview <- backend.itemSearch.findItemPreview(id, user.account.collective)
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
matches = BinaryUtil.matchETag(preview.map(_.meta), inm)
resp <-
preview
.map { data =>
if (matches) BinaryUtil.withResponseHeaders(dsl, NotModified())(data)
else BinaryUtil.makeByteResp(dsl)(data).map(Responses.noCache)
}
.getOrElse(NotFound(BasicResult(false, "Not found")))
} yield resp
case HEAD -> Root / Ident(id) / "preview" =>
for {
preview <- backend.itemSearch.findItemPreview(id, user.account.collective)
resp <-
preview
.map(data => BinaryUtil.withResponseHeaders(dsl, Ok())(data))
.getOrElse(NotFound(BasicResult(false, "Not found")))
} yield resp
case req @ POST -> Root / Ident(id) / "reprocess" =>
for {
data <- req.as[IdList]

View File

@ -72,6 +72,31 @@ object RAttachmentPreview {
.to[Vector]
}
def findByItemAndCollective(
itemId: Ident,
coll: Ident
): ConnectionIO[Option[RAttachmentPreview]] = {
val sId = Columns.id.prefix("s")
val aId = RAttachment.Columns.id.prefix("a")
val aItem = RAttachment.Columns.itemId.prefix("a")
val aPos = RAttachment.Columns.position.prefix("a")
val iId = RItem.Columns.id.prefix("i")
val iColl = RItem.Columns.cid.prefix("i")
val from =
table ++ fr"s INNER JOIN" ++ RAttachment.table ++ fr"a ON" ++ sId.is(aId) ++
fr"INNER JOIN" ++ RItem.table ++ fr"i ON" ++ iId.is(aItem)
selectSimple(
all.map(_.prefix("s")) ++ List(aPos),
from,
and(aItem.is(itemId), iColl.is(coll))
)
.query[(RAttachmentPreview, Int)]
.to[Vector]
.map(_.sortBy(_._2).headOption.map(_._1))
}
def findByItemWithMeta(
id: Ident
): ConnectionIO[Vector[(RAttachmentPreview, FileMeta)]] = {