mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Use search stats to populate search menu
This commit is contained in:
@ -10,17 +10,18 @@ import cats.data.NonEmptyList
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.ops.OItemSearch.AttachmentPreviewData
|
||||
import docspell.backend.ops._
|
||||
import docspell.restapi.model.BasicResult
|
||||
import docspell.store.records.RFileMeta
|
||||
import docspell.restserver.http4s.{QueryParam => QP}
|
||||
import docspell.store.records.RFileMeta
|
||||
|
||||
import org.http4s._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.headers._
|
||||
import org.http4s.headers.ETag.EntityTag
|
||||
import org.http4s.headers._
|
||||
import org.typelevel.ci.CIString
|
||||
|
||||
object BinaryUtil {
|
||||
@ -53,6 +54,15 @@ object BinaryUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def respondHead[F[_]: Async](
|
||||
dsl: Http4sDsl[F]
|
||||
)(fileData: Option[AttachmentPreviewData[F]]): F[Response[F]] = {
|
||||
import dsl._
|
||||
fileData
|
||||
.map(data => withResponseHeaders(dsl, Ok())(data))
|
||||
.getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
}
|
||||
|
||||
def withResponseHeaders[F[_]: Sync](dsl: Http4sDsl[F], resp: F[Response[F]])(
|
||||
data: OItemSearch.BinaryData[F]
|
||||
): F[Response[F]] = {
|
||||
|
@ -125,10 +125,7 @@ object AttachmentRoutes {
|
||||
for {
|
||||
fileData <-
|
||||
backend.itemSearch.findAttachmentPreview(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 POST -> Root / Ident(id) / "preview" =>
|
||||
|
@ -28,11 +28,11 @@ import docspell.restserver.http4s.BinaryUtil
|
||||
import docspell.restserver.http4s.Responses
|
||||
import docspell.restserver.http4s.{QueryParam => QP}
|
||||
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.headers._
|
||||
import org.http4s.{HttpRoutes, Response}
|
||||
import org.log4s._
|
||||
|
||||
object ItemRoutes {
|
||||
@ -415,7 +415,11 @@ object ItemRoutes {
|
||||
def searchItems[F[_]: Sync](
|
||||
backend: BackendApp[F],
|
||||
dsl: Http4sDsl[F]
|
||||
)(settings: OSimpleSearch.Settings, fixQuery: Query.Fix, itemQuery: ItemQueryString) = {
|
||||
)(
|
||||
settings: OSimpleSearch.Settings,
|
||||
fixQuery: Query.Fix,
|
||||
itemQuery: ItemQueryString
|
||||
): F[Response[F]] = {
|
||||
import dsl._
|
||||
|
||||
def convertFts(res: OSimpleSearch.Items.FtsItems): ItemLightList =
|
||||
@ -459,7 +463,7 @@ object ItemRoutes {
|
||||
settings: OSimpleSearch.StatsSettings,
|
||||
fixQuery: Query.Fix,
|
||||
itemQuery: ItemQueryString
|
||||
) = {
|
||||
): F[Response[F]] = {
|
||||
import dsl._
|
||||
|
||||
backend.simpleSearch
|
||||
@ -479,7 +483,6 @@ object ItemRoutes {
|
||||
case StringSearchResult.ParseFailed(pf) =>
|
||||
BadRequest(BasicResult(false, s"Error reading query: ${pf.render}"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
implicit final class OptionString(opt: Option[String]) {
|
||||
|
@ -26,12 +26,20 @@ object ShareAttachmentRoutes {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of { case req @ GET -> Root / Ident(id) / "preview" =>
|
||||
for {
|
||||
fileData <-
|
||||
backend.share.findAttachmentPreview(id, token.id).value
|
||||
resp <- BinaryUtil.respond(dsl, req)(fileData)
|
||||
} yield resp
|
||||
HttpRoutes.of {
|
||||
case req @ GET -> Root / Ident(id) / "preview" =>
|
||||
for {
|
||||
fileData <-
|
||||
backend.share.findAttachmentPreview(id, token.id).value
|
||||
resp <- BinaryUtil.respond(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)
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,15 +12,19 @@ import cats.implicits._
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.ShareToken
|
||||
import docspell.backend.ops.OSimpleSearch
|
||||
import docspell.backend.ops.OSimpleSearch.StringSearchResult
|
||||
import docspell.common._
|
||||
import docspell.restapi.model.ItemQuery
|
||||
import docspell.query.FulltextExtract.Result.{TooMany, UnsupportedPosition}
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.Config
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.store.qb.Batch
|
||||
import docspell.store.queries.Query
|
||||
import docspell.store.queries.{Query, SearchSummary}
|
||||
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
import org.http4s.{HttpRoutes, Response}
|
||||
|
||||
object ShareSearchRoutes {
|
||||
|
||||
@ -34,33 +38,68 @@ object ShareSearchRoutes {
|
||||
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 = share.asAccount
|
||||
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())
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / "query" =>
|
||||
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 = share.asAccount
|
||||
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())
|
||||
|
||||
case req @ POST -> Root / "stats" =>
|
||||
for {
|
||||
userQuery <- req.as[ItemQuery]
|
||||
itemQuery = ItemQueryString(userQuery.query)
|
||||
settings = OSimpleSearch.StatsSettings(
|
||||
useFTS = cfg.fullTextSearch.enabled,
|
||||
searchMode = userQuery.searchMode.getOrElse(SearchMode.Normal)
|
||||
)
|
||||
stats <- backend.share.searchSummary(settings)(token.id, itemQuery).value
|
||||
resp <- stats.map(mkSummaryResponse(dsl)).getOrElse(NotFound())
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
def mkSummaryResponse[F[_]: Sync](
|
||||
dsl: Http4sDsl[F]
|
||||
)(r: StringSearchResult[SearchSummary]): F[Response[F]] = {
|
||||
import dsl._
|
||||
r match {
|
||||
case StringSearchResult.Success(summary) =>
|
||||
Ok(Conversions.mkSearchStats(summary))
|
||||
case StringSearchResult.FulltextMismatch(TooMany) =>
|
||||
BadRequest(BasicResult(false, "Fulltext search is not possible in this share."))
|
||||
case StringSearchResult.FulltextMismatch(UnsupportedPosition) =>
|
||||
BadRequest(
|
||||
BasicResult(
|
||||
false,
|
||||
"Fulltext search must be in root position or inside the first AND."
|
||||
)
|
||||
)
|
||||
case StringSearchResult.ParseFailed(pf) =>
|
||||
BadRequest(BasicResult(false, s"Error reading query: ${pf.render}"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user