mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Apply folder restriction to fulltext only search
And update index when folder changes.
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
package docspell.ftssolr
|
||||
|
||||
import docspell.common._
|
||||
|
||||
final case class DocIdResult(ids: List[Ident]) {
|
||||
|
||||
def toSetFolder(folder: Option[Ident]): List[SetFolder] =
|
||||
ids.map(id => SetFolder(id, folder))
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package docspell.ftssolr
|
||||
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.ftsclient._
|
||||
|
||||
@ -21,6 +23,7 @@ trait JsonCodec {
|
||||
(Field.id.name, enc(td.id)),
|
||||
(Field.itemId.name, enc(td.item)),
|
||||
(Field.collectiveId.name, enc(td.collective)),
|
||||
(Field.folderId.name, td.folder.getOrElse(Ident.unsafe("")).asJson),
|
||||
(Field.attachmentId.name, enc(td.attachId)),
|
||||
(Field.attachmentName.name, Json.fromString(td.name.getOrElse(""))),
|
||||
(Field.discriminator.name, Json.fromString("attachment"))
|
||||
@ -37,6 +40,7 @@ trait JsonCodec {
|
||||
(Field.id.name, enc(td.id)),
|
||||
(Field.itemId.name, enc(td.item)),
|
||||
(Field.collectiveId.name, enc(td.collective)),
|
||||
(Field.folderId.name, td.folder.getOrElse(Ident.unsafe("")).asJson),
|
||||
(Field.itemName.name, Json.fromString(td.name.getOrElse(""))),
|
||||
(Field.itemNotes.name, Json.fromString(td.notes.getOrElse(""))),
|
||||
(Field.discriminator.name, Json.fromString("item"))
|
||||
@ -49,6 +53,18 @@ trait JsonCodec {
|
||||
): Encoder[TextData] =
|
||||
Encoder(_.fold(ae.apply, ie.apply))
|
||||
|
||||
implicit def docIdResultsDecoder: Decoder[DocIdResult] =
|
||||
new Decoder[DocIdResult] {
|
||||
final def apply(c: HCursor): Decoder.Result[DocIdResult] =
|
||||
c.downField("response")
|
||||
.downField("docs")
|
||||
.values
|
||||
.getOrElse(Nil)
|
||||
.toList
|
||||
.traverse(_.hcursor.get[Ident](Field.id.name))
|
||||
.map(DocIdResult.apply)
|
||||
}
|
||||
|
||||
implicit def ftsResultDecoder: Decoder[FtsResult] =
|
||||
new Decoder[FtsResult] {
|
||||
final def apply(c: HCursor): Decoder.Result[FtsResult] =
|
||||
@ -89,6 +105,12 @@ trait JsonCodec {
|
||||
} yield md
|
||||
}
|
||||
|
||||
implicit def decodeEverythingToUnit: Decoder[Unit] =
|
||||
new Decoder[Unit] {
|
||||
final def apply(c: HCursor): Decoder.Result[Unit] =
|
||||
Right(())
|
||||
}
|
||||
|
||||
implicit def identKeyEncoder: KeyEncoder[Ident] =
|
||||
new KeyEncoder[Ident] {
|
||||
override def apply(ident: Ident): String = ident.id
|
||||
@ -129,9 +151,24 @@ trait JsonCodec {
|
||||
}
|
||||
}
|
||||
|
||||
implicit def textDataEncoder: Encoder[SetFields] =
|
||||
implicit def setTextDataFieldsEncoder: Encoder[SetFields] =
|
||||
Encoder(_.td.fold(setAttachmentEncoder.apply, setItemEncoder.apply))
|
||||
|
||||
implicit def setFolderEncoder(implicit
|
||||
enc: Encoder[Option[Ident]]
|
||||
): Encoder[SetFolder] =
|
||||
new Encoder[SetFolder] {
|
||||
final def apply(td: SetFolder): Json =
|
||||
Json.fromFields(
|
||||
List(
|
||||
(Field.id.name, td.docId.asJson),
|
||||
(
|
||||
Field.folderId.name,
|
||||
Map("set" -> td.folder.asJson).asJson
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object JsonCodec extends JsonCodec
|
||||
|
@ -40,16 +40,26 @@ object QueryData {
|
||||
fields: List[Field],
|
||||
fq: FtsQuery
|
||||
): QueryData = {
|
||||
val q = sanitize(fq.q)
|
||||
val extQ = search.map(f => s"${f.name}:($q)").mkString(" OR ")
|
||||
val items = fq.items.map(_.id).mkString(" ")
|
||||
val collQ = s"""${Field.collectiveId.name}:"${fq.collective.id}""""
|
||||
val filterQ = fq.items match {
|
||||
case s if s.isEmpty =>
|
||||
collQ
|
||||
case _ =>
|
||||
(collQ :: List(s"""${Field.itemId.name}:($items)""")).mkString(" AND ")
|
||||
}
|
||||
val q = sanitize(fq.q)
|
||||
val extQ = search.map(f => s"${f.name}:($q)").mkString(" OR ")
|
||||
val items = fq.items.map(_.id).mkString(" ")
|
||||
val folders = fq.folders.map(_.id).mkString(" ")
|
||||
val filterQ = List(
|
||||
s"""${Field.collectiveId.name}:"${fq.collective.id}"""",
|
||||
fq.items match {
|
||||
case s if s.isEmpty =>
|
||||
""
|
||||
case _ =>
|
||||
s"""${Field.itemId.name}:($items)"""
|
||||
},
|
||||
fq.folders match {
|
||||
case s if s.isEmpty =>
|
||||
""
|
||||
case _ =>
|
||||
s"""${Field.folderId.name}:($folders) OR (*:* NOT ${Field.folderId.name}:*)"""
|
||||
}
|
||||
).filterNot(_.isEmpty).map(t => s"($t)").mkString(" AND ")
|
||||
|
||||
QueryData(
|
||||
extQ,
|
||||
filterQ,
|
||||
|
@ -0,0 +1,5 @@
|
||||
package docspell.ftssolr
|
||||
|
||||
import docspell.common._
|
||||
|
||||
final case class SetFolder(docId: Ident, folder: Option[Ident])
|
@ -29,6 +29,17 @@ final class SolrFtsClient[F[_]: Effect](
|
||||
def updateIndex(logger: Logger[F], data: Stream[F, TextData]): F[Unit] =
|
||||
modifyIndex(logger, data)(solrUpdate.update)
|
||||
|
||||
def updateFolder(
|
||||
logger: Logger[F],
|
||||
itemId: Ident,
|
||||
collective: Ident,
|
||||
folder: Option[Ident]
|
||||
): F[Unit] =
|
||||
logger.debug(
|
||||
s"Update folder in solr index for coll/item ${collective.id}/${itemId.id}"
|
||||
) *>
|
||||
solrUpdate.updateFolder(itemId, collective, folder)
|
||||
|
||||
def modifyIndex(logger: Logger[F], data: Stream[F, TextData])(
|
||||
f: List[TextData] => F[Unit]
|
||||
): F[Unit] =
|
||||
|
@ -1,7 +1,9 @@
|
||||
package docspell.ftssolr
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.ftsclient._
|
||||
import docspell.ftssolr.JsonCodec._
|
||||
|
||||
@ -11,6 +13,7 @@ import org.http4s._
|
||||
import org.http4s.circe._
|
||||
import org.http4s.client.Client
|
||||
import org.http4s.client.dsl.Http4sClientDsl
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
|
||||
trait SolrUpdate[F[_]] {
|
||||
|
||||
@ -18,6 +21,8 @@ trait SolrUpdate[F[_]] {
|
||||
|
||||
def update(tds: List[TextData]): F[Unit]
|
||||
|
||||
def updateFolder(itemId: Ident, collective: Ident, folder: Option[Ident]): F[Unit]
|
||||
|
||||
def delete(q: String, commitWithin: Option[Int]): F[Unit]
|
||||
}
|
||||
|
||||
@ -43,6 +48,29 @@ object SolrUpdate {
|
||||
client.expect[Unit](req)
|
||||
}
|
||||
|
||||
def updateFolder(
|
||||
itemId: Ident,
|
||||
collective: Ident,
|
||||
folder: Option[Ident]
|
||||
): F[Unit] = {
|
||||
val queryUrl = Uri.unsafeFromString(cfg.url.asString) / "query"
|
||||
val q = QueryData(
|
||||
"*:*",
|
||||
s"${Field.itemId.name}:${itemId.id} AND ${Field.collectiveId.name}:${collective.id}",
|
||||
Int.MaxValue,
|
||||
0,
|
||||
List(Field.id),
|
||||
Map.empty
|
||||
)
|
||||
val searchReq = Method.POST(q.asJson, queryUrl)
|
||||
for {
|
||||
docIds <- client.expect[DocIdResult](searchReq)
|
||||
sets = docIds.toSetFolder(folder)
|
||||
req = Method.POST(sets.asJson, url)
|
||||
_ <- client.expect[Unit](req)
|
||||
} yield ()
|
||||
}
|
||||
|
||||
def delete(q: String, commitWithin: Option[Int]): F[Unit] = {
|
||||
val uri = commitWithin match {
|
||||
case Some(n) =>
|
||||
|
Reference in New Issue
Block a user