mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-04 10:29:34 +00:00
Prevent duplicate bookmark names
This commit is contained in:
parent
54a4e6efee
commit
ccb4df5bd7
@ -57,14 +57,11 @@ object OQueryBookmarks {
|
||||
_.map(r => Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created))
|
||||
)
|
||||
|
||||
def create(account: AccountId, b: NewBookmark): F[AddResult] =
|
||||
store
|
||||
.transact(for {
|
||||
r <- RQueryBookmark.createNew(account, b.name, b.label, b.query, b.personal)
|
||||
n <- RQueryBookmark.insert(r)
|
||||
} yield n)
|
||||
.attempt
|
||||
.map(AddResult.fromUpdate)
|
||||
def create(account: AccountId, b: NewBookmark): F[AddResult] = {
|
||||
val record =
|
||||
RQueryBookmark.createNew(account, b.name, b.label, b.query, b.personal)
|
||||
store.transact(RQueryBookmark.insertIfNotExists(account, record))
|
||||
}
|
||||
|
||||
def update(account: AccountId, id: Ident, b: NewBookmark): F[UpdateResult] =
|
||||
UpdateResult.fromUpdate(
|
||||
|
@ -6,7 +6,8 @@ CREATE TABLE "query_bookmark" (
|
||||
"cid" varchar(254) not null,
|
||||
"query" varchar(2000) not null,
|
||||
"created" timestamp,
|
||||
"__user_id" varchar(254) not null,
|
||||
foreign key ("user_id") references "user_"("uid") on delete cascade,
|
||||
foreign key ("cid") references "collective"("cid") on delete cascade,
|
||||
unique("cid", "user_id", "name")
|
||||
unique("cid", "__user_id", "name")
|
||||
)
|
||||
|
@ -6,7 +6,8 @@ CREATE TABLE `query_bookmark` (
|
||||
`cid` varchar(254) not null,
|
||||
`query` varchar(2000) not null,
|
||||
`created` timestamp,
|
||||
`__user_id` varchar(254) not null,
|
||||
foreign key (`user_id`) references `user_`(`uid`) on delete cascade,
|
||||
foreign key (`cid`) references `collective`(`cid`) on delete cascade,
|
||||
unique(`cid`, `user_id`, `name`)
|
||||
unique(`cid`, `__user_id`, `name`)
|
||||
)
|
||||
|
@ -6,7 +6,8 @@ CREATE TABLE "query_bookmark" (
|
||||
"cid" varchar(254) not null,
|
||||
"query" varchar(2000) not null,
|
||||
"created" timestamp,
|
||||
"__user_id" varchar(254) not null,
|
||||
foreign key ("user_id") references "user_"("uid") on delete cascade,
|
||||
foreign key ("cid") references "collective"("cid") on delete cascade,
|
||||
unique("cid", "user_id", "name")
|
||||
unique("cid", "__user_id", "name")
|
||||
)
|
||||
|
@ -7,7 +7,7 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.syntax.option._
|
||||
import cats.syntax.all._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.query.ItemQuery
|
||||
@ -16,6 +16,7 @@ import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import docspell.store.AddResult
|
||||
|
||||
final case class RQueryBookmark(
|
||||
id: Ident,
|
||||
@ -48,6 +49,8 @@ object RQueryBookmark {
|
||||
val query = Column[ItemQuery]("query", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
|
||||
val internUserId = Column[String]("__user_id", this)
|
||||
|
||||
val all: NonEmptyList[Column[_]] =
|
||||
NonEmptyList.of(id, name, label, userId, cid, query, created)
|
||||
}
|
||||
@ -76,12 +79,14 @@ object RQueryBookmark {
|
||||
curTime
|
||||
)
|
||||
|
||||
def insert(r: RQueryBookmark): ConnectionIO[Int] =
|
||||
def insert(r: RQueryBookmark): ConnectionIO[Int] = {
|
||||
val userIdDummy = r.userId.getOrElse(Ident.unsafe("-"))
|
||||
DML.insert(
|
||||
T,
|
||||
T.all,
|
||||
sql"${r.id},${r.name},${r.label},${r.userId},${r.cid},${r.query},${r.created}"
|
||||
T.all.append(T.internUserId),
|
||||
sql"${r.id},${r.name},${r.label},${r.userId},${r.cid},${r.query},${r.created},$userIdDummy"
|
||||
)
|
||||
}
|
||||
|
||||
def update(r: RQueryBookmark): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
@ -97,6 +102,42 @@ object RQueryBookmark {
|
||||
def deleteById(cid: Ident, id: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.id === id && T.cid === cid)
|
||||
|
||||
def nameExists(account: AccountId, name: String): ConnectionIO[Boolean] = {
|
||||
val user = RUser.as("u")
|
||||
val bm = RQueryBookmark.as("bm")
|
||||
|
||||
val users = Select(
|
||||
user.uid.s,
|
||||
from(user),
|
||||
user.cid === account.collective && user.login === account.user
|
||||
)
|
||||
Select(
|
||||
select(count(bm.id)),
|
||||
from(bm),
|
||||
bm.name === name && bm.cid === account.collective && (bm.userId.isNull || bm.userId
|
||||
.in(users))
|
||||
).build.query[Int].unique.map(_ > 0)
|
||||
}
|
||||
|
||||
// impl note: store.add doesn't work, because it checks for duplicate
|
||||
// after trying to insert the check is necessary because a name
|
||||
// should be unique across personal *and* collective bookmarks
|
||||
def insertIfNotExists(
|
||||
account: AccountId,
|
||||
r: ConnectionIO[RQueryBookmark]
|
||||
): ConnectionIO[AddResult] =
|
||||
for {
|
||||
bm <- r
|
||||
res <-
|
||||
nameExists(account, bm.name).flatMap {
|
||||
case true =>
|
||||
AddResult
|
||||
.entityExists(s"A bookmark '${bm.name}' already exists.")
|
||||
.pure[ConnectionIO]
|
||||
case false => insert(bm).attempt.map(AddResult.fromUpdate)
|
||||
}
|
||||
} yield res
|
||||
|
||||
def allForUser(account: AccountId): ConnectionIO[Vector[RQueryBookmark]] = {
|
||||
val user = RUser.as("u")
|
||||
val bm = RQueryBookmark.as("bm")
|
||||
|
@ -76,9 +76,11 @@ initWith bm =
|
||||
|
||||
isValid : Model -> Bool
|
||||
isValid model =
|
||||
Comp.PowerSearchInput.isValid model.queryModel
|
||||
&& model.name
|
||||
/= Nothing
|
||||
List.all identity
|
||||
[ Comp.PowerSearchInput.isValid model.queryModel
|
||||
, model.name /= Nothing
|
||||
, not model.nameExists
|
||||
]
|
||||
|
||||
|
||||
get : Model -> Maybe BookmarkedQuery
|
||||
@ -116,14 +118,6 @@ update flags msg model =
|
||||
nameCheck1 name =
|
||||
Api.bookmarkNameExists flags name NameExistsResp
|
||||
|
||||
nameCheck2 =
|
||||
case model.name of
|
||||
Just n ->
|
||||
Api.bookmarkNameExists flags n NameExistsResp
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
throttleSub =
|
||||
Throttle.ifNeeded
|
||||
(Time.every 150 (\_ -> UpdateThrottle))
|
||||
@ -141,11 +135,7 @@ update flags msg model =
|
||||
)
|
||||
|
||||
SetPersonal flag ->
|
||||
let
|
||||
( newThrottle, cmd ) =
|
||||
Throttle.try nameCheck2 model.nameExistsThrottle
|
||||
in
|
||||
( { model | isPersonal = flag, nameExistsThrottle = newThrottle }, cmd, throttleSub )
|
||||
( { model | isPersonal = flag }, Cmd.none, Sub.none )
|
||||
|
||||
QueryMsg lm ->
|
||||
let
|
||||
@ -218,7 +208,7 @@ view texts model =
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class S.infoMessagePlain
|
||||
[ class S.warnMessagePlain
|
||||
, class "font-medium text-sm"
|
||||
, classList [ ( "invisible", not model.nameExists ) ]
|
||||
]
|
||||
|
@ -33,7 +33,7 @@ gb =
|
||||
, userLocationText = "The bookmarked query is just for you"
|
||||
, collectiveLocation = "Collective scope"
|
||||
, collectiveLocationText = "The bookmarked query can be used and edited by all users"
|
||||
, nameExistsWarning = "A bookmark with this name exists, it is overwritten on save!"
|
||||
, nameExistsWarning = "A bookmark with this name exists!"
|
||||
}
|
||||
|
||||
|
||||
@ -45,5 +45,5 @@ de =
|
||||
, userLocationText = "Der Bookmark ist nur für dich"
|
||||
, collectiveLocation = "Kollektiv-Bookmark"
|
||||
, collectiveLocationText = "Der Bookmark kann von allen Benutzer verwendet werden"
|
||||
, nameExistsWarning = "Der Bookmark existiert bereits. Er wird beim Speichern überschrieben."
|
||||
, nameExistsWarning = "Der Bookmark existiert bereits!"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user