Implement binary routes for shares

This commit is contained in:
eikek 2021-10-06 00:48:48 +02:00
parent 4ad90b76b4
commit 9eb2f9c6fe
6 changed files with 97 additions and 41 deletions

View File

@ -12,7 +12,7 @@ import cats.implicits._
import docspell.backend.PasswordCrypt
import docspell.backend.auth.ShareToken
import docspell.backend.ops.OItemSearch.{AttachmentPreviewData, Batch, Query}
import docspell.backend.ops.OItemSearch._
import docspell.backend.ops.OShare.{ShareQuery, VerifyResult}
import docspell.backend.ops.OSimpleSearch.StringSearchResult
import docspell.common._
@ -55,6 +55,8 @@ trait OShare[F[_]] {
shareId: Ident
): OptionT[F, AttachmentPreviewData[F]]
def findAttachment(attachId: Ident, shareId: Ident): OptionT[F, AttachmentData[F]]
def searchSummary(
settings: OSimpleSearch.StatsSettings
)(shareId: Ident, q: ItemQueryString): OptionT[F, StringSearchResult[SearchSummary]]
@ -232,24 +234,36 @@ object OShare {
): OptionT[F, AttachmentPreviewData[F]] =
for {
sq <- findShareQuery(shareId)
account = sq.asAccount
checkQuery = Query(
Query.Fix(account, Some(sq.query.expr), None),
Query.QueryExpr(AttachId(attachId.id))
)
checkRes <- OptionT.liftF(itemSearch.findItems(0)(checkQuery, Batch.limit(1)))
res <-
if (checkRes.isEmpty)
OptionT
.liftF(
logger.info(
s"Attempt to load unshared attachment '${attachId.id}' for share: ${shareId.id}"
)
)
.mapFilter(_ => None)
else OptionT(itemSearch.findAttachmentPreview(attachId, sq.cid))
_ <- checkAttachment(sq, attachId)
res <- OptionT(itemSearch.findAttachmentPreview(attachId, sq.cid))
} yield res
def findAttachment(attachId: Ident, shareId: Ident): OptionT[F, AttachmentData[F]] =
for {
sq <- findShareQuery(shareId)
_ <- checkAttachment(sq, attachId)
res <- OptionT(itemSearch.findAttachment(attachId, sq.cid))
} yield res
/** Check whether the attachment with the given id is in the results of the given
* share
*/
private def checkAttachment(sq: ShareQuery, attachId: Ident): OptionT[F, Unit] = {
val checkQuery = Query(
Query.Fix(sq.asAccount, Some(sq.query.expr), None),
Query.QueryExpr(AttachId(attachId.id))
)
OptionT(
itemSearch
.findItems(0)(checkQuery, Batch.limit(1))
.map(_.headOption.map(_ => ()))
).flatTapNone(
logger.info(
s"Attempt to load unshared attachment '${attachId.id}' via share: ${sq.id.id}"
)
)
}
def searchSummary(
settings: OSimpleSearch.StatsSettings
)(

View File

@ -7,9 +7,11 @@
package docspell.restserver.conv
import java.time.{LocalDate, ZoneId}
import cats.effect.{Async, Sync}
import cats.implicits._
import fs2.Stream
import docspell.backend.ops.OCollective.{InsightData, PassChangeResult}
import docspell.backend.ops.OCustomFields.SetValueResult
import docspell.backend.ops.OJob.JobCancelResult
@ -23,6 +25,7 @@ import docspell.restserver.conv.Conversions._
import docspell.store.queries.{AttachmentLight => QAttachmentLight, IdRefCount}
import docspell.store.records._
import docspell.store.{AddResult, UpdateResult}
import org.http4s.headers.`Content-Type`
import org.http4s.multipart.Multipart
import org.log4s.Logger

View File

@ -11,7 +11,7 @@ import cats.data.OptionT
import cats.effect._
import cats.implicits._
import docspell.backend.ops.OItemSearch.AttachmentPreviewData
import docspell.backend.ops.OItemSearch.{AttachmentData, AttachmentPreviewData}
import docspell.backend.ops._
import docspell.restapi.model.BasicResult
import docspell.restserver.http4s.{QueryParam => QP}
@ -27,6 +27,31 @@ import org.typelevel.ci.CIString
object BinaryUtil {
def respond[F[_]: Async](dsl: Http4sDsl[F], req: Request[F])(
fileData: Option[AttachmentData[F]]
): F[Response[F]] = {
import dsl._
val inm = req.headers.get[`If-None-Match`].flatMap(_.tags)
val matches = BinaryUtil.matchETag(fileData.map(_.meta), inm)
fileData
.map { data =>
if (matches) withResponseHeaders(dsl, NotModified())(data)
else makeByteResp(dsl)(data)
}
.getOrElse(NotFound(BasicResult(false, "Not found")))
}
def respondHead[F[_]: Async](dsl: Http4sDsl[F])(
fileData: Option[AttachmentData[F]]
): F[Response[F]] = {
import dsl._
fileData
.map(data => withResponseHeaders(dsl, Ok())(data))
.getOrElse(NotFound(BasicResult(false, "Not found")))
}
def respondPreview[F[_]: Async](dsl: Http4sDsl[F], req: Request[F])(
fileData: Option[AttachmentPreviewData[F]]
): F[Response[F]] = {
import dsl._
@ -54,7 +79,7 @@ object BinaryUtil {
}
}
def respondHead[F[_]: Async](
def respondPreviewHead[F[_]: Async](
dsl: Http4sDsl[F]
)(fileData: Option[AttachmentPreviewData[F]]): F[Response[F]] = {
import dsl._

View File

@ -46,24 +46,13 @@ object AttachmentRoutes {
case HEAD -> Root / Ident(id) =>
for {
fileData <- backend.itemSearch.findAttachment(id, user.account.collective)
resp <-
fileData
.map(data => withResponseHeaders(Ok())(data))
.getOrElse(NotFound(BasicResult(false, "Not found")))
resp <- BinaryUtil.respondHead(dsl)(fileData)
} yield resp
case req @ GET -> Root / Ident(id) =>
for {
fileData <- backend.itemSearch.findAttachment(id, user.account.collective)
inm = req.headers.get[`If-None-Match`].flatMap(_.tags)
matches = BinaryUtil.matchETag(fileData.map(_.meta), inm)
resp <-
fileData
.map { data =>
if (matches) withResponseHeaders(NotModified())(data)
else makeByteResp(data)
}
.getOrElse(NotFound(BasicResult(false, "Not found")))
resp <- BinaryUtil.respond[F](dsl, req)(fileData)
} yield resp
case HEAD -> Root / Ident(id) / "original" =>
@ -118,14 +107,14 @@ object AttachmentRoutes {
for {
fileData <-
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
resp <- BinaryUtil.respond(dsl, req)(fileData)
resp <- BinaryUtil.respondPreview(dsl, req)(fileData)
} yield resp
case HEAD -> Root / Ident(id) / "preview" =>
for {
fileData <-
backend.itemSearch.findAttachmentPreview(id, user.account.collective)
resp <- BinaryUtil.respondHead(dsl)(fileData)
resp <- BinaryUtil.respondPreviewHead(dsl)(fileData)
} yield resp
case POST -> Root / Ident(id) / "preview" =>

View File

@ -13,9 +13,11 @@ import docspell.backend.BackendApp
import docspell.backend.auth.ShareToken
import docspell.common._
import docspell.restserver.http4s.BinaryUtil
import docspell.restserver.webapp.Webjars
import org.http4s.HttpRoutes
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.headers._
object ShareAttachmentRoutes {
@ -27,18 +29,35 @@ object ShareAttachmentRoutes {
import dsl._
HttpRoutes.of {
case HEAD -> Root / Ident(id) =>
for {
fileData <- backend.share.findAttachment(id, token.id).value
resp <- BinaryUtil.respondHead(dsl)(fileData)
} yield resp
case req @ GET -> Root / Ident(id) =>
for {
fileData <- backend.share.findAttachment(id, token.id).value
resp <- BinaryUtil.respond(dsl, req)(fileData)
} yield resp
case GET -> Root / Ident(id) / "view" =>
// this route exists to provide a stable url
// it redirects currently to viewerjs
val attachUrl = s"/api/v1/share/attachment/${id.id}"
val path = s"/app/assets${Webjars.viewerjs}/index.html#$attachUrl"
SeeOther(Location(Uri(path = Uri.Path.unsafeFromString(path))))
case req @ GET -> Root / Ident(id) / "preview" =>
for {
fileData <-
backend.share.findAttachmentPreview(id, token.id).value
resp <- BinaryUtil.respond(dsl, req)(fileData)
fileData <- backend.share.findAttachmentPreview(id, token.id).value
resp <- BinaryUtil.respondPreview(dsl, req)(fileData)
} yield resp
case HEAD -> Root / Ident(id) / "preview" =>
for {
fileData <-
backend.share.findAttachmentPreview(id, token.id).value
resp <- BinaryUtil.respondHead(dsl)(fileData)
fileData <- backend.share.findAttachmentPreview(id, token.id).value
resp <- BinaryUtil.respondPreviewHead(dsl)(fileData)
} yield resp
}
}

View File

@ -1,3 +1,9 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.store.queries
import docspell.common._