mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Add and change custom fields
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
CREATE TABLE "custom_field" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"name" varchar(254) not null,
|
||||
"label" varchar(254),
|
||||
"cid" varchar(254) not null,
|
||||
"ftype" varchar(100) not null,
|
||||
"created" timestamp not null,
|
||||
|
@ -1,5 +1,7 @@
|
||||
package docspell.store.impl
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import docspell.common.Timestamp
|
||||
|
||||
import doobie._
|
||||
@ -7,6 +9,12 @@ import doobie.implicits._
|
||||
|
||||
trait DoobieSyntax {
|
||||
|
||||
def groupBy(c0: Column, cs: Column*): Fragment =
|
||||
groupBy(NonEmptyList.of(c0, cs: _*))
|
||||
|
||||
def groupBy(cs: NonEmptyList[Column]): Fragment =
|
||||
fr" GROUP BY (" ++ commas(cs.toList.map(_.f)) ++ fr")"
|
||||
|
||||
def coalesce(f0: Fragment, fs: Fragment*): Fragment =
|
||||
sql" coalesce(" ++ commas(f0 :: fs.toList) ++ sql") "
|
||||
|
||||
|
@ -0,0 +1,64 @@
|
||||
package docspell.store.queries
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
import docspell.store.records._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
object QCustomField {
|
||||
|
||||
case class CustomFieldData(field: RCustomField, usageCount: Int)
|
||||
|
||||
def findAllLike(
|
||||
coll: Ident,
|
||||
nameQuery: Option[String]
|
||||
): ConnectionIO[Vector[CustomFieldData]] =
|
||||
findFragment(coll, nameQuery, None).query[CustomFieldData].to[Vector]
|
||||
|
||||
def findById(field: Ident, collective: Ident): ConnectionIO[Option[CustomFieldData]] =
|
||||
findFragment(collective, None, field.some).query[CustomFieldData].option
|
||||
|
||||
private def findFragment(
|
||||
coll: Ident,
|
||||
nameQuery: Option[String],
|
||||
fieldId: Option[Ident]
|
||||
): Fragment = {
|
||||
val fId = RCustomField.Columns.id.prefix("f")
|
||||
val fColl = RCustomField.Columns.cid.prefix("f")
|
||||
val fName = RCustomField.Columns.name.prefix("f")
|
||||
val fLabel = RCustomField.Columns.label.prefix("f")
|
||||
val vField = RCustomFieldValue.Columns.field.prefix("v")
|
||||
|
||||
val join = RCustomField.table ++ fr"f LEFT OUTER JOIN" ++
|
||||
RCustomFieldValue.table ++ fr"v ON" ++ fId.is(vField)
|
||||
|
||||
val cols = RCustomField.Columns.all.map(_.prefix("f")) :+ Column("COUNT(v.id)")
|
||||
|
||||
val nameCond = nameQuery.map(QueryWildcard.apply) match {
|
||||
case Some(q) =>
|
||||
or(fName.lowerLike(q), fLabel.lowerLike(q))
|
||||
case None =>
|
||||
Fragment.empty
|
||||
}
|
||||
val fieldCond = fieldId match {
|
||||
case Some(id) =>
|
||||
fId.is(id)
|
||||
case None =>
|
||||
Fragment.empty
|
||||
}
|
||||
val cond = and(fColl.is(coll), nameCond, fieldCond)
|
||||
|
||||
val group = NonEmptyList.fromList(RCustomField.Columns.all) match {
|
||||
case Some(nel) => groupBy(nel.map(_.prefix("f")))
|
||||
case None => Fragment.empty
|
||||
}
|
||||
|
||||
selectSimple(cols, join, cond) ++ group
|
||||
}
|
||||
}
|
@ -342,8 +342,8 @@ object QItem {
|
||||
TagItemName.itemsWithTagOrCategory(q.tagsExclude, q.tagCategoryExcl)
|
||||
|
||||
val iFolder = IC.folder.prefix("i")
|
||||
val name = q.name.map(_.toLowerCase).map(queryWildcard)
|
||||
val allNames = q.allNames.map(_.toLowerCase).map(queryWildcard)
|
||||
val name = q.name.map(_.toLowerCase).map(QueryWildcard.apply)
|
||||
val allNames = q.allNames.map(_.toLowerCase).map(QueryWildcard.apply)
|
||||
val cond = and(
|
||||
IC.cid.prefix("i").is(q.account.collective),
|
||||
IC.state.prefix("i").isOneOf(q.states),
|
||||
@ -516,8 +516,9 @@ object QItem {
|
||||
rn <- QAttachment.deleteItemAttachments(store)(itemId, collective)
|
||||
tn <- store.transact(RTagItem.deleteItemTags(itemId))
|
||||
mn <- store.transact(RSentMail.deleteByItem(itemId))
|
||||
cf <- store.transact(RCustomFieldValue.deleteByItem(itemId))
|
||||
n <- store.transact(RItem.deleteByIdAndCollective(itemId, collective))
|
||||
} yield tn + rn + n + mn
|
||||
} yield tn + rn + n + mn + cf
|
||||
|
||||
private def findByFileIdsQuery(
|
||||
fileMetaIds: NonEmptyList[Ident],
|
||||
@ -618,18 +619,6 @@ object QItem {
|
||||
.to[Vector]
|
||||
}
|
||||
|
||||
private def queryWildcard(value: String): String = {
|
||||
def prefix(n: String) =
|
||||
if (n.startsWith("*")) s"%${n.substring(1)}"
|
||||
else n
|
||||
|
||||
def suffix(n: String) =
|
||||
if (n.endsWith("*")) s"${n.dropRight(1)}%"
|
||||
else n
|
||||
|
||||
prefix(suffix(value))
|
||||
}
|
||||
|
||||
final case class NameAndNotes(
|
||||
id: Ident,
|
||||
collective: Ident,
|
||||
|
@ -0,0 +1,17 @@
|
||||
package docspell.store.queries
|
||||
|
||||
object QueryWildcard {
|
||||
|
||||
def apply(value: String): String = {
|
||||
def prefix(n: String) =
|
||||
if (n.startsWith("*")) s"%${n.substring(1)}"
|
||||
else n
|
||||
|
||||
def suffix(n: String) =
|
||||
if (n.endsWith("*")) s"${n.dropRight(1)}%"
|
||||
else n
|
||||
|
||||
prefix(suffix(value))
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,8 @@ import doobie.implicits._
|
||||
|
||||
case class RCustomField(
|
||||
id: Ident,
|
||||
name: String,
|
||||
name: Ident,
|
||||
label: Option[String],
|
||||
cid: Ident,
|
||||
ftype: CustomFieldType,
|
||||
created: Timestamp
|
||||
@ -23,22 +24,49 @@ object RCustomField {
|
||||
|
||||
val id = Column("id")
|
||||
val name = Column("name")
|
||||
val label = Column("label")
|
||||
val cid = Column("cid")
|
||||
val ftype = Column("ftype")
|
||||
val created = Column("created")
|
||||
|
||||
val all = List(id, name, cid, ftype, created)
|
||||
val all = List(id, name, label, cid, ftype, created)
|
||||
}
|
||||
import Columns._
|
||||
|
||||
def insert(value: RCustomField): ConnectionIO[Int] = {
|
||||
val sql = insertRow(
|
||||
table,
|
||||
Columns.all,
|
||||
fr"${value.id},${value.name},${value.cid},${value.ftype},${value.created}"
|
||||
fr"${value.id},${value.name},${value.label},${value.cid},${value.ftype},${value.created}"
|
||||
)
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def exists(fname: Ident, coll: Ident): ConnectionIO[Boolean] =
|
||||
???
|
||||
|
||||
def findById(fid: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] =
|
||||
selectSimple(all, table, and(id.is(fid), cid.is(coll))).query[RCustomField].option
|
||||
|
||||
def findByIdOrName(idOrName: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] =
|
||||
selectSimple(all, table, and(cid.is(coll), or(id.is(idOrName), name.is(idOrName))))
|
||||
.query[RCustomField]
|
||||
.option
|
||||
|
||||
def deleteById(fid: Ident, coll: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, and(id.is(fid), cid.is(coll))).update.run
|
||||
|
||||
def findAll(coll: Ident): ConnectionIO[Vector[RCustomField]] =
|
||||
selectSimple(Columns.all, table, Columns.cid.is(coll)).query[RCustomField].to[Vector]
|
||||
selectSimple(all, table, cid.is(coll)).query[RCustomField].to[Vector]
|
||||
|
||||
def update(value: RCustomField): ConnectionIO[Int] =
|
||||
updateRow(
|
||||
table,
|
||||
and(id.is(value.id), cid.is(value.cid)),
|
||||
commas(
|
||||
name.setTo(value.name),
|
||||
label.setTo(value.label),
|
||||
ftype.setTo(value.ftype)
|
||||
)
|
||||
).update.run
|
||||
}
|
||||
|
@ -39,4 +39,12 @@ object RCustomFieldValue {
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def countField(fieldId: Ident): ConnectionIO[Int] =
|
||||
selectCount(Columns.id, table, Columns.field.is(fieldId)).query[Int].unique
|
||||
|
||||
def deleteByField(fieldId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, Columns.field.is(fieldId)).update.run
|
||||
|
||||
def deleteByItem(item: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, Columns.itemId.is(item)).update.run
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package docspell.store.queries
|
||||
|
||||
import minitest._
|
||||
|
||||
object QueryWildcardTest extends SimpleTestSuite {
|
||||
|
||||
test("replace prefix") {
|
||||
assertEquals("%name", QueryWildcard("*name"))
|
||||
assertEquals("%some more", QueryWildcard("*some more"))
|
||||
}
|
||||
|
||||
test("replace suffix") {
|
||||
assertEquals("name%", QueryWildcard("name*"))
|
||||
assertEquals("some other name%", QueryWildcard("some other name*"))
|
||||
}
|
||||
|
||||
test("replace both sides") {
|
||||
assertEquals("%name%", QueryWildcard("*name*"))
|
||||
assertEquals("%some other name%", QueryWildcard("*some other name*"))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user