From cbc95b11e65e0c20def2b49a854ac2df93a092fc Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Sun, 22 Mar 2020 21:21:49 +0100 Subject: [PATCH] Add routes to retrive the archive of an attachment --- .../scala/docspell/backend/ops/OItem.scala | 39 ++++++++++---- .../src/main/resources/docspell-openapi.yml | 52 +++++++++++++++++++ .../restserver/routes/AttachmentRoutes.scala | 22 ++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) 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 cdc2621d..f6b448d8 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OItem.scala @@ -8,17 +8,10 @@ import doobie._ import doobie.implicits._ import docspell.store.{AddResult, Store} import docspell.store.queries.{QAttachment, QItem} -import OItem.{AttachmentData, AttachmentSourceData, ItemData, ListItem, Query} +import OItem.{AttachmentArchiveData, AttachmentData, AttachmentSourceData, ItemData, ListItem, Query} import bitpeace.{FileMeta, RangeDef} import docspell.common.{Direction, Ident, ItemState, MetaProposalList, Timestamp} -import docspell.store.records.{ - RAttachment, - RAttachmentMeta, - RAttachmentSource, - RItem, - RSource, - RTagItem -} +import docspell.store.records._ trait OItem[F[_]] { @@ -30,6 +23,8 @@ trait OItem[F[_]] { def findAttachmentSource(id: Ident, collective: Ident): F[Option[AttachmentSourceData[F]]] + def findAttachmentArchive(id: Ident, collective: Ident): F[Option[AttachmentArchiveData[F]]] + def setTags(item: Ident, tagIds: List[Ident], collective: Ident): F[AddResult] def setDirection(item: Ident, direction: Direction, collective: Ident): F[AddResult] @@ -96,6 +91,15 @@ object OItem { val fileId = rs.fileId } + case class AttachmentArchiveData[F[_]]( + rs: RAttachmentArchive, + meta: FileMeta, + data: Stream[F, Byte] + ) extends BinaryData[F] { + val name = rs.name + val fileId = rs.fileId + } + def apply[F[_]: Effect](store: Store[F]): Resource[F, OItem[F]] = Resource.pure[F, OItem[F]](new OItem[F] { @@ -139,6 +143,23 @@ object OItem { (None: Option[AttachmentSourceData[F]]).pure[F] }) + def findAttachmentArchive(id: Ident, collective: Ident): F[Option[AttachmentArchiveData[F]]] = + store + .transact(RAttachmentArchive.findByIdAndCollective(id, collective)) + .flatMap({ + case Some(ra) => + makeBinaryData(ra.fileId) { m => + AttachmentArchiveData[F]( + ra, + m, + store.bitpeace.fetchData2(RangeDef.all)(Stream.emit(m)) + ) + } + + case None => + (None: Option[AttachmentArchiveData[F]]).pure[F] + }) + private def makeBinaryData[A](fileId: Ident)(f: FileMeta => A): F[Option[A]] = store.bitpeace .get(fileId.id) diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 12d9c61a..6176d345 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1265,6 +1265,58 @@ paths: schema: type: string format: binary + /sec/attachment/{id}/archive: + head: + tags: [ Attachment ] + summary: Get an attachment archive file. + description: | + Get information about the archive that contains the attachment + with the given id. + + If the attachment was not uploaded as part of an archive, 404 + is returned. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + responses: + 200: + description: Ok + headers: + Content-Type: + schema: + type: string + Content-Length: + schema: + type: integer + format: int64 + ETag: + schema: + type: string + Content-Disposition: + schema: + type: string + get: + tags: [ Attachment ] + summary: Get an attachment archive file. + description: | + Get the archive file that was originally uploaded that + contains the attachment with the given id. + + If the attachment was not uploaded as part of an archive, a + 404 is returned. + security: + - authTokenHeader: [] + parameters: + - $ref: "#/components/parameters/id" + responses: + 200: + description: Ok + content: + application/octet-stream: + schema: + type: string + format: binary /sec/attachment/{id}/meta: get: tags: [ Attachment ] diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala index 503be6d1..071a5807 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/AttachmentRoutes.scala @@ -83,6 +83,28 @@ object AttachmentRoutes { .getOrElse(NotFound(BasicResult(false, "Not found"))) } yield resp + case HEAD -> Root / Ident(id) / "archive" => + for { + fileData <- backend.item.findAttachmentArchive(id, user.account.collective) + resp <- fileData + .map(data => withResponseHeaders(Ok())(data)) + .getOrElse(NotFound(BasicResult(false, "Not found"))) + } yield resp + + case req @ GET -> Root / Ident(id) / "archive" => + for { + fileData <- backend.item.findAttachmentArchive(id, user.account.collective) + inm = req.headers.get(`If-None-Match`).flatMap(_.tags) + matches = matchETag(fileData.map(_.meta), inm) + resp <- fileData + .map { data => + if (matches) withResponseHeaders(NotModified())(data) + else makeByteResp(data) + } + .getOrElse(NotFound(BasicResult(false, "Not found"))) + } yield resp + + case GET -> Root / Ident(id) / "view" => // this route exists to provide a stable url // it redirects currently to viewerjs