Allow simple search when listing meta data

This commit is contained in:
Eike Kettner 2020-01-02 19:59:46 +01:00
parent eb6c483ef0
commit 8814de3c38
14 changed files with 163 additions and 46 deletions

View File

@ -8,7 +8,7 @@ import docspell.store.records.{REquipment, RItem}
trait OEquipment[F[_]] {
def findAll(account: AccountId): F[Vector[REquipment]]
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[REquipment]]
def add(s: REquipment): F[AddResult]
@ -21,8 +21,8 @@ object OEquipment {
def apply[F[_]: Effect](store: Store[F]): Resource[F, OEquipment[F]] =
Resource.pure(new OEquipment[F] {
def findAll(account: AccountId): F[Vector[REquipment]] =
store.transact(REquipment.findAll(account.collective, _.name))
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[REquipment]] =
store.transact(REquipment.findAll(account.collective, nameQuery, _.name))
def add(e: REquipment): F[AddResult] = {
def insert = REquipment.insert(e)

View File

@ -9,17 +9,17 @@ import OOrganization._
import docspell.store.queries.QOrganization
trait OOrganization[F[_]] {
def findAllOrg(account: AccountId): F[Vector[OrgAndContacts]]
def findAllOrg(account: AccountId, query: Option[String]): F[Vector[OrgAndContacts]]
def findAllOrgRefs(account: AccountId): F[Vector[IdRef]]
def findAllOrgRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]]
def addOrg(s: OrgAndContacts): F[AddResult]
def updateOrg(s: OrgAndContacts): F[AddResult]
def findAllPerson(account: AccountId): F[Vector[PersonAndContacts]]
def findAllPerson(account: AccountId, query: Option[String]): F[Vector[PersonAndContacts]]
def findAllPersonRefs(account: AccountId): F[Vector[IdRef]]
def findAllPersonRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]]
def addPerson(s: PersonAndContacts): F[AddResult]
@ -39,15 +39,15 @@ object OOrganization {
def apply[F[_]: Effect](store: Store[F]): Resource[F, OOrganization[F]] =
Resource.pure(new OOrganization[F] {
def findAllOrg(account: AccountId): F[Vector[OrgAndContacts]] =
def findAllOrg(account: AccountId, query: Option[String]): F[Vector[OrgAndContacts]] =
store
.transact(QOrganization.findOrgAndContact(account.collective, _.name))
.transact(QOrganization.findOrgAndContact(account.collective, query, _.name))
.map({ case (org, cont) => OrgAndContacts(org, cont) })
.compile
.toVector
def findAllOrgRefs(account: AccountId): F[Vector[IdRef]] =
store.transact(ROrganization.findAllRef(account.collective, _.name))
def findAllOrgRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]] =
store.transact(ROrganization.findAllRef(account.collective, nameQuery, _.name))
def addOrg(s: OrgAndContacts): F[AddResult] =
QOrganization.addOrg(s.org, s.contacts, s.org.cid)(store)
@ -55,15 +55,15 @@ object OOrganization {
def updateOrg(s: OrgAndContacts): F[AddResult] =
QOrganization.updateOrg(s.org, s.contacts, s.org.cid)(store)
def findAllPerson(account: AccountId): F[Vector[PersonAndContacts]] =
def findAllPerson(account: AccountId, query: Option[String]): F[Vector[PersonAndContacts]] =
store
.transact(QOrganization.findPersonAndContact(account.collective, _.name))
.transact(QOrganization.findPersonAndContact(account.collective, query, _.name))
.map({ case (person, cont) => PersonAndContacts(person, cont) })
.compile
.toVector
def findAllPersonRefs(account: AccountId): F[Vector[IdRef]] =
store.transact(RPerson.findAllRef(account.collective, _.name))
def findAllPersonRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]] =
store.transact(RPerson.findAllRef(account.collective, nameQuery, _.name))
def addPerson(s: PersonAndContacts): F[AddResult] =
QOrganization.addPerson(s.person, s.contacts, s.person.cid)(store)

View File

@ -8,7 +8,7 @@ import docspell.store.records.{RTag, RTagItem}
trait OTag[F[_]] {
def findAll(account: AccountId): F[Vector[RTag]]
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]]
def add(s: RTag): F[AddResult]
@ -21,8 +21,8 @@ object OTag {
def apply[F[_]: Effect](store: Store[F]): Resource[F, OTag[F]] =
Resource.pure(new OTag[F] {
def findAll(account: AccountId): F[Vector[RTag]] =
store.transact(RTag.findAll(account.collective, _.name))
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]] =
store.transact(RTag.findAll(account.collective, nameQuery, _.name))
def add(t: RTag): F[AddResult] = {
def insert = RTag.insert(t)

View File

@ -3,14 +3,17 @@ package docspell.common
import java.security.SecureRandom
import java.util.UUID
import cats.Eq
import cats.implicits._
import cats.effect.Sync
import io.circe.{Decoder, Encoder}
import scodec.bits.ByteVector
case class Ident(id: String) {
}
case class Ident(id: String) {}
object Ident {
implicit val identEq: Eq[Ident] =
Eq.by(_.id)
val chars: Set[Char] = (('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ "-_").toSet
@ -46,8 +49,6 @@ object Ident {
def unapply(arg: String): Option[Ident] =
fromString(arg).toOption
implicit val encodeIdent: Encoder[Ident] =
Encoder.encodeString.contramap(_.id)

View File

@ -255,6 +255,8 @@ paths:
Return a list of all configured tags.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/q"
responses:
200:
description: Ok
@ -329,6 +331,7 @@ paths:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/full"
- $ref: "#/components/parameters/q"
responses:
200:
description: Ok
@ -421,6 +424,7 @@ paths:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/full"
- $ref: "#/components/parameters/q"
responses:
200:
description: Ok
@ -511,6 +515,8 @@ paths:
Return a list of all configured equipments.
security:
- authTokenHeader: []
parameters:
- $ref: "#/components/parameters/q"
responses:
200:
description: Ok
@ -2169,3 +2175,10 @@ components:
required: true
schema:
type: string
q:
name: q
in: query
description: A query string.
required: false
schema:
type: string

View File

@ -19,9 +19,10 @@ object EquipmentRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root =>
case req @ GET -> Root =>
val q = req.params.get("q").map(_.trim).filter(_.nonEmpty)
for {
data <- backend.equipment.findAll(user.account)
data <- backend.equipment.findAll(user.account, q)
resp <- Ok(EquipmentList(data.map(mkEquipment).toList))
} yield resp

View File

@ -20,15 +20,16 @@ object OrganizationRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? FullQueryParamMatcher(full) =>
case req @ GET -> Root :? FullQueryParamMatcher(full) =>
val q = req.params.get("q").map(_.trim).filter(_.nonEmpty)
if (full.getOrElse(false)) {
for {
data <- backend.organization.findAllOrg(user.account)
data <- backend.organization.findAllOrg(user.account, q)
resp <- Ok(OrganizationList(data.map(mkOrg).toList))
} yield resp
} else {
for {
data <- backend.organization.findAllOrgRefs(user.account)
data <- backend.organization.findAllOrgRefs(user.account, q)
resp <- Ok(ReferenceList(data.map(mkIdName).toList))
} yield resp
}

View File

@ -23,15 +23,16 @@ object PersonRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root :? FullQueryParamMatcher(full) =>
case req @ GET -> Root :? FullQueryParamMatcher(full) =>
val q = req.params.get("q").map(_.trim).filter(_.nonEmpty)
if (full.getOrElse(false)) {
for {
data <- backend.organization.findAllPerson(user.account)
data <- backend.organization.findAllPerson(user.account, q)
resp <- Ok(PersonList(data.map(mkPerson).toList))
} yield resp
} else {
for {
data <- backend.organization.findAllPersonRefs(user.account)
data <- backend.organization.findAllPersonRefs(user.account, q)
resp <- Ok(ReferenceList(data.map(mkIdName).toList))
} yield resp
}

View File

@ -20,9 +20,10 @@ object TagRoutes {
import dsl._
HttpRoutes.of {
case GET -> Root =>
case req @ GET -> Root =>
val q = req.params.get("q").map(_.trim).filter(_.nonEmpty)
for {
all <- backend.tag.findAll(user.account)
all <- backend.tag.findAll(user.account, q)
resp <- Ok(TagList(all.size, all.map(mkTag).toList))
} yield resp

View File

@ -3,9 +3,11 @@ package docspell.store.queries
import fs2._
import cats.implicits._
import doobie._
import doobie.implicits._
import docspell.common._
import docspell.store.{AddResult, Store}
import docspell.store.impl.Column
import docspell.store.impl.Implicits._
import docspell.store.records.ROrganization.{Columns => OC}
import docspell.store.records.RPerson.{Columns => PC}
import docspell.store.records._
@ -14,16 +16,75 @@ object QOrganization {
def findOrgAndContact(
coll: Ident,
query: Option[String],
order: OC.type => Column
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] =
ROrganization
.findAll(coll, order)
.evalMap(ro => RContact.findAllOrg(ro.oid).map(cs => (ro, cs)))
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = {
val oColl = ROrganization.Columns.cid.prefix("o")
val oName = ROrganization.Columns.name.prefix("o")
val oNotes = ROrganization.Columns.notes.prefix("o")
val oId = ROrganization.Columns.oid.prefix("o")
val cOrg = RContact.Columns.orgId.prefix("c")
val cVal = RContact.Columns.value.prefix("c")
val cols = ROrganization.Columns.all.map(_.prefix("o")) ++ RContact.Columns.all
.map(_.prefix("c"))
val from = ROrganization.table ++ fr"o LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cOrg.is(oId)
val q = Seq(oColl.is(coll)) ++ (query match {
case Some(str) =>
val v = s"%$str%"
Seq(or(cVal.lowerLike(v), oName.lowerLike(v), oNotes.lowerLike(v)))
case None =>
Seq.empty
})
(selectSimple(cols, from, and(q)) ++ orderBy(order(OC).f))
.query[(ROrganization, Option[RContact])]
.stream
.groupAdjacentBy(_._1)
.map({
case (ro, chunk) =>
val cs = chunk.toVector.flatMap(_._2)
(ro, cs)
})
}
def findPersonAndContact(
coll: Ident,
query: Option[String],
order: PC.type => Column
): Stream[ConnectionIO, (RPerson, Vector[RContact])] =
RPerson.findAll(coll, order).evalMap(ro => RContact.findAllPerson(ro.pid).map(cs => (ro, cs)))
): Stream[ConnectionIO, (RPerson, Vector[RContact])] = {
val pColl = PC.cid.prefix("p")
val pName = RPerson.Columns.name.prefix("p")
val pNotes = RPerson.Columns.notes.prefix("p")
val pId = RPerson.Columns.pid.prefix("p")
val cPers = RContact.Columns.personId.prefix("c")
val cVal = RContact.Columns.value.prefix("c")
val cols = RPerson.Columns.all.map(_.prefix("p")) ++ RContact.Columns.all
.map(_.prefix("c"))
val from = RPerson.table ++ fr"p LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cPers.is(pId)
val q = Seq(pColl.is(coll)) ++ (query match {
case Some(str) =>
val v = s"%${str.toLowerCase}%"
Seq(or(cVal.lowerLike(v), pName.lowerLike(v), pNotes.lowerLike(v)))
case None =>
Seq.empty
})
(selectSimple(cols, from, and(q)) ++ orderBy(order(PC).f))
.query[(RPerson, Option[RContact])]
.stream
.groupAdjacentBy(_._1)
.map({
case (ro, chunk) =>
val cs = chunk.toVector.flatMap(_._2)
(ro, cs)
})
}
def addOrg[F[_]](
org: ROrganization,

View File

@ -47,8 +47,16 @@ object REquipment {
sql.query[REquipment].option
}
def findAll(coll: Ident, order: Columns.type => Column): ConnectionIO[Vector[REquipment]] = {
val sql = selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f)
def findAll(
coll: Ident,
nameQ: Option[String],
order: Columns.type => Column
): ConnectionIO[Vector[REquipment]] = {
val q = Seq(cid.is(coll)) ++ (nameQ match {
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
case None => Seq.empty
})
val sql = selectSimple(all, table, and(q)) ++ orderBy(order(Columns).f)
sql.query[REquipment].to[Vector]
}

View File

@ -1,5 +1,6 @@
package docspell.store.records
import cats.Eq
import fs2.Stream
import doobie._
import doobie.implicits._
@ -20,6 +21,8 @@ case class ROrganization(
) {}
object ROrganization {
implicit val orgEq: Eq[ROrganization] =
Eq.by[ROrganization, Ident](_.oid)
val table = fr"organization"
@ -105,8 +108,16 @@ object ROrganization {
sql.query[ROrganization].stream
}
def findAllRef(coll: Ident, order: Columns.type => Column): ConnectionIO[Vector[IdRef]] = {
val sql = selectSimple(List(oid, name), table, cid.is(coll)) ++ orderBy(order(Columns).f)
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Columns.type => Column
): ConnectionIO[Vector[IdRef]] = {
val q = Seq(cid.is(coll)) ++ (nameQ match {
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
case None => Seq.empty
})
val sql = selectSimple(List(oid, name), table, and(q)) ++ orderBy(order(Columns).f)
sql.query[IdRef].to[Vector]
}

View File

@ -1,6 +1,7 @@
package docspell.store.records
import fs2.Stream
import cats.Eq
import doobie._
import doobie.implicits._
import docspell.common.{IdRef, _}
@ -21,6 +22,8 @@ case class RPerson(
) {}
object RPerson {
implicit val personEq: Eq[RPerson] =
Eq.by(_.pid)
val table = fr"person"
@ -116,8 +119,16 @@ object RPerson {
sql.query[RPerson].stream
}
def findAllRef(coll: Ident, order: Columns.type => Column): ConnectionIO[Vector[IdRef]] = {
val sql = selectSimple(List(pid, name), table, cid.is(coll)) ++ orderBy(order(Columns).f)
def findAllRef(
coll: Ident,
nameQ: Option[String],
order: Columns.type => Column
): ConnectionIO[Vector[IdRef]] = {
val q = Seq(cid.is(coll)) ++ (nameQ match {
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
case None => Seq.empty
})
val sql = selectSimple(List(pid, name), table, and(q)) ++ orderBy(order(Columns).f)
sql.query[IdRef].to[Vector]
}

View File

@ -65,8 +65,16 @@ object RTag {
sql.query[Int].unique.map(_ > 0)
}
def findAll(coll: Ident, order: Columns.type => Column): ConnectionIO[Vector[RTag]] = {
val sql = selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f)
def findAll(
coll: Ident,
nameQ: Option[String],
order: Columns.type => Column
): ConnectionIO[Vector[RTag]] = {
val q = Seq(cid.is(coll)) ++ (nameQ match {
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
case None => Seq.empty
})
val sql = selectSimple(all, table, and(q)) ++ orderBy(order(Columns).f)
sql.query[RTag].to[Vector]
}