mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 10:59:33 +00:00
Initial impl of search route
This commit is contained in:
parent
f4596db63d
commit
a286556116
@ -12,7 +12,7 @@ import cats.implicits._
|
|||||||
|
|
||||||
import docspell.backend.PasswordCrypt
|
import docspell.backend.PasswordCrypt
|
||||||
import docspell.backend.auth.ShareToken
|
import docspell.backend.auth.ShareToken
|
||||||
import docspell.backend.ops.OShare.VerifyResult
|
import docspell.backend.ops.OShare.{ShareQuery, VerifyResult}
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.query.ItemQuery
|
import docspell.query.ItemQuery
|
||||||
import docspell.store.Store
|
import docspell.store.Store
|
||||||
@ -36,26 +36,37 @@ trait OShare[F[_]] {
|
|||||||
removePassword: Boolean
|
removePassword: Boolean
|
||||||
): F[OShare.ChangeResult]
|
): F[OShare.ChangeResult]
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
/** 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]
|
||||||
|
|
||||||
|
/** Verifies the authorization token. */
|
||||||
def verifyToken(key: ByteVector)(token: String): F[VerifyResult]
|
def verifyToken(key: ByteVector)(token: String): F[VerifyResult]
|
||||||
|
|
||||||
|
def findShareQuery(id: Ident): OptionT[F, ShareQuery]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OShare {
|
object OShare {
|
||||||
|
final case class ShareQuery(id: Ident, cid: Ident, query: ItemQuery)
|
||||||
|
|
||||||
sealed trait VerifyResult {
|
sealed trait VerifyResult {
|
||||||
def toEither: Either[String, ShareToken] =
|
def toEither: Either[String, ShareToken] =
|
||||||
this match {
|
this match {
|
||||||
case VerifyResult.Success(token) => Right(token)
|
case VerifyResult.Success(token, _) =>
|
||||||
case _ => Left("Authentication failed.")
|
Right(token)
|
||||||
|
case _ => Left("Authentication failed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object VerifyResult {
|
object VerifyResult {
|
||||||
case class Success(token: ShareToken) extends VerifyResult
|
case class Success(token: ShareToken, shareName: Option[String]) extends VerifyResult
|
||||||
case object NotFound extends VerifyResult
|
case object NotFound extends VerifyResult
|
||||||
case object PasswordMismatch extends VerifyResult
|
case object PasswordMismatch extends VerifyResult
|
||||||
case object InvalidToken extends VerifyResult
|
case object InvalidToken extends VerifyResult
|
||||||
|
|
||||||
def success(token: ShareToken): VerifyResult = Success(token)
|
def success(token: ShareToken): VerifyResult = Success(token, None)
|
||||||
|
def success(token: ShareToken, name: Option[String]): VerifyResult =
|
||||||
|
Success(token, name)
|
||||||
def notFound: VerifyResult = NotFound
|
def notFound: VerifyResult = NotFound
|
||||||
def passwordMismatch: VerifyResult = PasswordMismatch
|
def passwordMismatch: VerifyResult = PasswordMismatch
|
||||||
def invalidToken: VerifyResult = InvalidToken
|
def invalidToken: VerifyResult = InvalidToken
|
||||||
@ -158,8 +169,8 @@ object OShare {
|
|||||||
|
|
||||||
val token = ShareToken.create(id, shareKey)
|
val token = ShareToken.create(id, shareKey)
|
||||||
pwCheck match {
|
pwCheck match {
|
||||||
case Some(true) => token.map(VerifyResult.success)
|
case Some(true) => token.map(t => VerifyResult.success(t, share.name))
|
||||||
case None => token.map(VerifyResult.success)
|
case None => token.map(t => VerifyResult.success(t, share.name))
|
||||||
case Some(false) => VerifyResult.passwordMismatch.pure[F]
|
case Some(false) => VerifyResult.passwordMismatch.pure[F]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,5 +197,11 @@ object OShare {
|
|||||||
logger.debug(s"Invalid session token: $err") *>
|
logger.debug(s"Invalid session token: $err") *>
|
||||||
VerifyResult.invalidToken.pure[F]
|
VerifyResult.invalidToken.pure[F]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findShareQuery(id: Ident): OptionT[F, ShareQuery] =
|
||||||
|
RShare
|
||||||
|
.findCurrentActive(id)
|
||||||
|
.mapK(store.transform)
|
||||||
|
.map(share => ShareQuery(share.id, share.cid, share.query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1558,6 +1558,30 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
|
/share/search:
|
||||||
|
post:
|
||||||
|
operationId: "share-search"
|
||||||
|
tags: [Share]
|
||||||
|
summary: Performs a search in a share.
|
||||||
|
description: |
|
||||||
|
Allows to run a search query in the shared documents. The
|
||||||
|
input data structure is the same as with a standard query. The
|
||||||
|
`searchMode` parameter is ignored here.
|
||||||
|
security:
|
||||||
|
- shareTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ItemQuery"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ItemLightList"
|
||||||
|
|
||||||
/admin/user/resetPassword:
|
/admin/user/resetPassword:
|
||||||
post:
|
post:
|
||||||
operationId: "admin-user-reset-password"
|
operationId: "admin-user-reset-password"
|
||||||
@ -4248,6 +4272,11 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The name of the share if it exists. Only valid to use when
|
||||||
|
`success` is `true`.
|
||||||
|
|
||||||
ShareData:
|
ShareData:
|
||||||
description: |
|
description: |
|
||||||
@ -6475,6 +6504,10 @@ components:
|
|||||||
type: apiKey
|
type: apiKey
|
||||||
in: header
|
in: header
|
||||||
name: Docspell-Admin-Secret
|
name: Docspell-Admin-Secret
|
||||||
|
shareTokenHeader:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Docspell-Share-Auth
|
||||||
parameters:
|
parameters:
|
||||||
id:
|
id:
|
||||||
name: id
|
name: id
|
||||||
|
@ -81,16 +81,16 @@ object ShareRoutes {
|
|||||||
res <- backend.share
|
res <- backend.share
|
||||||
.verify(cfg.auth.serverSecret)(secret.shareId, secret.password)
|
.verify(cfg.auth.serverSecret)(secret.shareId, secret.password)
|
||||||
resp <- res match {
|
resp <- res match {
|
||||||
case VerifyResult.Success(token) =>
|
case VerifyResult.Success(token, name) =>
|
||||||
val cd = ShareCookieData(token)
|
val cd = ShareCookieData(token)
|
||||||
Ok(ShareVerifyResult(true, token.asString, false, "Success"))
|
Ok(ShareVerifyResult(true, token.asString, false, "Success", name))
|
||||||
.map(cd.addCookie(ClientRequestInfo.getBaseUrl(cfg, req)))
|
.map(cd.addCookie(ClientRequestInfo.getBaseUrl(cfg, req)))
|
||||||
case VerifyResult.PasswordMismatch =>
|
case VerifyResult.PasswordMismatch =>
|
||||||
Ok(ShareVerifyResult(false, "", true, "Failed"))
|
Ok(ShareVerifyResult(false, "", true, "Failed", None))
|
||||||
case VerifyResult.NotFound =>
|
case VerifyResult.NotFound =>
|
||||||
Ok(ShareVerifyResult(false, "", false, "Failed"))
|
Ok(ShareVerifyResult(false, "", false, "Failed", None))
|
||||||
case VerifyResult.InvalidToken =>
|
case VerifyResult.InvalidToken =>
|
||||||
Ok(ShareVerifyResult(false, "", false, "Failed"))
|
Ok(ShareVerifyResult(false, "", false, "Failed", None))
|
||||||
}
|
}
|
||||||
} yield resp
|
} yield resp
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,20 @@
|
|||||||
package docspell.restserver.routes
|
package docspell.restserver.routes
|
||||||
|
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.backend.BackendApp
|
import docspell.backend.BackendApp
|
||||||
import docspell.backend.auth.ShareToken
|
import docspell.backend.auth.ShareToken
|
||||||
import docspell.common.Logger
|
import docspell.backend.ops.OSimpleSearch
|
||||||
|
import docspell.common._
|
||||||
|
import docspell.restapi.model.ItemQuery
|
||||||
import docspell.restserver.Config
|
import docspell.restserver.Config
|
||||||
|
import docspell.store.qb.Batch
|
||||||
|
import docspell.store.queries.Query
|
||||||
|
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
|
||||||
object ShareSearchRoutes {
|
object ShareSearchRoutes {
|
||||||
|
|
||||||
@ -23,7 +30,37 @@ object ShareSearchRoutes {
|
|||||||
token: ShareToken
|
token: ShareToken
|
||||||
): HttpRoutes[F] = {
|
): HttpRoutes[F] = {
|
||||||
val logger = Logger.log4s[F](org.log4s.getLogger)
|
val logger = Logger.log4s[F](org.log4s.getLogger)
|
||||||
logger.trace(s"$backend $cfg $token")
|
|
||||||
???
|
val dsl = new Http4sDsl[F] {}
|
||||||
|
import dsl._
|
||||||
|
|
||||||
|
HttpRoutes.of { case req @ POST -> Root =>
|
||||||
|
backend.share
|
||||||
|
.findShareQuery(token.id)
|
||||||
|
.semiflatMap { share =>
|
||||||
|
for {
|
||||||
|
userQuery <- req.as[ItemQuery]
|
||||||
|
batch = Batch(
|
||||||
|
userQuery.offset.getOrElse(0),
|
||||||
|
userQuery.limit.getOrElse(cfg.maxItemPageSize)
|
||||||
|
).restrictLimitTo(
|
||||||
|
cfg.maxItemPageSize
|
||||||
|
)
|
||||||
|
itemQuery = ItemQueryString(userQuery.query)
|
||||||
|
settings = OSimpleSearch.Settings(
|
||||||
|
batch,
|
||||||
|
cfg.fullTextSearch.enabled,
|
||||||
|
userQuery.withDetails.getOrElse(false),
|
||||||
|
cfg.maxNoteLength,
|
||||||
|
searchMode = SearchMode.Normal
|
||||||
|
)
|
||||||
|
account = AccountId(share.cid, Ident.unsafe(""))
|
||||||
|
fixQuery = Query.Fix(account, Some(share.query.expr), None)
|
||||||
|
_ <- logger.debug(s"Searching in share ${share.id.id}: ${userQuery.query}")
|
||||||
|
resp <- ItemRoutes.searchItems(backend, dsl)(settings, fixQuery, itemQuery)
|
||||||
|
} yield resp
|
||||||
|
}
|
||||||
|
.getOrElseF(NotFound())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user