From edb344314fde8199948b8e26e0f01913a40cb677 Mon Sep 17 00:00:00 2001 From: eikek Date: Sat, 14 Aug 2021 15:08:29 +0200 Subject: [PATCH] Use an enum instead of a boolean to differentiate search It's not very likely to have more modes of search besides normal and trashed, but got surprised in that way quite often and it's nicer this way anyways. --- build.sbt | 4 ++ .../docspell/backend/ops/OSimpleSearch.scala | 16 ++++--- .../scala/docspell/common/SearchMode.scala | 43 +++++++++++++++++++ .../src/main/resources/docspell-openapi.yml | 23 ++++++---- .../restserver/http4s/QueryParam.scala | 8 +++- .../restserver/routes/ItemRoutes.scala | 12 +++--- .../webapp/src/main/elm/Data/ItemQuery.elm | 2 +- 7 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 modules/common/src/main/scala/docspell/common/SearchMode.scala diff --git a/build.sbt b/build.sbt index 3eea7c5e..3d1b1272 100644 --- a/build.sbt +++ b/build.sbt @@ -238,6 +238,10 @@ val openapiScalaSettings = Seq( field.copy(typeDef = TypeDef("EquipmentUse", Imports("docspell.common.EquipmentUse")) ) + case "searchmode" => + field => + field + .copy(typeDef = TypeDef("SearchMode", Imports("docspell.common.SearchMode"))) })) ) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OSimpleSearch.scala b/modules/backend/src/main/scala/docspell/backend/ops/OSimpleSearch.scala index aa2ecf00..2ae4cd14 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OSimpleSearch.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OSimpleSearch.scala @@ -87,11 +87,11 @@ object OSimpleSearch { useFTS: Boolean, resolveDetails: Boolean, maxNoteLen: Int, - deleted: Boolean + searchMode: SearchMode ) final case class StatsSettings( useFTS: Boolean, - deleted: Boolean + searchMode: SearchMode ) sealed trait Items { @@ -223,8 +223,10 @@ object OSimpleSearch { // 2. sql+fulltext if fulltextQuery.isDefined && q.nonEmpty && useFTS // 3. sql-only else (if fulltextQuery.isEmpty || !useFTS) val validItemQuery = - if (settings.deleted) q.withFix(_.andQuery(ItemQuery.Expr.Trashed)) - else q.withFix(_.andQuery(ItemQuery.Expr.ValidItemStates)) + settings.searchMode match { + case SearchMode.Trashed => q.withFix(_.andQuery(ItemQuery.Expr.Trashed)) + case SearchMode.Normal => q.withFix(_.andQuery(ItemQuery.Expr.ValidItemStates)) + } fulltextQuery match { case Some(ftq) if settings.useFTS => if (q.isEmpty) { @@ -280,8 +282,10 @@ object OSimpleSearch { settings: StatsSettings )(q: Query, fulltextQuery: Option[String]): F[SearchSummary] = { val validItemQuery = - if (settings.deleted) q.withFix(_.andQuery(ItemQuery.Expr.Trashed)) - else q.withFix(_.andQuery(ItemQuery.Expr.ValidItemStates)) + settings.searchMode match { + case SearchMode.Trashed => q.withFix(_.andQuery(ItemQuery.Expr.Trashed)) + case SearchMode.Normal => q.withFix(_.andQuery(ItemQuery.Expr.ValidItemStates)) + } fulltextQuery match { case Some(ftq) if settings.useFTS => if (q.isEmpty) diff --git a/modules/common/src/main/scala/docspell/common/SearchMode.scala b/modules/common/src/main/scala/docspell/common/SearchMode.scala new file mode 100644 index 00000000..451f5de9 --- /dev/null +++ b/modules/common/src/main/scala/docspell/common/SearchMode.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Docspell Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package docspell.common + +import cats.data.NonEmptyList + +import io.circe.Decoder +import io.circe.Encoder + +sealed trait SearchMode { self: Product => + + final def name: String = + productPrefix.toLowerCase + +} + +object SearchMode { + + final case object Normal extends SearchMode + final case object Trashed extends SearchMode + + def fromString(str: String): Either[String, SearchMode] = + str.toLowerCase match { + case "normal" => Right(Normal) + case "trashed" => Right(Trashed) + case _ => Left(s"Invalid search mode: $str") + } + + val all: NonEmptyList[SearchMode] = + NonEmptyList.of(Normal, Trashed) + + def unsafe(str: String): SearchMode = + fromString(str).fold(sys.error, identity) + + implicit val jsonDecoder: Decoder[SearchMode] = + Decoder.decodeString.emap(fromString) + implicit val jsonEncoder: Encoder[SearchMode] = + Encoder.encodeString.contramap(_.name) +} diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index a45d266f..48a65da4 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1478,7 +1478,7 @@ paths: - $ref: "#/components/parameters/limit" - $ref: "#/components/parameters/offset" - $ref: "#/components/parameters/withDetails" - - $ref: "#/components/parameters/deleted" + - $ref: "#/components/parameters/searchMode" responses: 200: description: Ok @@ -4114,12 +4114,16 @@ components: withDetails: type: boolean default: false - deleted: - type: boolean - default: false + searchMode: + type: string + format: searchmode + enum: + - normal + - trashed + default: normal description: | - If this is true, the search performed only for - soft-deleted items. + Specify whether the search query should apply to + soft-deleted items or not. query: type: string description: | @@ -5846,12 +5850,13 @@ components: description: Whether to return details to each item. schema: type: boolean - deleted: - name: deleted + searchMode: + name: searchMode in: query description: Whether to search in soft-deleted items only. schema: - type: boolean + type: string + format: searchmode name: name: name in: path diff --git a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala index da9afe80..23619cd3 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/http4s/QueryParam.scala @@ -7,6 +7,7 @@ package docspell.restserver.http4s import docspell.common.ContactKind +import docspell.common.SearchMode import org.http4s.ParseFailure import org.http4s.QueryParamDecoder @@ -23,6 +24,11 @@ object QueryParam { implicit val queryStringDecoder: QueryParamDecoder[QueryString] = QueryParamDecoder[String].map(s => QueryString(s.trim.toLowerCase)) + implicit val searchModeDecoder: QueryParamDecoder[SearchMode] = + QueryParamDecoder[String].emap(str => + SearchMode.fromString(str).left.map(s => ParseFailure(str, s)) + ) + object FullOpt extends OptionalQueryParamDecoderMatcher[Boolean]("full") object OwningOpt extends OptionalQueryParamDecoderMatcher[Boolean]("owning") @@ -35,7 +41,7 @@ object QueryParam { object Limit extends OptionalQueryParamDecoderMatcher[Int]("limit") object Offset extends OptionalQueryParamDecoderMatcher[Int]("offset") object WithDetails extends OptionalQueryParamDecoderMatcher[Boolean]("withDetails") - object Deleted extends OptionalQueryParamDecoderMatcher[Boolean]("deleted") + object SearchKind extends OptionalQueryParamDecoderMatcher[SearchMode]("searchMode") object WithFallback extends OptionalQueryParamDecoderMatcher[Boolean]("withFallback") } diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala index 02030d7d..71bdbfb7 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ItemRoutes.scala @@ -49,7 +49,7 @@ object ItemRoutes { HttpRoutes.of { case GET -> Root / "search" :? QP.Query(q) :? QP.Limit(limit) :? QP.Offset( offset - ) :? QP.WithDetails(detailFlag) :? QP.Deleted(deletedFlag) => + ) :? QP.WithDetails(detailFlag) :? QP.SearchKind(searchMode) => val batch = Batch(offset.getOrElse(0), limit.getOrElse(cfg.maxItemPageSize)) .restrictLimitTo(cfg.maxItemPageSize) val itemQuery = ItemQueryString(q) @@ -58,17 +58,17 @@ object ItemRoutes { cfg.fullTextSearch.enabled, detailFlag.getOrElse(false), cfg.maxNoteLength, - deletedFlag.getOrElse(false) + searchMode.getOrElse(SearchMode.Normal) ) val fixQuery = Query.Fix(user.account, None, None) searchItems(backend, dsl)(settings, fixQuery, itemQuery) - case GET -> Root / "searchStats" :? QP.Query(q) :? QP.Deleted(deletedFlag) => + case GET -> Root / "searchStats" :? QP.Query(q) :? QP.SearchKind(searchMode) => val itemQuery = ItemQueryString(q) val fixQuery = Query.Fix(user.account, None, None) val settings = OSimpleSearch.StatsSettings( useFTS = cfg.fullTextSearch.enabled, - deleted = deletedFlag.getOrElse(false) + searchMode = searchMode.getOrElse(SearchMode.Normal) ) searchItemStats(backend, dsl)(settings, fixQuery, itemQuery) @@ -87,7 +87,7 @@ object ItemRoutes { cfg.fullTextSearch.enabled, userQuery.withDetails.getOrElse(false), cfg.maxNoteLength, - deleted = userQuery.deleted.getOrElse(false) + searchMode = userQuery.searchMode.getOrElse(SearchMode.Normal) ) fixQuery = Query.Fix(user.account, None, None) resp <- searchItems(backend, dsl)(settings, fixQuery, itemQuery) @@ -100,7 +100,7 @@ object ItemRoutes { fixQuery = Query.Fix(user.account, None, None) settings = OSimpleSearch.StatsSettings( useFTS = cfg.fullTextSearch.enabled, - deleted = userQuery.deleted.getOrElse(false) + searchMode = userQuery.searchMode.getOrElse(SearchMode.Normal) ) resp <- searchItemStats(backend, dsl)(settings, fixQuery, itemQuery) } yield resp diff --git a/modules/webapp/src/main/elm/Data/ItemQuery.elm b/modules/webapp/src/main/elm/Data/ItemQuery.elm index 02ceb825..46d1e4be 100644 --- a/modules/webapp/src/main/elm/Data/ItemQuery.elm +++ b/modules/webapp/src/main/elm/Data/ItemQuery.elm @@ -79,7 +79,7 @@ request mq = , limit = Nothing , withDetails = Just True , query = renderMaybe mq - , deleted = Just False + , searchMode = Nothing }