Add route to get settings for a share

Returns the client settings of the creator.
This commit is contained in:
eikek
2022-01-29 10:11:54 +01:00
parent c29ce73dd0
commit 1ca64f09d1
4 changed files with 78 additions and 9 deletions

View File

@ -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. */ /** Verifies the given id and password and returns a authorization token on success. */
def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult] def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult]
@ -277,6 +279,9 @@ object OShare {
VerifyResult.invalidToken.pure[F] 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] = def findShareQuery(id: Ident): OptionT[F, ShareQuery] =
RShare RShare
.findCurrentActive(id) .findCurrentActive(id)

View File

@ -2304,7 +2304,7 @@ paths:
/share/attachment/{id}/preview: /share/attachment/{id}/preview:
head: head:
operationId: "share-attach-check-preview" operationId: "share-attach-check-preview"
tags: [ Attachment ] tags: [ Share ]
summary: Get the headers to a preview image of an attachment file. summary: Get the headers to a preview image of an attachment file.
description: | description: |
Checks if an image file showing a preview of the attachment is Checks if an image file showing a preview of the attachment is
@ -2320,7 +2320,7 @@ paths:
description: NotFound description: NotFound
get: get:
operationId: "share-attach-get-preview" operationId: "share-attach-get-preview"
tags: [ Attachment ] tags: [ Share ]
summary: Get a preview image of an attachment file. summary: Get a preview image of an attachment file.
description: | description: |
Gets a image file showing a preview of the attachment. Usually Gets a image file showing a preview of the attachment. Usually
@ -2347,6 +2347,37 @@ paths:
schema: schema:
type: string type: string
format: binary 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: /admin/user/resetPassword:
post: post:

View File

@ -213,7 +213,8 @@ object RestServer {
Router( Router(
"search" -> ShareSearchRoutes(restApp.backend, cfg, token), "search" -> ShareSearchRoutes(restApp.backend, cfg, token),
"attachment" -> ShareAttachmentRoutes(restApp.backend, 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] = { def redirectTo[F[_]: Async](path: String): HttpRoutes[F] = {

View File

@ -8,10 +8,11 @@ package docspell.restserver.routes
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import cats.kernel.Semigroup import cats.{Monad, Semigroup}
import cats.data.OptionT
import docspell.backend.BackendApp import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken import docspell.backend.auth.{AuthToken, ShareToken}
import docspell.common._ import docspell.common._
import docspell.restapi.model._ import docspell.restapi.model._
@ -23,6 +24,30 @@ import org.http4s.dsl.Http4sDsl
object ClientSettingsRoutes { 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] = { def apply[F[_]: Async](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
val dsl = new Http4sDsl[F] {} val dsl = new Http4sDsl[F] {}
import dsl._ import dsl._
@ -30,10 +55,7 @@ object ClientSettingsRoutes {
HttpRoutes.of { HttpRoutes.of {
case GET -> Root / Ident(clientId) => case GET -> Root / Ident(clientId) =>
for { for {
collData <- backend.clientSettings.loadCollective(clientId, user.account) mergedData <- getMergedSettings(backend, user.account, clientId)
userData <- backend.clientSettings.loadUser(clientId, user.account)
mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData)
res <- mergedData match { res <- mergedData match {
case Some(j) => Ok(j) 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] = implicit def jsonSemigroup: Semigroup[Json] =
Semigroup.instance((a1, a2) => a1.deepMerge(a2)) Semigroup.instance((a1, a2) => a1.deepMerge(a2))
} }