From 1ca64f09d1dc44475e85bac5d68ecdadcd109056 Mon Sep 17 00:00:00 2001 From: eikek Date: Sat, 29 Jan 2022 10:11:54 +0100 Subject: [PATCH] Add route to get settings for a share Returns the client settings of the creator. --- .../scala/docspell/backend/ops/OShare.scala | 5 +++ .../src/main/resources/docspell-openapi.yml | 35 ++++++++++++++- .../docspell/restserver/RestServer.scala | 3 +- .../routes/ClientSettingsRoutes.scala | 44 ++++++++++++++++--- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OShare.scala b/modules/backend/src/main/scala/docspell/backend/ops/OShare.scala index cd0bac69..75bdc27a 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OShare.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OShare.scala @@ -48,6 +48,8 @@ trait OShare[F[_]] { // --- + def findActiveById(id: Ident): OptionT[F, ShareData] + /** Verifies the given id and password and returns a authorization token on success. */ def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult] @@ -277,6 +279,9 @@ object OShare { VerifyResult.invalidToken.pure[F] } + def findActiveById(id: Ident): OptionT[F, ShareData] = + RShare.findCurrentActive(id).mapK(store.transform).map(ShareData.tupled) + def findShareQuery(id: Ident): OptionT[F, ShareQuery] = RShare .findCurrentActive(id) diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index c585b147..11deb901 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -2304,7 +2304,7 @@ paths: /share/attachment/{id}/preview: head: operationId: "share-attach-check-preview" - tags: [ Attachment ] + tags: [ Share ] summary: Get the headers to a preview image of an attachment file. description: | Checks if an image file showing a preview of the attachment is @@ -2320,7 +2320,7 @@ paths: description: NotFound get: operationId: "share-attach-get-preview" - tags: [ Attachment ] + tags: [ Share ] summary: Get a preview image of an attachment file. description: | Gets a image file showing a preview of the attachment. Usually @@ -2347,6 +2347,37 @@ paths: schema: type: string format: binary + /share/clientSettings/{clientId}: + parameters: + - $ref: "#/components/parameters/clientId" + get: + operationId: "share-clientsettings-get" + tags: [ Share ] + summary: Return the client settings of current user + description: | + Returns the settings for the share. This is the settings of + the user who created the share. It is created by merging the + client settings for the collective and the user's own client + settings into one json structure. + + Null, Array, Boolean, String and Number are treated as values, + and values from the users settings completely replace values + from the collective's settings. + + The `clientId` is an identifier to a client application. It + returns a JSON structure. The server doesn't care about the + actual data, since it is meant to be interpreted by clients. + security: + - shareTokenHeader: [] + responses: + 422: + description: BadRequest + 200: + description: Ok + content: + application/json: + schema: {} + /admin/user/resetPassword: post: diff --git a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala index dd36286e..2d85a4f9 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/RestServer.scala @@ -213,7 +213,8 @@ object RestServer { Router( "search" -> ShareSearchRoutes(restApp.backend, cfg, token), "attachment" -> ShareAttachmentRoutes(restApp.backend, token), - "item" -> ShareItemRoutes(restApp.backend, token) + "item" -> ShareItemRoutes(restApp.backend, token), + "clientSettings" -> ClientSettingsRoutes.share(restApp.backend, token) ) def redirectTo[F[_]: Async](path: String): HttpRoutes[F] = { diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala index aef0b386..25f8a4df 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala @@ -8,10 +8,11 @@ package docspell.restserver.routes import cats.effect._ import cats.implicits._ -import cats.kernel.Semigroup +import cats.{Monad, Semigroup} +import cats.data.OptionT import docspell.backend.BackendApp -import docspell.backend.auth.AuthToken +import docspell.backend.auth.{AuthToken, ShareToken} import docspell.common._ import docspell.restapi.model._ @@ -23,6 +24,30 @@ import org.http4s.dsl.Http4sDsl object ClientSettingsRoutes { + def share[F[_]: Async]( + backend: BackendApp[F], + token: ShareToken + ): HttpRoutes[F] = { + val logger = Logger.log4s[F](org.log4s.getLogger) + + val dsl = new Http4sDsl[F] {} + import dsl._ + + HttpRoutes.of { + case GET -> Root / Ident(clientId) => + (for { + _ <- OptionT.liftF(logger.debug(s"Get client settings for share ${token.id}")) + share <- backend.share.findActiveById(token.id) + merged <- OptionT.liftF(getMergedSettings(backend, share.user.accountId, clientId)) + res <- OptionT.liftF(merged match { + case Some(j) => Ok(j) + case None => NotFound() + }) + } yield res) + .getOrElseF(Ok(Map.empty[String, String])) + } + } + def apply[F[_]: Async](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = { val dsl = new Http4sDsl[F] {} import dsl._ @@ -30,10 +55,7 @@ object ClientSettingsRoutes { HttpRoutes.of { case GET -> Root / Ident(clientId) => for { - collData <- backend.clientSettings.loadCollective(clientId, user.account) - userData <- backend.clientSettings.loadUser(clientId, user.account) - - mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData) + mergedData <- getMergedSettings(backend, user.account, clientId) res <- mergedData match { case Some(j) => Ok(j) @@ -97,6 +119,16 @@ object ClientSettingsRoutes { } } + + def getMergedSettings[F[_]: Monad](backend:BackendApp[F], account: AccountId, clientId: Ident) = + for { + collData <- backend.clientSettings.loadCollective(clientId, account) + userData <- backend.clientSettings.loadUser(clientId, account) + + mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData) + } yield mergedData + + implicit def jsonSemigroup: Semigroup[Json] = Semigroup.instance((a1, a2) => a1.deepMerge(a2)) }