Adopt store module to new collective table

This commit is contained in:
eikek
2022-07-05 21:17:18 +02:00
parent 35882fce84
commit 77f22bb5ea
65 changed files with 783 additions and 635 deletions

View File

@ -18,12 +18,13 @@ import scodec.bits.ByteVector
case class AuthToken( case class AuthToken(
nowMillis: Long, nowMillis: Long,
account: AccountId, account: AccountInfo,
requireSecondFactor: Boolean, requireSecondFactor: Boolean,
valid: Option[Duration], valid: Option[Duration],
salt: String, salt: String,
sig: String sig: String
) { ) {
def asString = def asString =
valid match { valid match {
case Some(v) => case Some(v) =>
@ -63,7 +64,7 @@ object AuthToken {
for { for {
millis <- TokenUtil.asInt(ms).toRight("Cannot read authenticator data") millis <- TokenUtil.asInt(ms).toRight("Cannot read authenticator data")
acc <- TokenUtil.b64dec(as).toRight("Cannot read authenticator data") acc <- TokenUtil.b64dec(as).toRight("Cannot read authenticator data")
accId <- AccountId.parse(acc) accId <- AccountInfo.parse(acc)
twofac <- Right[String, Boolean](java.lang.Boolean.parseBoolean(fa)) twofac <- Right[String, Boolean](java.lang.Boolean.parseBoolean(fa))
valid <- TokenUtil valid <- TokenUtil
.asInt(vs) .asInt(vs)
@ -75,7 +76,7 @@ object AuthToken {
for { for {
millis <- TokenUtil.asInt(ms).toRight("Cannot read authenticator data") millis <- TokenUtil.asInt(ms).toRight("Cannot read authenticator data")
acc <- TokenUtil.b64dec(as).toRight("Cannot read authenticator data") acc <- TokenUtil.b64dec(as).toRight("Cannot read authenticator data")
accId <- AccountId.parse(acc) accId <- AccountInfo.parse(acc)
twofac <- Right[String, Boolean](java.lang.Boolean.parseBoolean(fa)) twofac <- Right[String, Boolean](java.lang.Boolean.parseBoolean(fa))
} yield AuthToken(millis, accId, twofac, None, salt, sig) } yield AuthToken(millis, accId, twofac, None, salt, sig)
@ -84,7 +85,7 @@ object AuthToken {
} }
def user[F[_]: Sync]( def user[F[_]: Sync](
accountId: AccountId, accountId: AccountInfo,
requireSecondFactor: Boolean, requireSecondFactor: Boolean,
key: ByteVector, key: ByteVector,
valid: Option[Duration] valid: Option[Duration]

View File

@ -96,10 +96,12 @@ object Login {
for { for {
data <- store.transact(QLogin.findUser(accountId)) data <- store.transact(QLogin.findUser(accountId))
_ <- logF.trace(s"Account lookup: $data") _ <- logF.trace(s"Account lookup: $data")
res <- res <- data match {
if (data.exists(checkNoPassword(_, Set(AccountSource.OpenId)))) case Some(d) if checkNoPassword(d, Set(AccountSource.OpenId)) =>
doLogin(config, accountId, false) doLogin(config, d.account, false)
else Result.invalidAuth.pure[F] case _ =>
Result.invalidAuth.pure[F]
}
} yield res } yield res
def loginSession(config: Config)(sessionKey: String): F[Result] = def loginSession(config: Config)(sessionKey: String): F[Result] =
@ -122,9 +124,12 @@ object Login {
for { for {
data <- store.transact(QLogin.findUser(acc)) data <- store.transact(QLogin.findUser(acc))
_ <- logF.trace(s"Account lookup: $data") _ <- logF.trace(s"Account lookup: $data")
res <- res <- data match {
if (data.exists(check(up.pass))) doLogin(config, acc, up.rememberMe) case Some(d) if check(up.pass)(d) =>
else Result.invalidAuth.pure[F] doLogin(config, d.account, up.rememberMe)
case _ =>
Result.invalidAuth.pure[F]
}
} yield res } yield res
case Left(_) => case Left(_) =>
logF.info(s"User authentication failed for: ${up.hidePass}") *> logF.info(s"User authentication failed for: ${up.hidePass}") *>
@ -162,7 +167,7 @@ object Login {
(for { (for {
_ <- validateToken _ <- validateToken
key <- EitherT.fromOptionF( key <- EitherT.fromOptionF(
store.transact(RTotp.findEnabledByLogin(sf.token.account, true)), store.transact(RTotp.findEnabledByLogin(sf.token.account.userId, true)),
Result.invalidAuth Result.invalidAuth
) )
now <- EitherT.right[Result](Timestamp.current[F]) now <- EitherT.right[Result](Timestamp.current[F])
@ -175,13 +180,13 @@ object Login {
} }
def loginRememberMe(config: Config)(token: String): F[Result] = { def loginRememberMe(config: Config)(token: String): F[Result] = {
def okResult(acc: AccountId) = def okResult(acc: AccountInfo) =
for { for {
_ <- store.transact(RUser.updateLogin(acc)) _ <- store.transact(RUser.updateLogin(acc))
token <- AuthToken.user(acc, false, config.serverSecret, None) token <- AuthToken.user(acc, false, config.serverSecret, None)
} yield Result.ok(token, None) } yield Result.ok(token, None)
def doLogin(rid: Ident) = def rememberedLogin(rid: Ident) =
(for { (for {
now <- OptionT.liftF(Timestamp.current[F]) now <- OptionT.liftF(Timestamp.current[F])
minTime = now - config.rememberMe.valid minTime = now - config.rememberMe.valid
@ -214,7 +219,7 @@ object Login {
else if (rt.isExpired(config.rememberMe.valid)) else if (rt.isExpired(config.rememberMe.valid))
logF.info(s"RememberMe cookie expired ($rt).") *> Result.invalidTime logF.info(s"RememberMe cookie expired ($rt).") *> Result.invalidTime
.pure[F] .pure[F]
else doLogin(rt.rememberId) else rememberedLogin(rt.rememberId)
case Left(err) => case Left(err) =>
logF.info(s"RememberMe cookie was invalid: $err") *> Result.invalidAuth logF.info(s"RememberMe cookie was invalid: $err") *> Result.invalidAuth
.pure[F] .pure[F]
@ -245,11 +250,11 @@ object Login {
private def doLogin( private def doLogin(
config: Config, config: Config,
acc: AccountId, acc: AccountInfo,
rememberMe: Boolean rememberMe: Boolean
): F[Result] = ): F[Result] =
for { for {
require2FA <- store.transact(RTotp.isEnabled(acc)) require2FA <- store.transact(RTotp.isEnabled(acc.userId))
_ <- _ <-
if (require2FA) ().pure[F] if (require2FA) ().pure[F]
else store.transact(RUser.updateLogin(acc)) else store.transact(RUser.updateLogin(acc))
@ -263,13 +268,11 @@ object Login {
private def insertRememberToken( private def insertRememberToken(
store: Store[F], store: Store[F],
acc: AccountId, acc: AccountInfo,
config: Config config: Config
): F[RememberToken] = ): F[RememberToken] =
for { for {
uid <- OptionT(store.transact(RUser.findIdByAccount(acc))) rme <- RRememberMe.generate[F](acc.userId)
.getOrRaise(new IllegalStateException(s"No user_id found for account: $acc"))
rme <- RRememberMe.generate[F](uid)
_ <- store.transact(RRememberMe.insert(rme)) _ <- store.transact(RRememberMe.insert(rme))
token <- RememberToken.user(rme.id, config.serverSecret) token <- RememberToken.user(rme.id, config.serverSecret)
} yield token } yield token

View File

@ -6,7 +6,6 @@
package docspell.backend.ops package docspell.backend.ops
import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
@ -19,19 +18,19 @@ import docspell.store.records._
trait OQueryBookmarks[F[_]] { trait OQueryBookmarks[F[_]] {
def getAll(account: AccountId): F[Vector[OQueryBookmarks.Bookmark]] def getAll(account: AccountInfo): F[Vector[OQueryBookmarks.Bookmark]]
def findOne(account: AccountId, nameOrId: String): F[Option[OQueryBookmarks.Bookmark]] def findOne(account: AccountInfo, nameOrId: String): F[Option[OQueryBookmarks.Bookmark]]
def create(account: AccountId, bookmark: OQueryBookmarks.NewBookmark): F[AddResult] def create(account: AccountInfo, bookmark: OQueryBookmarks.NewBookmark): F[AddResult]
def update( def update(
account: AccountId, account: AccountInfo,
id: Ident, id: Ident,
bookmark: OQueryBookmarks.NewBookmark bookmark: OQueryBookmarks.NewBookmark
): F[UpdateResult] ): F[UpdateResult]
def delete(account: AccountId, bookmark: Ident): F[Unit] def delete(account: AccountInfo, bookmark: Ident): F[Unit]
} }
object OQueryBookmarks { object OQueryBookmarks {
@ -53,39 +52,43 @@ object OQueryBookmarks {
def apply[F[_]: Sync](store: Store[F]): Resource[F, OQueryBookmarks[F]] = def apply[F[_]: Sync](store: Store[F]): Resource[F, OQueryBookmarks[F]] =
Resource.pure(new OQueryBookmarks[F] { Resource.pure(new OQueryBookmarks[F] {
def getAll(account: AccountId): F[Vector[Bookmark]] = def getAll(account: AccountInfo): F[Vector[Bookmark]] =
store store
.transact(RQueryBookmark.allForUser(account)) .transact(RQueryBookmark.allForUser(account.collectiveId, account.userId))
.map(_.map(convert.toModel)) .map(_.map(convert.toModel))
def findOne( def findOne(
account: AccountId, account: AccountInfo,
nameOrId: String nameOrId: String
): F[Option[OQueryBookmarks.Bookmark]] = ): F[Option[OQueryBookmarks.Bookmark]] =
store store
.transact(RQueryBookmark.findByNameOrId(account, nameOrId)) .transact(
RQueryBookmark.findByNameOrId(account.collectiveId, account.userId, nameOrId)
)
.map(_.map(convert.toModel)) .map(_.map(convert.toModel))
def create(account: AccountId, b: NewBookmark): F[AddResult] = { def create(account: AccountInfo, b: NewBookmark): F[AddResult] = {
val uid = if (b.personal) account.userId.some else None
val record = val record =
RQueryBookmark.createNew(account, b.name, b.label, b.query, b.personal) RQueryBookmark.createNew(
store.transact(RQueryBookmark.insertIfNotExists(account, record)) account.collectiveId,
uid,
b.name,
b.label,
b.query
)
store.transact(
RQueryBookmark.insertIfNotExists(account.collectiveId, account.userId, record)
)
} }
def update(account: AccountId, id: Ident, b: NewBookmark): F[UpdateResult] = def update(acc: AccountInfo, id: Ident, b: NewBookmark): F[UpdateResult] =
UpdateResult.fromUpdate( UpdateResult.fromUpdate(
store.transact { store.transact(RQueryBookmark.update(convert.toRecord(acc, id, b)))
(for {
userId <- OptionT(RUser.findIdByAccount(account))
n <- OptionT.liftF(
RQueryBookmark.update(convert.toRecord(account, id, userId, b))
)
} yield n).getOrElse(0)
}
) )
def delete(account: AccountId, bookmark: Ident): F[Unit] = def delete(account: AccountInfo, bookmark: Ident): F[Unit] =
store.transact(RQueryBookmark.deleteById(account.collective, bookmark)).as(()) store.transact(RQueryBookmark.deleteById(account.collectiveId, bookmark)).as(())
}) })
private object convert { private object convert {
@ -94,17 +97,16 @@ object OQueryBookmarks {
Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created) Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created)
def toRecord( def toRecord(
account: AccountId, account: AccountInfo,
id: Ident, id: Ident,
userId: Ident,
b: NewBookmark b: NewBookmark
): RQueryBookmark = ): RQueryBookmark =
RQueryBookmark( RQueryBookmark(
id, id,
b.name, b.name,
b.label, b.label,
if (b.personal) userId.some else None, if (b.personal) account.userId.some else None,
account.collective, account.collectiveId,
b.query, b.query,
Timestamp.Epoch Timestamp.Epoch
) )

View File

@ -6,29 +6,29 @@
package docspell.backend.ops package docspell.backend.ops
import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.backend.ops.OTotp.{ConfirmResult, InitResult, OtpState} import docspell.backend.ops.OTotp.{ConfirmResult, InitResult, OtpState}
import docspell.common._ import docspell.common._
import docspell.store.records.{RTotp, RUser} import docspell.store.records.RTotp
import docspell.store.{AddResult, Store, UpdateResult} import docspell.store.{AddResult, Store, UpdateResult}
import docspell.totp.{Key, OnetimePassword, Totp} import docspell.totp.{Key, OnetimePassword, Totp}
trait OTotp[F[_]] { trait OTotp[F[_]] {
/** Return whether TOTP is enabled for this account or not. */ /** Return whether TOTP is enabled for this account or not. */
def state(accountId: AccountId): F[OtpState] def state(accountId: AccountInfo): F[OtpState]
/** Initializes TOTP by generating a secret and storing it in the database. TOTP is /** Initializes TOTP by generating a secret and storing it in the database. TOTP is
* still disabled, it must be confirmed in order to be active. * still disabled, it must be confirmed in order to be active.
*/ */
def initialize(accountId: AccountId): F[InitResult] def initialize(accountId: AccountInfo): F[InitResult]
/** Confirms and finishes initialization. TOTP is active after this for the given /** Confirms and finishes initialization. TOTP is active after this for the given
* account. * account.
*/ */
def confirmInit(accountId: AccountId, otp: OnetimePassword): F[ConfirmResult] def confirmInit(accountId: AccountInfo, otp: OnetimePassword): F[ConfirmResult]
/** Disables TOTP and removes the shared secret. If a otp is specified, it must be /** Disables TOTP and removes the shared secret. If a otp is specified, it must be
* valid. * valid.
@ -57,7 +57,7 @@ object OTotp {
sealed trait InitResult sealed trait InitResult
object InitResult { object InitResult {
final case class Success(accountId: AccountId, key: Key) extends InitResult { final case class Success(accountId: AccountInfo, key: Key) extends InitResult {
def authenticatorUrl(issuer: String): LenientUri = def authenticatorUrl(issuer: String): LenientUri =
LenientUri.unsafe( LenientUri.unsafe(
s"otpauth://totp/$issuer:${accountId.asString}?secret=${key.data.toBase32}&issuer=$issuer" s"otpauth://totp/$issuer:${accountId.asString}?secret=${key.data.toBase32}&issuer=$issuer"
@ -67,7 +67,7 @@ object OTotp {
case object NotFound extends InitResult case object NotFound extends InitResult
final case class Failed(ex: Throwable) extends InitResult final case class Failed(ex: Throwable) extends InitResult
def success(accountId: AccountId, key: Key): InitResult = def success(accountId: AccountInfo, key: Key): InitResult =
Success(accountId, key) Success(accountId, key)
def alreadyExists: InitResult = AlreadyExists def alreadyExists: InitResult = AlreadyExists
@ -85,47 +85,41 @@ object OTotp {
Resource.pure[F, OTotp[F]](new OTotp[F] { Resource.pure[F, OTotp[F]](new OTotp[F] {
val log = docspell.logging.getLogger[F] val log = docspell.logging.getLogger[F]
def initialize(accountId: AccountId): F[InitResult] = def initialize(accountId: AccountInfo): F[InitResult] =
for { for {
_ <- log.info(s"Initializing TOTP for account ${accountId.asString}") _ <- log.info(s"Initializing TOTP for account ${accountId.asString}")
userId <- store.transact(RUser.findIdByAccount(accountId)) result <- for {
result <- userId match { record <- RTotp.generate[F](accountId.userId, totp.settings.mac)
case Some(uid) => un <- store.transact(RTotp.updateDisabled(record))
for { an <-
record <- RTotp.generate[F](uid, totp.settings.mac) if (un != 0)
un <- store.transact(RTotp.updateDisabled(record)) AddResult.entityExists("Entity exists, but update was ok").pure[F]
an <- else store.add(RTotp.insert(record), RTotp.existsByUserId(accountId.userId))
if (un != 0) innerResult <-
AddResult.entityExists("Entity exists, but update was ok").pure[F] if (un != 0) InitResult.success(accountId, record.secret).pure[F]
else store.add(RTotp.insert(record), RTotp.existsByLogin(accountId)) else
innerResult <- an match {
if (un != 0) InitResult.success(accountId, record.secret).pure[F] case AddResult.EntityExists(msg) =>
else log.warn(
an match { s"A totp record already exists for account '${accountId.asString}': $msg!"
case AddResult.EntityExists(msg) => ) *>
log.warn( InitResult.alreadyExists.pure[F]
s"A totp record already exists for account '${accountId.asString}': $msg!" case AddResult.Failure(ex) =>
) *> log.warn(
InitResult.alreadyExists.pure[F] s"Failed to setup totp record for '${accountId.asString}': ${ex.getMessage}"
case AddResult.Failure(ex) => ) *>
log.warn( InitResult.failed(ex).pure[F]
s"Failed to setup totp record for '${accountId.asString}': ${ex.getMessage}" case AddResult.Success =>
) *> InitResult.success(accountId, record.secret).pure[F]
InitResult.failed(ex).pure[F] }
case AddResult.Success => } yield innerResult
InitResult.success(accountId, record.secret).pure[F]
}
} yield innerResult
case None =>
log.warn(s"No user found for account: ${accountId.asString}!") *>
InitResult.NotFound.pure[F]
}
} yield result } yield result
def confirmInit(accountId: AccountId, otp: OnetimePassword): F[ConfirmResult] = def confirmInit(accountId: AccountInfo, otp: OnetimePassword): F[ConfirmResult] =
for { for {
_ <- log.info(s"Confirm TOTP setup for account ${accountId.asString}") _ <- log.info(s"Confirm TOTP setup for account ${accountId.asString}")
key <- store.transact(RTotp.findEnabledByLogin(accountId, false)) key <- store.transact(RTotp.findEnabledByUserId(accountId.userId, false))
now <- Timestamp.current[F] now <- Timestamp.current[F]
res <- key match { res <- key match {
case None => case None =>
@ -134,7 +128,7 @@ object OTotp {
val check = totp.checkPassword(r.secret, otp, now.value) val check = totp.checkPassword(r.secret, otp, now.value)
if (check) if (check)
store store
.transact(RTotp.setEnabled(accountId, true)) .transact(RTotp.setEnabled(accountId.userId, true))
.map(_ => ConfirmResult.Success) .map(_ => ConfirmResult.Success)
else ConfirmResult.Failed.pure[F] else ConfirmResult.Failed.pure[F]
} }
@ -154,7 +148,7 @@ object OTotp {
val check = totp.checkPassword(r.secret, pw, now.value) val check = totp.checkPassword(r.secret, pw, now.value)
if (check) if (check)
UpdateResult.fromUpdate( UpdateResult.fromUpdate(
store.transact(RTotp.setEnabled(accountId, false)) store.transact(RTotp.setEnabled(r.userId, false))
) )
else else
log.info(s"TOTP code was invalid. Not disabling it.") *> UpdateResult log.info(s"TOTP code was invalid. Not disabling it.") *> UpdateResult
@ -163,12 +157,17 @@ object OTotp {
} }
} yield res } yield res
case None => case None =>
UpdateResult.fromUpdate(store.transact(RTotp.setEnabled(accountId, false))) UpdateResult.fromUpdate {
(for {
key <- OptionT(RTotp.findEnabledByLogin(accountId, true))
n <- OptionT.liftF(RTotp.setEnabled(key.userId, false))
} yield n).mapK(store.transform).getOrElse(0)
}
} }
def state(accountId: AccountId): F[OtpState] = def state(acc: AccountInfo): F[OtpState] =
for { for {
record <- store.transact(RTotp.findEnabledByLogin(accountId, true)) record <- store.transact(RTotp.findEnabledByUserId(acc.userId, true))
result = record match { result = record match {
case Some(r) => case Some(r) =>
OtpState.Enabled(r.created) OtpState.Enabled(r.created)

View File

@ -0,0 +1,48 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.common
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}
final case class AccountInfo(
collectiveId: CollectiveId,
collective: Ident,
userId: Ident,
login: Ident
) {
def asAccountId: AccountId =
AccountId(collective, login)
def asString: String =
s"${collectiveId.value}/${collective.id}/${userId.id}/${login.id}"
}
object AccountInfo {
implicit val jsonDecoder: Decoder[AccountInfo] = deriveDecoder
implicit val jsonEncoder: Encoder[AccountInfo] = deriveEncoder
def parse(str: String): Either[String, AccountInfo] = {
val input = str.replaceAll("\\s+", "").trim
val invalid: Either[String, AccountInfo] =
Left(s"Cannot parse account info: $str")
input.split('/').toList match {
case collId :: collName :: userId :: login :: Nil =>
for {
cid <- collId.toLongOption.toRight(s"Invalid collective id: $collId")
cn <- Ident.fromString(collName)
uid <- Ident.fromString(userId)
un <- Ident.fromString(login)
} yield AccountInfo(CollectiveId(cid), cn, uid, un)
case _ =>
invalid
}
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2020 Eike K. & Contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package docspell.common
import io.circe.{Decoder, Encoder}
final class CollectiveId(val value: Long) extends AnyVal {
override def toString =
s"CollectiveId($value)"
}
object CollectiveId {
val unknown: CollectiveId = CollectiveId(-1)
def apply(n: Long): CollectiveId = new CollectiveId(n)
implicit val jsonEncoder: Encoder[CollectiveId] =
Encoder.encodeLong.contramap(_.value)
implicit val jsonDecoder: Decoder[CollectiveId] =
Decoder.decodeLong.map(CollectiveId.apply)
}

View File

@ -21,7 +21,7 @@ sealed trait Event {
def eventType: EventType def eventType: EventType
/** The user who caused it. */ /** The user who caused it. */
def account: AccountId def account: AccountInfo
/** The base url for generating links. This is dynamic. */ /** The base url for generating links. This is dynamic. */
def baseUrl: Option[LenientUri] def baseUrl: Option[LenientUri]
@ -62,7 +62,7 @@ object Event {
/** Event triggered when tags of one or more items have changed */ /** Event triggered when tags of one or more items have changed */
final case class TagsChanged( final case class TagsChanged(
account: AccountId, account: AccountInfo,
items: Nel[Ident], items: Nel[Ident],
added: List[String], added: List[String],
removed: List[String], removed: List[String],
@ -75,11 +75,11 @@ object Event {
items: Nel[Ident], items: Nel[Ident],
added: List[String], added: List[String],
removed: List[String] removed: List[String]
): (AccountId, Option[LenientUri]) => TagsChanged = ): (AccountInfo, Option[LenientUri]) => TagsChanged =
(acc, url) => TagsChanged(acc, items, added, removed, url) (acc, url) => TagsChanged(acc, items, added, removed, url)
def sample[F[_]: Sync]( def sample[F[_]: Sync](
account: AccountId, account: AccountInfo,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
): F[TagsChanged] = ): F[TagsChanged] =
for { for {
@ -91,7 +91,7 @@ object Event {
/** Event triggered when a custom field on an item changes. */ /** Event triggered when a custom field on an item changes. */
final case class SetFieldValue( final case class SetFieldValue(
account: AccountId, account: AccountInfo,
items: Nel[Ident], items: Nel[Ident],
field: Ident, field: Ident,
value: String, value: String,
@ -104,11 +104,11 @@ object Event {
items: Nel[Ident], items: Nel[Ident],
field: Ident, field: Ident,
value: String value: String
): (AccountId, Option[LenientUri]) => SetFieldValue = ): (AccountInfo, Option[LenientUri]) => SetFieldValue =
(acc, url) => SetFieldValue(acc, items, field, value, url) (acc, url) => SetFieldValue(acc, items, field, value, url)
def sample[F[_]: Sync]( def sample[F[_]: Sync](
account: AccountId, account: AccountInfo,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
): F[SetFieldValue] = ): F[SetFieldValue] =
for { for {
@ -118,7 +118,7 @@ object Event {
} }
final case class DeleteFieldValue( final case class DeleteFieldValue(
account: AccountId, account: AccountInfo,
items: Nel[Ident], items: Nel[Ident],
field: Ident, field: Ident,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
@ -129,11 +129,11 @@ object Event {
def partial( def partial(
items: Nel[Ident], items: Nel[Ident],
field: Ident field: Ident
): (AccountId, Option[LenientUri]) => DeleteFieldValue = ): (AccountInfo, Option[LenientUri]) => DeleteFieldValue =
(acc, url) => DeleteFieldValue(acc, items, field, url) (acc, url) => DeleteFieldValue(acc, items, field, url)
def sample[F[_]: Sync]( def sample[F[_]: Sync](
account: AccountId, account: AccountInfo,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
): F[DeleteFieldValue] = ): F[DeleteFieldValue] =
for { for {
@ -147,7 +147,7 @@ object Event {
* search results. * search results.
*/ */
final case class ItemSelection( final case class ItemSelection(
account: AccountId, account: AccountInfo,
items: Nel[Ident], items: Nel[Ident],
more: Boolean, more: Boolean,
baseUrl: Option[LenientUri], baseUrl: Option[LenientUri],
@ -158,7 +158,7 @@ object Event {
case object ItemSelection extends EventType { case object ItemSelection extends EventType {
def sample[F[_]: Sync]( def sample[F[_]: Sync](
account: AccountId, account: AccountInfo,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
): F[ItemSelection] = ): F[ItemSelection] =
for { for {
@ -169,6 +169,7 @@ object Event {
/** Event when a new job is added to the queue */ /** Event when a new job is added to the queue */
final case class JobSubmitted( final case class JobSubmitted(
account: AccountInfo,
jobId: Ident, jobId: Ident,
group: Ident, group: Ident,
task: Ident, task: Ident,
@ -179,26 +180,27 @@ object Event {
) extends Event { ) extends Event {
val eventType = JobSubmitted val eventType = JobSubmitted
val baseUrl = None val baseUrl = None
def account: AccountId = AccountId(group, submitter)
} }
case object JobSubmitted extends EventType { case object JobSubmitted extends EventType {
def sample[F[_]: Sync](account: AccountId): F[JobSubmitted] = def sample[F[_]: Sync](account: AccountInfo): F[JobSubmitted] =
for { for {
id <- Ident.randomId[F] id <- Ident.randomId[F]
ev = JobSubmitted( ev = JobSubmitted(
account,
id, id,
account.collective, account.collective,
Ident.unsafe("process-something-task"), Ident.unsafe("process-something-task"),
"", "",
JobState.running, JobState.running,
"Process 3 files", "Process 3 files",
account.user account.login
) )
} yield ev } yield ev
} }
/** Event when a job is finished (in final state). */ /** Event when a job is finished (in final state). */
final case class JobDone( final case class JobDone(
account: AccountInfo,
jobId: Ident, jobId: Ident,
group: Ident, group: Ident,
task: Ident, task: Ident,
@ -211,20 +213,20 @@ object Event {
) extends Event { ) extends Event {
val eventType = JobDone val eventType = JobDone
val baseUrl = None val baseUrl = None
def account: AccountId = AccountId(group, submitter)
} }
case object JobDone extends EventType { case object JobDone extends EventType {
def sample[F[_]: Sync](account: AccountId): F[JobDone] = def sample[F[_]: Sync](account: AccountInfo): F[JobDone] =
for { for {
id <- Ident.randomId[F] id <- Ident.randomId[F]
ev = JobDone( ev = JobDone(
account,
id, id,
account.collective, account.collective,
Ident.unsafe("process-something-task"), Ident.unsafe("process-something-task"),
"", "",
JobState.running, JobState.running,
"Process 3 files", "Process 3 files",
account.user, account.login,
Json.Null, Json.Null,
None None
) )
@ -233,7 +235,7 @@ object Event {
def sample[F[_]: Sync]( def sample[F[_]: Sync](
evt: EventType, evt: EventType,
account: AccountId, account: AccountInfo,
baseUrl: Option[LenientUri] baseUrl: Option[LenientUri]
): F[Event] = ): F[Event] =
evt match { evt match {

View File

@ -25,7 +25,7 @@ trait EventContext {
"eventType" -> event.eventType.asJson, "eventType" -> event.eventType.asJson,
"account" -> Json.obj( "account" -> Json.obj(
"collective" -> event.account.collective.asJson, "collective" -> event.account.collective.asJson,
"user" -> event.account.user.asJson, "user" -> event.account.login.asJson,
"login" -> event.account.asJson "login" -> event.account.asJson
), ),
"content" -> content "content" -> content

View File

@ -72,7 +72,7 @@ object TotpRoutes {
for { for {
data <- req.as[OtpConfirm] data <- req.as[OtpConfirm]
result <- backend.totp.disable( result <- backend.totp.disable(
user.account, user.account.asAccountId,
OnetimePassword(data.otp.pass).some OnetimePassword(data.otp.pass).some
) )
resp <- Ok(Conversions.basicResult(result, "TOTP setup disabled.")) resp <- Ok(Conversions.basicResult(result, "TOTP setup disabled."))

View File

@ -13,6 +13,7 @@ import cats.implicits._
import docspell.common._ import docspell.common._
import docspell.common.syntax.StringSyntax._ import docspell.common.syntax.StringSyntax._
import docspell.notification.api._ import docspell.notification.api._
import docspell.store.queries.QLogin
import docspell.store.records._ import docspell.store.records._
import db.migration.data._ import db.migration.data._
@ -122,7 +123,8 @@ trait MigrationTasks {
private def saveChannel(ch: Channel, account: AccountId): ConnectionIO[ChannelRef] = private def saveChannel(ch: Channel, account: AccountId): ConnectionIO[ChannelRef] =
(for { (for {
newId <- OptionT.liftF(Ident.randomId[ConnectionIO]) newId <- OptionT.liftF(Ident.randomId[ConnectionIO])
userId <- OptionT(RUser.findIdByAccount(account)) userData <- OptionT(QLogin.findUser(account))
userId = userData.account.userId
r <- RNotificationChannel.fromChannel(ch, newId, userId) r <- RNotificationChannel.fromChannel(ch, newId, userId)
_ <- OptionT.liftF(RNotificationChannel.insert(r)) _ <- OptionT.liftF(RNotificationChannel.insert(r))
_ <- OptionT.liftF( _ <- OptionT.liftF(
@ -172,7 +174,8 @@ trait MigrationTasks {
} }
for { for {
userId <- OptionT(RUser.findIdByAccount(old.account)) userData <- OptionT(QLogin.findUser(old.account))
userId = userData.account.userId
id <- OptionT.liftF(Ident.randomId[ConnectionIO]) id <- OptionT.liftF(Ident.randomId[ConnectionIO])
now <- OptionT.liftF(Timestamp.current[ConnectionIO]) now <- OptionT.liftF(Timestamp.current[ConnectionIO])
chName = Some("migrate notify items") chName = Some("migrate notify items")
@ -198,8 +201,7 @@ trait MigrationTasks {
} }
def mkTransactor(ctx: Context): Transactor[IO] = { def mkTransactor(ctx: Context): Transactor[IO] = {
val xa = Transactor.fromConnection[IO](ctx.getConnection()) val xa = Transactor.fromConnection[IO](ctx.getConnection)
Transactor.strategy.set(xa, Strategy.void) // transactions are handled by flyway Transactor.strategy.set(xa, Strategy.void) // transactions are handled by flyway
} }
} }

View File

@ -42,6 +42,9 @@ trait DoobieMeta extends EmilDoobieMeta {
e.apply(a).noSpaces e.apply(a).noSpaces
) )
implicit val metaCollectiveId: Meta[CollectiveId] =
Meta[Long].timap(CollectiveId.apply)(_.value)
implicit val metaAddonTriggerType: Meta[AddonTriggerType] = implicit val metaAddonTriggerType: Meta[AddonTriggerType] =
Meta[String].timap(AddonTriggerType.unsafeFromString)(_.name) Meta[String].timap(AddonTriggerType.unsafeFromString)(_.name)

View File

@ -24,12 +24,12 @@ import doobie.util.Put
object ItemQueryGenerator { object ItemQueryGenerator {
def apply(today: LocalDate, tables: Tables, coll: Ident)(q: ItemQuery)(implicit def apply(today: LocalDate, tables: Tables, coll: CollectiveId)(q: ItemQuery)(implicit
PT: Put[Timestamp] PT: Put[Timestamp]
): Condition = ): Condition =
fromExpr(today, tables, coll)(q.expr) fromExpr(today, tables, coll)(q.expr)
final def fromExpr(today: LocalDate, tables: Tables, coll: Ident)( final def fromExpr(today: LocalDate, tables: Tables, coll: CollectiveId)(
expr: Expr expr: Expr
)(implicit PT: Put[Timestamp]): Condition = )(implicit PT: Put[Timestamp]): Condition =
expr match { expr match {
@ -217,7 +217,7 @@ object ItemQueryGenerator {
case Date.Local(date) => case Date.Local(date) =>
date date
case Date.Millis(ms) => case Date.Millis(ms) =>
Instant.ofEpochMilli(ms).atZone(Timestamp.UTC).toLocalDate() Instant.ofEpochMilli(ms).atZone(Timestamp.UTC).toLocalDate
case Date.Today => case Date.Today =>
today today
} }
@ -285,7 +285,7 @@ object ItemQueryGenerator {
private def itemsWithCustomField( private def itemsWithCustomField(
sel: RCustomField.Table => Condition sel: RCustomField.Table => Condition
)(coll: Ident, op: QOp, value: String): Select = { )(coll: CollectiveId, op: QOp, value: String): Select = {
val cf = RCustomField.as("cf") val cf = RCustomField.as("cf")
val cfv = RCustomFieldValue.as("cfv") val cfv = RCustomFieldValue.as("cfv")

View File

@ -24,6 +24,6 @@ case class ItemData(
relatedItems: Vector[ListItem] relatedItems: Vector[ListItem]
) { ) {
def filterCollective(coll: Ident): Option[ItemData] = def filterCollective(coll: CollectiveId): Option[ItemData] =
if (item.cid == coll) Some(this) else None if (item.cid == coll) Some(this) else None
} }

View File

@ -75,7 +75,7 @@ object QAttachment {
*/ */
def deleteSingleAttachment[F[_]: Sync]( def deleteSingleAttachment[F[_]: Sync](
store: Store[F] store: Store[F]
)(attachId: Ident, coll: Ident): F[Int] = { )(attachId: Ident, coll: CollectiveId): F[Int] = {
val loadFiles = for { val loadFiles = for {
ra <- RAttachment.findByIdAndCollective(attachId, coll).map(_.map(_.fileId)) ra <- RAttachment.findByIdAndCollective(attachId, coll).map(_.map(_.fileId))
rs <- RAttachmentSource.findByIdAndCollective(attachId, coll).map(_.map(_.fileId)) rs <- RAttachmentSource.findByIdAndCollective(attachId, coll).map(_.map(_.fileId))
@ -138,7 +138,7 @@ object QAttachment {
def deleteItemAttachments[F[_]: Sync]( def deleteItemAttachments[F[_]: Sync](
store: Store[F] store: Store[F]
)(itemId: Ident, coll: Ident): F[Int] = { )(itemId: Ident, coll: CollectiveId): F[Int] = {
val logger = docspell.logging.getLogger[F] val logger = docspell.logging.getLogger[F]
for { for {
ras <- store.transact(RAttachment.findByItemAndCollective(itemId, coll)) ras <- store.transact(RAttachment.findByItemAndCollective(itemId, coll))
@ -151,7 +151,10 @@ object QAttachment {
} yield ns.sum } yield ns.sum
} }
def getMetaProposals(itemId: Ident, coll: Ident): ConnectionIO[MetaProposalList] = { def getMetaProposals(
itemId: Ident,
coll: CollectiveId
): ConnectionIO[MetaProposalList] = {
val qa = Select( val qa = Select(
select(am.proposals), select(am.proposals),
from(am) from(am)
@ -177,7 +180,7 @@ object QAttachment {
def getAttachmentMeta( def getAttachmentMeta(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Option[RAttachmentMeta]] = { ): ConnectionIO[Option[RAttachmentMeta]] = {
val q = Select( val q = Select(
select(am.all), select(am.all),
@ -204,14 +207,14 @@ object QAttachment {
case class ContentAndName( case class ContentAndName(
id: Ident, id: Ident,
item: Ident, item: Ident,
collective: Ident, collective: CollectiveId,
folder: Option[Ident], folder: Option[Ident],
lang: Language, lang: Language,
name: Option[String], name: Option[String],
content: Option[String] content: Option[String]
) )
def allAttachmentMetaAndName( def allAttachmentMetaAndName(
coll: Option[Ident], coll: Option[CollectiveId],
itemIds: Option[Nel[Ident]], itemIds: Option[Nel[Ident]],
itemStates: Nel[ItemState], itemStates: Nel[ItemState],
chunkSize: Int chunkSize: Int
@ -237,5 +240,4 @@ object QAttachment {
).build ).build
.query[ContentAndName] .query[ContentAndName]
.streamWithChunkSize(chunkSize) .streamWithChunkSize(chunkSize)
} }

View File

@ -30,7 +30,7 @@ object QCollective {
val empty = Names(Vector.empty, Vector.empty, Vector.empty) val empty = Names(Vector.empty, Vector.empty, Vector.empty)
} }
def allNames(collective: Ident, maxEntries: Int): ConnectionIO[Names] = { def allNames(collective: CollectiveId, maxEntries: Int): ConnectionIO[Names] = {
val created = Column[Timestamp]("created", TableDef("")) val created = Column[Timestamp]("created", TableDef(""))
union( union(
Select( Select(
@ -70,7 +70,7 @@ object QCollective {
tags: List[TagCount] tags: List[TagCount]
) )
def getInsights(coll: Ident): ConnectionIO[InsightData] = { def getInsights(coll: CollectiveId): ConnectionIO[InsightData] = {
val q0 = Select( val q0 = Select(
count(i.id).s, count(i.id).s,
from(i), from(i),
@ -120,7 +120,7 @@ object QCollective {
} yield InsightData(incoming, outgoing, deleted, size.getOrElse(0L), tags) } yield InsightData(incoming, outgoing, deleted, size.getOrElse(0L), tags)
} }
def tagCloud(coll: Ident): ConnectionIO[List[TagCount]] = { def tagCloud(coll: CollectiveId): ConnectionIO[List[TagCount]] = {
val sql = val sql =
Select( Select(
select(t.all).append(count(ti.itemId).s), select(t.all).append(count(ti.itemId).s),
@ -132,7 +132,7 @@ object QCollective {
} }
def getContacts( def getContacts(
coll: Ident, coll: CollectiveId,
query: Option[String], query: Option[String],
kind: Option[ContactKind] kind: Option[ContactKind]
): Stream[ConnectionIO, RContact] = { ): Stream[ConnectionIO, RContact] = {

View File

@ -23,17 +23,20 @@ object QCustomField {
final case class CustomFieldData(field: RCustomField, usageCount: Int) final case class CustomFieldData(field: RCustomField, usageCount: Int)
def findAllLike( def findAllLike(
coll: Ident, coll: CollectiveId,
nameQuery: Option[String], nameQuery: Option[String],
order: RCustomField.Table => Nel[OrderBy] order: RCustomField.Table => Nel[OrderBy]
): ConnectionIO[Vector[CustomFieldData]] = ): ConnectionIO[Vector[CustomFieldData]] =
findFragment(coll, nameQuery, None, order).build.query[CustomFieldData].to[Vector] findFragment(coll, nameQuery, None, order).build.query[CustomFieldData].to[Vector]
def findById(field: Ident, collective: Ident): ConnectionIO[Option[CustomFieldData]] = def findById(
field: Ident,
collective: CollectiveId
): ConnectionIO[Option[CustomFieldData]] =
findFragment(collective, None, field.some).build.query[CustomFieldData].option findFragment(collective, None, field.some).build.query[CustomFieldData].option
private def findFragment( private def findFragment(
coll: Ident, coll: CollectiveId,
nameQuery: Option[String], nameQuery: Option[String],
fieldId: Option[Ident], fieldId: Option[Ident],
order: RCustomField.Table => Nel[OrderBy] = t => Nel.of(t.name.asc) order: RCustomField.Table => Nel[OrderBy] = t => Nel.of(t.name.asc)
@ -69,5 +72,4 @@ object QCustomField {
.query[FieldValue] .query[FieldValue]
.to[List] .to[List]
} }
} }

View File

@ -54,7 +54,7 @@ object QFolder {
def exists: FolderChangeResult = Exists def exists: FolderChangeResult = Exists
} }
def delete(id: Ident, account: AccountId): ConnectionIO[FolderChangeResult] = { def delete(id: Ident, userId: Ident): ConnectionIO[FolderChangeResult] = {
def tryDelete = def tryDelete =
for { for {
_ <- RItem.removeFolder(id) _ <- RItem.removeFolder(id)
@ -64,10 +64,9 @@ object QFolder {
} yield FolderChangeResult.success } yield FolderChangeResult.success
(for { (for {
uid <- OptionT(findUserId(account))
folder <- OptionT(RFolder.findById(id)) folder <- OptionT(RFolder.findById(id))
res <- OptionT.liftF( res <- OptionT.liftF(
if (folder.owner == uid) tryDelete if (folder.owner == userId) tryDelete
else FolderChangeResult.forbidden.pure[ConnectionIO] else FolderChangeResult.forbidden.pure[ConnectionIO]
) )
} yield res).getOrElse(FolderChangeResult.notFound) } yield res).getOrElse(FolderChangeResult.notFound)
@ -75,7 +74,7 @@ object QFolder {
def changeName( def changeName(
folder: Ident, folder: Ident,
account: AccountId, userId: Ident,
name: String name: String
): ConnectionIO[FolderChangeResult] = { ): ConnectionIO[FolderChangeResult] = {
def tryUpdate(ns: RFolder): ConnectionIO[FolderChangeResult] = def tryUpdate(ns: RFolder): ConnectionIO[FolderChangeResult] =
@ -87,10 +86,9 @@ object QFolder {
} yield res } yield res
(for { (for {
uid <- OptionT(findUserId(account))
folder <- OptionT(RFolder.findById(folder)) folder <- OptionT(RFolder.findById(folder))
res <- OptionT.liftF( res <- OptionT.liftF(
if (folder.owner == uid) tryUpdate(folder.copy(name = name)) if (folder.owner == userId) tryUpdate(folder.copy(name = name))
else FolderChangeResult.forbidden.pure[ConnectionIO] else FolderChangeResult.forbidden.pure[ConnectionIO]
) )
} yield res).getOrElse(FolderChangeResult.notFound) } yield res).getOrElse(FolderChangeResult.notFound)
@ -98,7 +96,7 @@ object QFolder {
def removeMember( def removeMember(
folder: Ident, folder: Ident,
account: AccountId, userId: Ident,
member: Ident member: Ident
): ConnectionIO[FolderChangeResult] = { ): ConnectionIO[FolderChangeResult] = {
def tryRemove: ConnectionIO[FolderChangeResult] = def tryRemove: ConnectionIO[FolderChangeResult] =
@ -110,10 +108,9 @@ object QFolder {
} yield res } yield res
(for { (for {
uid <- OptionT(findUserId(account))
folder <- OptionT(RFolder.findById(folder)) folder <- OptionT(RFolder.findById(folder))
res <- OptionT.liftF( res <- OptionT.liftF(
if (folder.owner == uid) tryRemove if (folder.owner == userId) tryRemove
else FolderChangeResult.forbidden.pure[ConnectionIO] else FolderChangeResult.forbidden.pure[ConnectionIO]
) )
} yield res).getOrElse(FolderChangeResult.notFound) } yield res).getOrElse(FolderChangeResult.notFound)
@ -121,7 +118,7 @@ object QFolder {
def addMember( def addMember(
folder: Ident, folder: Ident,
account: AccountId, userId: Ident,
member: Ident member: Ident
): ConnectionIO[FolderChangeResult] = { ): ConnectionIO[FolderChangeResult] = {
def tryAdd: ConnectionIO[FolderChangeResult] = def tryAdd: ConnectionIO[FolderChangeResult] =
@ -134,16 +131,19 @@ object QFolder {
} yield res } yield res
(for { (for {
uid <- OptionT(findUserId(account))
folder <- OptionT(RFolder.findById(folder)) folder <- OptionT(RFolder.findById(folder))
res <- OptionT.liftF( res <- OptionT.liftF(
if (folder.owner == uid) tryAdd if (folder.owner == userId) tryAdd
else FolderChangeResult.forbidden.pure[ConnectionIO] else FolderChangeResult.forbidden.pure[ConnectionIO]
) )
} yield res).getOrElse(FolderChangeResult.notFound) } yield res).getOrElse(FolderChangeResult.notFound)
} }
def findById(id: Ident, account: AccountId): ConnectionIO[Option[FolderDetail]] = { def findById(
id: Ident,
collectiveId: CollectiveId,
userId: Ident
): ConnectionIO[Option[FolderDetail]] = {
val user = RUser.as("u") val user = RUser.as("u")
val member = RFolderMember.as("m") val member = RFolderMember.as("m")
val folder = RFolder.as("s") val folder = RFolder.as("s")
@ -153,12 +153,19 @@ object QFolder {
from(member) from(member)
.innerJoin(user, member.user === user.uid) .innerJoin(user, member.user === user.uid)
.innerJoin(folder, member.folder === folder.id), .innerJoin(folder, member.folder === folder.id),
member.folder === id && folder.collective === account.collective member.folder === id && folder.collective === collectiveId
).query[IdRef].to[Vector] ).query[IdRef].to[Vector]
(for { (for {
folder <- OptionT( folder <- OptionT(
findAll(account, Some(id), None, None, (ft, _) => Nel.of(ft.name.asc)) findAll(
collectiveId,
userId,
Some(id),
None,
None,
(ft, _) => Nel.of(ft.name.asc)
)
.map(_.headOption) .map(_.headOption)
) )
memb <- OptionT.liftF(memberQ) memb <- OptionT.liftF(memberQ)
@ -166,7 +173,8 @@ object QFolder {
} }
def findAll( def findAll(
account: AccountId, collectiveId: CollectiveId,
userId: Ident,
idQ: Option[Ident], idQ: Option[Ident],
ownerLogin: Option[Ident], ownerLogin: Option[Ident],
nameQ: Option[String], nameQ: Option[String],
@ -199,22 +207,20 @@ object QFolder {
val folder = RFolder.as("s") val folder = RFolder.as("s")
val memlogin = TableDef("memberlogin") val memlogin = TableDef("memberlogin")
val mlFolder = Column[Ident]("folder", memlogin) val mlFolder = Column[Ident]("folder", memlogin)
val mlLogin = Column[Ident]("login", memlogin) val mlUser = Column[Ident]("user_id", memlogin)
withCte( withCte(
memlogin -> union( memlogin -> union(
Select( Select(
select(member.folder.as(mlFolder), user.login.as(mlLogin)), select(member.folder.as(mlFolder), member.user.as(mlUser)),
from(member) from(member)
.innerJoin(user, user.uid === member.user)
.innerJoin(folder, folder.id === member.folder), .innerJoin(folder, folder.id === member.folder),
folder.collective === account.collective folder.collective === collectiveId
), ),
Select( Select(
select(folder.id.as(mlFolder), user.login.as(mlLogin)), select(folder.id.as(mlFolder), folder.owner.as(mlUser)),
from(folder) from(folder),
.innerJoin(user, user.uid === folder.owner), folder.collective === collectiveId
folder.collective === account.collective
) )
) )
)( )(
@ -228,7 +234,7 @@ object QFolder {
Select( Select(
select(countAll > 0), select(countAll > 0),
from(memlogin), from(memlogin),
mlFolder === folder.id && mlLogin === account.user mlFolder === folder.id && mlUser === userId
).as("member"), ).as("member"),
Select( Select(
select(countAll - 1), select(countAll - 1),
@ -239,7 +245,7 @@ object QFolder {
from(folder) from(folder)
.innerJoin(user, user.uid === folder.owner), .innerJoin(user, user.uid === folder.owner),
where( where(
folder.collective === account.collective &&? folder.collective === collectiveId &&?
idQ.map(id => folder.id === id) &&? idQ.map(id => folder.id === id) &&?
nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&? nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&?
ownerLogin.map(login => user.login === login) ownerLogin.map(login => user.login === login)
@ -249,7 +255,7 @@ object QFolder {
} }
/** Select all folder_id where the given account is member or owner. */ /** Select all folder_id where the given account is member or owner. */
def findMemberFolderIds(account: AccountId): Select = { def findMemberFolderIds(cid: CollectiveId, userId: Ident): Select = {
val user = RUser.as("u") val user = RUser.as("u")
val f = RFolder.as("f") val f = RFolder.as("f")
val m = RFolderMember.as("m") val m = RFolderMember.as("m")
@ -257,21 +263,21 @@ object QFolder {
Select( Select(
select(f.id), select(f.id),
from(f).innerJoin(user, f.owner === user.uid), from(f).innerJoin(user, f.owner === user.uid),
f.collective === account.collective && user.login === account.user f.collective === cid && user.uid === userId
), ),
Select( Select(
select(m.folder), select(m.folder),
from(m) from(m)
.innerJoin(f, f.id === m.folder) .innerJoin(f, f.id === m.folder)
.innerJoin(user, user.uid === m.user), .innerJoin(user, user.uid === m.user),
f.collective === account.collective && user.login === account.user f.collective === cid && user.uid === userId
) )
) )
} }
def getMemberFolders(account: AccountId): ConnectionIO[Set[Ident]] = def getMemberFolders(
findMemberFolderIds(account).build.query[Ident].to[Set] collectiveId: CollectiveId,
userId: Ident
private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] = ): ConnectionIO[Set[Ident]] =
RUser.findByAccount(account).map(_.map(_.uid)) findMemberFolderIds(collectiveId, userId).build.query[Ident].to[Set]
} }

View File

@ -64,7 +64,7 @@ object QItem extends FtsSupport {
val cteFts = ftsTable.map(cteTable) val cteFts = ftsTable.map(cteTable)
val sql = val sql =
findItemsBase(q.fix, today, maxNoteLen, cteFts) findItemsBase(q.fix, today, maxNoteLen, cteFts)
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.joinFtsDetails(i, ftsTable) .joinFtsDetails(i, ftsTable)
.limit(batch) .limit(batch)
.build .build
@ -73,7 +73,7 @@ object QItem extends FtsSupport {
sql.query[ListItem].stream sql.query[ListItem].stream
} }
def findItem(id: Ident, collective: Ident): ConnectionIO[Option[ItemData]] = { def findItem(id: Ident, collective: CollectiveId): ConnectionIO[Option[ItemData]] = {
val cq = val cq =
Select( Select(
select(i.all, org.all, pers0.all, pers1.all, equip.all) select(i.all, org.all, pers0.all, pers1.all, equip.all)
@ -121,7 +121,10 @@ object QItem extends FtsSupport {
) )
} }
def findRelatedItems(id: Ident, collective: Ident): ConnectionIO[Vector[ListItem]] = def findRelatedItems(
id: Ident,
collective: CollectiveId
): ConnectionIO[Vector[ListItem]] =
RItemLink RItemLink
.findLinked(collective, id) .findLinked(collective, id)
.map(v => Nel.fromList(v.toList)) .map(v => Nel.fromList(v.toList))
@ -131,7 +134,8 @@ object QItem extends FtsSupport {
case Some(nel) => case Some(nel) =>
val expr = val expr =
ItemQuery.Expr.and(ValidItemStates, ItemQueryDsl.Q.itemIdsIn(nel.map(_.id))) ItemQuery.Expr.and(ValidItemStates, ItemQueryDsl.Q.itemIdsIn(nel.map(_.id)))
val account = AccountId(collective, Ident.unsafe("")) val account =
AccountInfo(collective, Ident.unsafe(""), Ident.unsafe(""), Ident.unsafe(""))
findItemsBase( findItemsBase(
Query.Fix(account, Some(expr), None), Query.Fix(account, Some(expr), None),
@ -159,7 +163,7 @@ object QItem extends FtsSupport {
noteMaxLen: Int, noteMaxLen: Int,
ftsTable: Option[RFtsResult.Table] ftsTable: Option[RFtsResult.Table]
): Select.Ordered = { ): Select.Ordered = {
val coll = q.account.collective val coll = q.account.collectiveId
Select( Select(
select( select(
@ -197,7 +201,9 @@ object QItem extends FtsSupport {
i.cid === coll &&? q.query.map(qs => queryCondFromExpr(today, coll, qs)) i.cid === coll &&? q.query.map(qs => queryCondFromExpr(today, coll, qs))
&& or( && or(
i.folder.isNull, i.folder.isNull,
i.folder.in(QFolder.findMemberFolderIds(q.account)) i.folder.in(
QFolder.findMemberFolderIds(q.account.collectiveId, q.account.userId)
)
) )
) )
).orderBy( ).orderBy(
@ -223,7 +229,7 @@ object QItem extends FtsSupport {
from.innerJoin(meta, meta.id === as.fileId) from.innerJoin(meta, meta.id === as.fileId)
} }
) )
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.limit(maxFiles) .limit(maxFiles)
def findFiles( def findFiles(
@ -288,12 +294,20 @@ object QItem extends FtsSupport {
.streamWithChunkSize(chunkSize) .streamWithChunkSize(chunkSize)
} }
def queryCondFromExpr(today: LocalDate, coll: Ident, q: ItemQuery.Expr): Condition = { def queryCondFromExpr(
today: LocalDate,
coll: CollectiveId,
q: ItemQuery.Expr
): Condition = {
val tables = Tables(i, org, pers0, pers1, equip, f, a, m, AttachCountTable("cta")) val tables = Tables(i, org, pers0, pers1, equip, f, a, m, AttachCountTable("cta"))
ItemQueryGenerator.fromExpr(today, tables, coll)(q) ItemQueryGenerator.fromExpr(today, tables, coll)(q)
} }
def queryCondition(today: LocalDate, coll: Ident, cond: Query.QueryCond): Condition = def queryCondition(
today: LocalDate,
coll: CollectiveId,
cond: Query.QueryCond
): Condition =
cond match { cond match {
case Query.QueryExpr(Some(expr)) => case Query.QueryExpr(Some(expr)) =>
queryCondFromExpr(today, coll, expr) queryCondFromExpr(today, coll, expr)
@ -340,7 +354,7 @@ object QItem extends FtsSupport {
.joinFtsIdOnly(i, ftsTable) .joinFtsIdOnly(i, ftsTable)
.withSelect(select(tag.category).append(countDistinct(i.id).as("num"))) .withSelect(select(tag.category).append(countDistinct(i.id).as("num")))
.changeFrom(_.prepend(tagFrom)) .changeFrom(_.prepend(tagFrom))
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.groupBy(tag.category) .groupBy(tag.category)
.build .build
.query[CategoryCount] .query[CategoryCount]
@ -348,7 +362,7 @@ object QItem extends FtsSupport {
for { for {
existing <- catCloud existing <- catCloud
allCats <- RTag.listCategories(q.fix.account.collective) allCats <- RTag.listCategories(q.fix.account.collectiveId)
other = allCats.diff(existing.flatMap(_.category)) other = allCats.diff(existing.flatMap(_.category))
} yield existing ++ other.map(n => CategoryCount(n.some, 0)) } yield existing ++ other.map(n => CategoryCount(n.some, 0))
} }
@ -366,7 +380,7 @@ object QItem extends FtsSupport {
.joinFtsIdOnly(i, ftsTable) .joinFtsIdOnly(i, ftsTable)
.withSelect(select(tag.all).append(countDistinct(i.id).as("num"))) .withSelect(select(tag.all).append(countDistinct(i.id).as("num")))
.changeFrom(_.prepend(tagFrom)) .changeFrom(_.prepend(tagFrom))
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.groupBy(tag.tid) .groupBy(tag.tid)
.build .build
.query[TagCount] .query[TagCount]
@ -376,7 +390,7 @@ object QItem extends FtsSupport {
// are not included they are fetched separately // are not included they are fetched separately
for { for {
existing <- tagCloud existing <- tagCloud
other <- RTag.findOthers(q.fix.account.collective, existing.map(_.tag.tagId)) other <- RTag.findOthers(q.fix.account.collectiveId, existing.map(_.tag.tagId))
} yield existing ++ other.map(TagCount(_, 0)) } yield existing ++ other.map(TagCount(_, 0))
} }
@ -386,7 +400,7 @@ object QItem extends FtsSupport {
findItemsBase(q.fix, today, 0, None).unwrap findItemsBase(q.fix, today, 0, None).unwrap
.joinFtsIdOnly(i, ftsTable) .joinFtsIdOnly(i, ftsTable)
.withSelect(Nel.of(count(i.id).as("num"))) .withSelect(Nel.of(count(i.id).as("num")))
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.build .build
.query[Int] .query[Int]
.unique .unique
@ -422,7 +436,7 @@ object QItem extends FtsSupport {
.joinFtsIdOnly(i, ftsTable) .joinFtsIdOnly(i, ftsTable)
.withSelect(select(idCol, nameCol).append(count(idCol).as("num"))) .withSelect(select(idCol, nameCol).append(count(idCol).as("num")))
.changeWhere(c => .changeWhere(c =>
c && fkCol.isNotNull && queryCondition(today, q.fix.account.collective, q.cond) c && fkCol.isNotNull && queryCondition(today, q.fix.account.collectiveId, q.cond)
) )
.groupBy(idCol, nameCol) .groupBy(idCol, nameCol)
.build .build
@ -437,7 +451,7 @@ object QItem extends FtsSupport {
.joinFtsIdOnly(i, ftsTable) .joinFtsIdOnly(i, ftsTable)
.withSelect(select(f.id, f.name, f.owner, fu.login).append(count(i.id).as("num"))) .withSelect(select(f.id, f.name, f.owner, fu.login).append(count(i.id).as("num")))
.changeFrom(_.innerJoin(fu, fu.uid === f.owner)) .changeFrom(_.innerJoin(fu, fu.uid === f.owner))
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.groupBy(f.id, f.name, f.owner, fu.login) .groupBy(f.id, f.name, f.owner, fu.login)
.build .build
.query[FolderCount] .query[FolderCount]
@ -455,7 +469,7 @@ object QItem extends FtsSupport {
val base = val base =
findItemsBase(q.fix, today, 0, None).unwrap findItemsBase(q.fix, today, 0, None).unwrap
.changeFrom(_.prepend(fieldJoin)) .changeFrom(_.prepend(fieldJoin))
.changeWhere(c => c && queryCondition(today, q.fix.account.collective, q.cond)) .changeWhere(c => c && queryCondition(today, q.fix.account.collectiveId, q.cond))
.ftsCondition(i, ftsTable) .ftsCondition(i, ftsTable)
.groupBy(GroupBy(cf.all)) .groupBy(GroupBy(cf.all))
@ -507,7 +521,7 @@ object QItem extends FtsSupport {
* implemented by running an additional query per item. * implemented by running an additional query per item.
*/ */
def findItemsWithTags( def findItemsWithTags(
collective: Ident, collective: CollectiveId,
search: Stream[ConnectionIO, ListItem] search: Stream[ConnectionIO, ListItem]
): Stream[ConnectionIO, ListItemWithTags] = { ): Stream[ConnectionIO, ListItemWithTags] = {
def findTag( def findTag(
@ -555,7 +569,9 @@ object QItem extends FtsSupport {
a.itemId === item a.itemId === item
).build.query[AttachmentLight].to[List] ).build.query[AttachmentLight].to[List]
def delete[F[_]: Sync](store: Store[F])(itemId: Ident, collective: Ident): F[Int] = def delete[F[_]: Sync](
store: Store[F]
)(itemId: Ident, collective: CollectiveId): F[Int] =
for { for {
rn <- QAttachment.deleteItemAttachments(store)(itemId, collective) rn <- QAttachment.deleteItemAttachments(store)(itemId, collective)
tn <- store.transact(RTagItem.deleteItemTags(itemId)) tn <- store.transact(RTagItem.deleteItemTags(itemId))
@ -607,7 +623,7 @@ object QItem extends FtsSupport {
def findByChecksum( def findByChecksum(
checksum: String, checksum: String,
collective: Ident, collective: CollectiveId,
excludeFileMeta: Set[FileKey] excludeFileMeta: Set[FileKey]
): ConnectionIO[Vector[RItem]] = { ): ConnectionIO[Vector[RItem]] = {
val qq = findByChecksumQuery(checksum, collective, excludeFileMeta).build val qq = findByChecksumQuery(checksum, collective, excludeFileMeta).build
@ -617,7 +633,7 @@ object QItem extends FtsSupport {
def findByChecksumQuery( def findByChecksumQuery(
checksum: String, checksum: String,
collective: Ident, collective: CollectiveId,
excludeFileMeta: Set[FileKey] excludeFileMeta: Set[FileKey]
): Select = { ): Select = {
val m1 = RFileMeta.as("m1") val m1 = RFileMeta.as("m1")
@ -657,7 +673,7 @@ object QItem extends FtsSupport {
language: Language language: Language
) )
def allNameAndNotes( def allNameAndNotes(
coll: Option[Ident], coll: Option[CollectiveId],
itemIds: Option[Nel[Ident]], itemIds: Option[Nel[Ident]],
chunkSize: Int chunkSize: Int
): Stream[ConnectionIO, NameAndNotes] = { ): Stream[ConnectionIO, NameAndNotes] = {
@ -677,7 +693,7 @@ object QItem extends FtsSupport {
} }
def findAllNewesFirst( def findAllNewesFirst(
collective: Ident, collective: CollectiveId,
chunkSize: Int, chunkSize: Int,
limit: Batch limit: Batch
): Stream[ConnectionIO, Ident] = { ): Stream[ConnectionIO, Ident] = {
@ -691,7 +707,7 @@ object QItem extends FtsSupport {
} }
def resolveTextAndTag( def resolveTextAndTag(
collective: Ident, collective: CollectiveId,
itemId: Ident, itemId: Ident,
tagCategory: String, tagCategory: String,
maxLen: Int, maxLen: Int,
@ -724,7 +740,7 @@ object QItem extends FtsSupport {
} }
def resolveTextAndCorrOrg( def resolveTextAndCorrOrg(
collective: Ident, collective: CollectiveId,
itemId: Ident, itemId: Ident,
maxLen: Int, maxLen: Int,
pageSep: String pageSep: String
@ -741,7 +757,7 @@ object QItem extends FtsSupport {
} }
def resolveTextAndCorrPerson( def resolveTextAndCorrPerson(
collective: Ident, collective: CollectiveId,
itemId: Ident, itemId: Ident,
maxLen: Int, maxLen: Int,
pageSep: String pageSep: String
@ -758,7 +774,7 @@ object QItem extends FtsSupport {
} }
def resolveTextAndConcPerson( def resolveTextAndConcPerson(
collective: Ident, collective: CollectiveId,
itemId: Ident, itemId: Ident,
maxLen: Int, maxLen: Int,
pageSep: String pageSep: String
@ -775,7 +791,7 @@ object QItem extends FtsSupport {
} }
def resolveTextAndConcEquip( def resolveTextAndConcEquip(
collective: Ident, collective: CollectiveId,
itemId: Ident, itemId: Ident,
maxLen: Int, maxLen: Int,
pageSep: String pageSep: String
@ -797,12 +813,12 @@ object QItem extends FtsSupport {
m.content.s m.content.s
} else substring(m.content.s, 0, maxLen).s } else substring(m.content.s, 0, maxLen).s
private def readTextAndTag(collective: Ident, itemId: Ident, pageSep: String)( private def readTextAndTag(collective: CollectiveId, itemId: Ident, pageSep: String)(
q: Select q: Select
): ConnectionIO[TextAndTag] = ): ConnectionIO[TextAndTag] =
for { for {
_ <- logger.trace( _ <- logger.trace(
s"query: $q (${itemId.id}, ${collective.id})" s"query: $q (${itemId.id}, ${collective.value})"
) )
texts <- q.build.query[(String, Option[TextAndTag.TagName])].to[List] texts <- q.build.query[(String, Option[TextAndTag.TagName])].to[List]
_ <- logger.trace( _ <- logger.trace(

View File

@ -21,7 +21,7 @@ object QLogin {
private[this] val logger = docspell.logging.getLogger[ConnectionIO] private[this] val logger = docspell.logging.getLogger[ConnectionIO]
case class Data( case class Data(
account: AccountId, account: AccountInfo,
password: Password, password: Password,
collectiveState: CollectiveState, collectiveState: CollectiveState,
userState: UserState, userState: UserState,
@ -35,7 +35,16 @@ object QLogin {
val coll = RCollective.as("c") val coll = RCollective.as("c")
val sql = val sql =
Select( Select(
select(user.cid, user.login, user.password, coll.state, user.state, user.source), select(
coll.id,
coll.name,
user.uid,
user.login,
user.password,
coll.state,
user.state,
user.source
),
from(user).innerJoin(coll, user.cid === coll.id), from(user).innerJoin(coll, user.cid === coll.id),
where(user, coll) where(user, coll)
).build ).build
@ -44,7 +53,7 @@ object QLogin {
} }
def findUser(acc: AccountId): ConnectionIO[Option[Data]] = def findUser(acc: AccountId): ConnectionIO[Option[Data]] =
findUser0((user, _) => user.login === acc.user && user.cid === acc.collective) findUser0((user, coll) => user.login === acc.user && coll.name === acc.collective)
def findUser(userId: Ident): ConnectionIO[Option[Data]] = def findUser(userId: Ident): ConnectionIO[Option[Data]] =
findUser0((user, _) => user.uid === userId) findUser0((user, _) => user.uid === userId)

View File

@ -23,21 +23,27 @@ object QMails {
private val mailitem = RSentMailItem.as("mi") private val mailitem = RSentMailItem.as("mi")
private val user = RUser.as("u") private val user = RUser.as("u")
def delete(coll: Ident, mailId: Ident): ConnectionIO[Int] = def delete(coll: CollectiveId, mailId: Ident): ConnectionIO[Int] =
(for { (for {
m <- OptionT(findMail(coll, mailId)) m <- OptionT(findMail(coll, mailId))
k <- OptionT.liftF(RSentMailItem.deleteMail(mailId)) k <- OptionT.liftF(RSentMailItem.deleteMail(mailId))
n <- OptionT.liftF(RSentMail.delete(m._1.id)) n <- OptionT.liftF(RSentMail.delete(m._1.id))
} yield k + n).getOrElse(0) } yield k + n).getOrElse(0)
def findMail(coll: Ident, mailId: Ident): ConnectionIO[Option[(RSentMail, Ident)]] = def findMail(
coll: CollectiveId,
mailId: Ident
): ConnectionIO[Option[(RSentMail, Ident)]] =
partialFind partialFind
.where(smail.id === mailId && item.cid === coll) .where(smail.id === mailId && item.cid === coll)
.build .build
.query[(RSentMail, Ident)] .query[(RSentMail, Ident)]
.option .option
def findMails(coll: Ident, itemId: Ident): ConnectionIO[Vector[(RSentMail, Ident)]] = def findMails(
coll: CollectiveId,
itemId: Ident
): ConnectionIO[Vector[(RSentMail, Ident)]] =
partialFind partialFind
.where(mailitem.itemId === itemId && item.cid === coll) .where(mailitem.itemId === itemId && item.cid === coll)
.orderBy(smail.created.desc) .orderBy(smail.created.desc)
@ -53,5 +59,4 @@ object QMails {
.innerJoin(item, mailitem.itemId === item.id) .innerJoin(item, mailitem.itemId === item.id)
.innerJoin(user, user.uid === smail.uid) .innerJoin(user, user.uid === smail.uid)
) )
} }

View File

@ -26,7 +26,7 @@ object QNotification {
def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] = def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] =
for { for {
hooks <- listHooks(event.account.collective, event.eventType) hooks <- listHooks(event.account.collectiveId, event.eventType)
chs <- hooks.traverse(h => chs <- hooks.traverse(h =>
listChannels(h.id) listChannels(h.id)
.flatMap(_.flatTraverse(hc => readHookChannel(h.uid, hc))) .flatMap(_.flatTraverse(hc => readHookChannel(h.uid, hc)))
@ -42,7 +42,7 @@ object QNotification {
) )
def listHooks( def listHooks(
collective: Ident, collective: CollectiveId,
eventType: EventType eventType: EventType
): ConnectionIO[Vector[RNotificationHook]] = ): ConnectionIO[Vector[RNotificationHook]] =
run( run(

View File

@ -25,7 +25,7 @@ object QOrganization {
private val org = ROrganization.as("o") private val org = ROrganization.as("o")
def findOrgAndContact( def findOrgAndContact(
coll: Ident, coll: CollectiveId,
query: Option[String], query: Option[String],
order: ROrganization.Table => Nel[OrderBy] order: ROrganization.Table => Nel[OrderBy]
): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = { ): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = {
@ -50,7 +50,7 @@ object QOrganization {
} }
def getOrgAndContact( def getOrgAndContact(
coll: Ident, coll: CollectiveId,
orgId: Ident orgId: Ident
): ConnectionIO[Option[(ROrganization, Vector[RContact])]] = { ): ConnectionIO[Option[(ROrganization, Vector[RContact])]] = {
val sql = run( val sql = run(
@ -72,7 +72,7 @@ object QOrganization {
} }
def findPersonAndContact( def findPersonAndContact(
coll: Ident, coll: CollectiveId,
query: Option[String], query: Option[String],
order: (RPerson.Table, ROrganization.Table) => Nel[OrderBy] order: (RPerson.Table, ROrganization.Table) => Nel[OrderBy]
): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = { ): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = {
@ -99,7 +99,7 @@ object QOrganization {
} }
def getPersonAndContact( def getPersonAndContact(
coll: Ident, coll: CollectiveId,
persId: Ident persId: Ident
): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = { ): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = {
val sql = val sql =
@ -125,7 +125,7 @@ object QOrganization {
} }
def findPersonByContact( def findPersonByContact(
coll: Ident, coll: CollectiveId,
value: String, value: String,
ck: Option[ContactKind], ck: Option[ContactKind],
use: Option[Nel[PersonUse]] use: Option[Nel[PersonUse]]
@ -141,7 +141,7 @@ object QOrganization {
def addOrg[F[_]]( def addOrg[F[_]](
org: ROrganization, org: ROrganization,
contacts: Seq[RContact], contacts: Seq[RContact],
cid: Ident cid: CollectiveId
): Store[F] => F[AddResult] = { ): Store[F] => F[AddResult] = {
val insert = for { val insert = for {
n <- ROrganization.insert(org) n <- ROrganization.insert(org)
@ -156,7 +156,7 @@ object QOrganization {
def addPerson[F[_]]( def addPerson[F[_]](
person: RPerson, person: RPerson,
contacts: Seq[RContact], contacts: Seq[RContact],
cid: Ident cid: CollectiveId
): Store[F] => F[AddResult] = { ): Store[F] => F[AddResult] = {
val insert = for { val insert = for {
n <- RPerson.insert(person) n <- RPerson.insert(person)
@ -171,7 +171,7 @@ object QOrganization {
def updateOrg[F[_]]( def updateOrg[F[_]](
org: ROrganization, org: ROrganization,
contacts: Seq[RContact], contacts: Seq[RContact],
cid: Ident cid: CollectiveId
): Store[F] => F[AddResult] = { ): Store[F] => F[AddResult] = {
val insert = for { val insert = for {
n <- ROrganization.update(org) n <- ROrganization.update(org)
@ -187,7 +187,7 @@ object QOrganization {
def updatePerson[F[_]]( def updatePerson[F[_]](
person: RPerson, person: RPerson,
contacts: Seq[RContact], contacts: Seq[RContact],
cid: Ident cid: CollectiveId
): Store[F] => F[AddResult] = { ): Store[F] => F[AddResult] = {
val insert = for { val insert = for {
n <- RPerson.update(person) n <- RPerson.update(person)
@ -200,7 +200,7 @@ object QOrganization {
store => store.add(insert, exists) store => store.add(insert, exists)
} }
def deleteOrg(orgId: Ident, collective: Ident): ConnectionIO[Int] = def deleteOrg(orgId: Ident, collective: CollectiveId): ConnectionIO[Int] =
for { for {
n0 <- RItem.removeCorrOrg(collective, orgId) n0 <- RItem.removeCorrOrg(collective, orgId)
n1 <- RContact.deleteOrg(orgId) n1 <- RContact.deleteOrg(orgId)
@ -208,7 +208,7 @@ object QOrganization {
n3 <- ROrganization.delete(orgId, collective) n3 <- ROrganization.delete(orgId, collective)
} yield n0 + n1 + n2 + n3 } yield n0 + n1 + n2 + n3
def deletePerson(personId: Ident, collective: Ident): ConnectionIO[Int] = def deletePerson(personId: Ident, collective: CollectiveId): ConnectionIO[Int] =
for { for {
n0 <- RItem.removeCorrPerson(collective, personId) n0 <- RItem.removeCorrPerson(collective, personId)
n1 <- RItem.removeConcPerson(collective, personId) n1 <- RItem.removeConcPerson(collective, personId)

View File

@ -24,40 +24,35 @@ object QUser {
shares: Int shares: Int
) )
def getUserData(accountId: AccountId): ConnectionIO[UserData] = { def getUserData(cid: CollectiveId, uid: Ident): ConnectionIO[UserData] = {
val folder = RFolder.as("f") val folder = RFolder.as("f")
val mail = RSentMail.as("m") val mail = RSentMail.as("m")
val mitem = RSentMailItem.as("mi") val mitem = RSentMailItem.as("mi")
val user = RUser.as("u")
val share = RShare.as("s") val share = RShare.as("s")
for { for {
uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe("")))
folders <- run( folders <- run(
select(folder.name), select(folder.name),
from(folder), from(folder),
folder.owner === uid && folder.collective === accountId.collective folder.owner === uid && folder.collective === cid
).query[Ident].to[List] ).query[Ident].to[List]
mails <- run( mails <- run(
select(count(mail.id)), select(count(mail.id)),
from(mail) from(mail)
.innerJoin(mitem, mail.id === mitem.sentMailId) .innerJoin(mitem, mail.id === mitem.sentMailId),
.innerJoin(user, user.uid === mail.uid), mail.uid === uid
user.login === accountId.user && user.cid === accountId.collective
).query[Int].unique ).query[Int].unique
shares <- run( shares <- run(
select(count(share.id)), select(count(share.id)),
from(share) from(share),
.innerJoin(user, user.uid === share.userId), share.userId === uid
user.login === accountId.user && user.cid === accountId.collective
).query[Int].unique ).query[Int].unique
} yield UserData(folders, mails, shares) } yield UserData(folders, mails, shares)
} }
def deleteUserAndData(accountId: AccountId): ConnectionIO[Int] = def deleteUserAndData(uid: Ident): ConnectionIO[Int] =
for { for {
uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe(""))) _ <- logger.info(s"Remove user ${uid.id}")
_ <- logger.info(s"Remove user ${accountId.asString} (uid=${uid.id})")
n1 <- deleteUserFolders(uid) n1 <- deleteUserFolders(uid)
@ -125,8 +120,4 @@ object QUser {
n2 <- DML.delete(imap, imap.uid === uid) n2 <- DML.delete(imap, imap.uid === uid)
} yield n1 + n2 } yield n1 + n2
} }
private def loadUserId(id: AccountId): ConnectionIO[Option[Ident]] =
RUser.findIdByAccount(id)
} }

View File

@ -58,7 +58,7 @@ object Query {
Query(fix, QueryExpr(None)) Query(fix, QueryExpr(None))
case class Fix( case class Fix(
account: AccountId, account: AccountInfo,
query: Option[ItemQuery.Expr], query: Option[ItemQuery.Expr],
order: Option[OrderSelect => OrderBy] order: Option[OrderSelect => OrderBy]
) { ) {
@ -87,7 +87,7 @@ object Query {
QueryExpr(Some(q)) QueryExpr(Some(q))
} }
def all(account: AccountId): Query = def all(account: AccountInfo): Query =
Query(Fix(account, None, None), QueryExpr(None)) Query(Fix(account, None, None), QueryExpr(None))
} }

View File

@ -11,7 +11,7 @@ import cats.syntax.all._
import fs2.Stream import fs2.Stream
import docspell.addons.AddonTriggerType import docspell.addons.AddonTriggerType
import docspell.common.{Ident, Timestamp} import docspell.common.{CollectiveId, Ident, Timestamp}
import docspell.store.qb.DSL._ import docspell.store.qb.DSL._
import docspell.store.qb._ import docspell.store.qb._
@ -26,7 +26,7 @@ case class AddonRunConfigData(
object AddonRunConfigData { object AddonRunConfigData {
def findAll( def findAll(
cid: Ident, cid: CollectiveId,
enabled: Option[Boolean] = None, enabled: Option[Boolean] = None,
trigger: Set[AddonTriggerType] = Set.empty, trigger: Set[AddonTriggerType] = Set.empty,
configIds: Set[Ident] = Set.empty configIds: Set[Ident] = Set.empty
@ -88,7 +88,7 @@ object AddonRunConfigData {
} yield n1 + tts.sum + tas.sum } yield n1 + tts.sum + tas.sum
def findEnabledRef( def findEnabledRef(
cid: Ident, cid: CollectiveId,
taskId: Ident taskId: Ident
): ConnectionIO[List[(RAddonArchive, RAddonRunConfigAddon)]] = { ): ConnectionIO[List[(RAddonArchive, RAddonRunConfigAddon)]] = {
val run = RAddonRunConfig.as("run") val run = RAddonRunConfig.as("run")
@ -108,7 +108,7 @@ object AddonRunConfigData {
} }
def findEnabledRefs( def findEnabledRefs(
cid: Ident, cid: CollectiveId,
trigger: AddonTriggerType, trigger: AddonTriggerType,
addonTaskIds: Set[Ident] addonTaskIds: Set[Ident]
): Stream[ConnectionIO, (RAddonRunConfig, List[(RAddonArchive, String)])] = { ): Stream[ConnectionIO, (RAddonRunConfig, List[(RAddonArchive, String)])] = {

View File

@ -46,7 +46,7 @@ object AddonRunConfigResolved {
def findById( def findById(
configId: Ident, configId: Ident,
collective: Ident, collective: CollectiveId,
enabled: Option[Boolean] enabled: Option[Boolean]
): ConnectionIO[Option[AddonRunConfigResolved]] = ): ConnectionIO[Option[AddonRunConfigResolved]] =
(for { (for {
@ -56,7 +56,7 @@ object AddonRunConfigResolved {
} yield AddonRunConfigResolved(cfg, refs, tri)).value } yield AddonRunConfigResolved(cfg, refs, tri)).value
def findAllForCollective( def findAllForCollective(
cid: Ident, cid: CollectiveId,
enabled: Option[Boolean], enabled: Option[Boolean],
trigger: Set[AddonTriggerType], trigger: Set[AddonTriggerType],
configIds: Set[Ident] configIds: Set[Ident]

View File

@ -21,7 +21,7 @@ import io.circe.{Decoder, Encoder}
final case class RAddonArchive( final case class RAddonArchive(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
fileId: FileKey, fileId: FileKey,
originalUrl: Option[LenientUri], originalUrl: Option[LenientUri],
name: String, name: String,
@ -32,7 +32,7 @@ final case class RAddonArchive(
) { ) {
def nameAndVersion: String = def nameAndVersion: String =
s"${name}-${version}" s"$name-$version"
def isUnchanged(meta: AddonMeta): Boolean = def isUnchanged(meta: AddonMeta): Boolean =
name == meta.meta.name && name == meta.meta.name &&
@ -60,7 +60,7 @@ object RAddonArchive {
val tableName = "addon_archive" val tableName = "addon_archive"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val fileId = Column[FileKey]("file_id", this) val fileId = Column[FileKey]("file_id", this)
val originalUrl = Column[LenientUri]("original_url", this) val originalUrl = Column[LenientUri]("original_url", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
@ -85,7 +85,7 @@ object RAddonArchive {
def apply( def apply(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
fileId: FileKey, fileId: FileKey,
originalUrl: Option[LenientUri], originalUrl: Option[LenientUri],
meta: AddonMeta, meta: AddonMeta,
@ -116,14 +116,14 @@ object RAddonArchive {
else DML.insert(T, T.all, values) else DML.insert(T, T.all, values)
} }
def existsByUrl(cid: Ident, url: LenientUri): ConnectionIO[Boolean] = def existsByUrl(cid: CollectiveId, url: LenientUri): ConnectionIO[Boolean] =
Select( Select(
select(count(T.id)), select(count(T.id)),
from(T), from(T),
T.cid === cid && T.originalUrl === url T.cid === cid && T.originalUrl === url
).build.query[Int].unique.map(_ > 0) ).build.query[Int].unique.map(_ > 0)
def findByUrl(cid: Ident, url: LenientUri): ConnectionIO[Option[RAddonArchive]] = def findByUrl(cid: CollectiveId, url: LenientUri): ConnectionIO[Option[RAddonArchive]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
@ -131,7 +131,7 @@ object RAddonArchive {
).build.query[RAddonArchive].option ).build.query[RAddonArchive].option
def findByNameAndVersion( def findByNameAndVersion(
cid: Ident, cid: CollectiveId,
name: String, name: String,
version: String version: String
): ConnectionIO[Option[RAddonArchive]] = ): ConnectionIO[Option[RAddonArchive]] =
@ -141,14 +141,17 @@ object RAddonArchive {
T.cid === cid && T.name === name && T.version === version T.cid === cid && T.name === name && T.version === version
).build.query[RAddonArchive].option ).build.query[RAddonArchive].option
def findById(cid: Ident, id: Ident): ConnectionIO[Option[RAddonArchive]] = def findById(cid: CollectiveId, id: Ident): ConnectionIO[Option[RAddonArchive]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
T.cid === cid && T.id === id T.cid === cid && T.id === id
).build.query[RAddonArchive].option ).build.query[RAddonArchive].option
def findByIds(cid: Ident, ids: NonEmptyList[Ident]): ConnectionIO[List[RAddonArchive]] = def findByIds(
cid: CollectiveId,
ids: NonEmptyList[Ident]
): ConnectionIO[List[RAddonArchive]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
@ -169,14 +172,14 @@ object RAddonArchive {
) )
) )
def listAll(cid: Ident): ConnectionIO[List[RAddonArchive]] = def listAll(cid: CollectiveId): ConnectionIO[List[RAddonArchive]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
T.cid === cid T.cid === cid
).orderBy(T.name.asc).build.query[RAddonArchive].to[List] ).orderBy(T.name.asc).build.query[RAddonArchive].to[List]
def deleteById(cid: Ident, id: Ident): ConnectionIO[Int] = def deleteById(cid: CollectiveId, id: Ident): ConnectionIO[Int] =
DML.delete(T, T.cid === cid && T.id === id) DML.delete(T, T.cid === cid && T.id === id)
implicit val jsonDecoder: Decoder[RAddonArchive] = deriveDecoder implicit val jsonDecoder: Decoder[RAddonArchive] = deriveDecoder

View File

@ -18,7 +18,7 @@ import doobie.implicits._
final case class RAddonRunConfig( final case class RAddonRunConfig(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
userId: Option[Ident], userId: Option[Ident],
name: String, name: String,
enabled: Boolean, enabled: Boolean,
@ -30,7 +30,7 @@ object RAddonRunConfig {
val tableName = "addon_run_config" val tableName = "addon_run_config"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val userId = Column[Ident]("user_id", this) val userId = Column[Ident]("user_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val enabled = Column[Boolean]("enabled", this) val enabled = Column[Boolean]("enabled", this)
@ -61,13 +61,13 @@ object RAddonRunConfig {
) )
) )
def findById(cid: Ident, id: Ident): ConnectionIO[Option[RAddonRunConfig]] = def findById(cid: CollectiveId, id: Ident): ConnectionIO[Option[RAddonRunConfig]] =
Select(select(T.all), from(T), T.cid === cid && T.id === id).build Select(select(T.all), from(T), T.cid === cid && T.id === id).build
.query[RAddonRunConfig] .query[RAddonRunConfig]
.option .option
def findByCollective( def findByCollective(
cid: Ident, cid: CollectiveId,
enabled: Option[Boolean], enabled: Option[Boolean],
trigger: Set[AddonTriggerType], trigger: Set[AddonTriggerType],
configIds: Set[Ident] configIds: Set[Ident]
@ -94,6 +94,6 @@ object RAddonRunConfig {
selectConfigs.build.query[RAddonRunConfig].to[List] selectConfigs.build.query[RAddonRunConfig].to[List]
} }
def deleteById(cid: Ident, id: Ident): ConnectionIO[Int] = def deleteById(cid: CollectiveId, id: Ident): ConnectionIO[Int] =
DML.delete(T, T.cid === cid && T.id === id) DML.delete(T, T.cid === cid && T.id === id)
} }

View File

@ -125,7 +125,7 @@ object RAttachment {
def updateName( def updateName(
attachId: Ident, attachId: Ident,
collective: Ident, collective: CollectiveId,
aname: Option[String] aname: Option[String]
): ConnectionIO[Int] = { ): ConnectionIO[Int] = {
val update = DML.update(T, T.id === attachId, DML.set(T.name.setTo(aname))) val update = DML.update(T, T.id === attachId, DML.set(T.name.setTo(aname)))
@ -137,7 +137,7 @@ object RAttachment {
def findByIdAndCollective( def findByIdAndCollective(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Option[RAttachment]] = { ): ConnectionIO[Option[RAttachment]] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
val i = RItem.as("i") val i = RItem.as("i")
@ -153,7 +153,7 @@ object RAttachment {
def existsByIdAndCollective( def existsByIdAndCollective(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Boolean] = { ): ConnectionIO[Boolean] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
val i = RItem.as("i") val i = RItem.as("i")
@ -167,7 +167,7 @@ object RAttachment {
def findByItemAndCollective( def findByItemAndCollective(
id: Ident, id: Ident,
coll: Ident coll: CollectiveId
): ConnectionIO[Vector[RAttachment]] = { ): ConnectionIO[Vector[RAttachment]] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
val i = RItem.as("i") val i = RItem.as("i")
@ -181,7 +181,7 @@ object RAttachment {
def findByItemCollectiveSource( def findByItemCollectiveSource(
id: Ident, id: Ident,
coll: Ident, coll: CollectiveId,
fileIds: NonEmptyList[FileKey] fileIds: NonEmptyList[FileKey]
): ConnectionIO[Vector[RAttachment]] = { ): ConnectionIO[Vector[RAttachment]] = {
val i = RItem.as("i") val i = RItem.as("i")
@ -202,7 +202,7 @@ object RAttachment {
def findByItemAndCollectiveWithMeta( def findByItemAndCollectiveWithMeta(
id: Ident, id: Ident,
coll: Ident coll: CollectiveId
): ConnectionIO[Vector[(RAttachment, RFileMeta)]] = { ): ConnectionIO[Vector[(RAttachment, RFileMeta)]] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
val m = RFileMeta.as("m") val m = RFileMeta.as("m")
@ -250,7 +250,7 @@ object RAttachment {
} }
def findAll( def findAll(
coll: Option[Ident], coll: Option[CollectiveId],
chunkSize: Int chunkSize: Int
): Stream[ConnectionIO, RAttachment] = { ): Stream[ConnectionIO, RAttachment] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
@ -283,7 +283,7 @@ object RAttachment {
} }
def findWithoutPreview( def findWithoutPreview(
coll: Option[Ident], coll: Option[CollectiveId],
chunkSize: Int chunkSize: Int
): Stream[ConnectionIO, RAttachment] = { ): Stream[ConnectionIO, RAttachment] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
@ -299,7 +299,7 @@ object RAttachment {
} }
def findNonConvertedPdf( def findNonConvertedPdf(
coll: Option[Ident], coll: Option[CollectiveId],
chunkSize: Int chunkSize: Int
): Stream[ConnectionIO, RAttachment] = { ): Stream[ConnectionIO, RAttachment] = {
val pdfType = "application/pdf%" val pdfType = "application/pdf%"
@ -322,7 +322,7 @@ object RAttachment {
def filterAttachments( def filterAttachments(
attachments: NonEmptyList[Ident], attachments: NonEmptyList[Ident],
coll: Ident coll: CollectiveId
): ConnectionIO[Vector[Ident]] = { ): ConnectionIO[Vector[Ident]] = {
val a = RAttachment.as("a") val a = RAttachment.as("a")
val i = RItem.as("i") val i = RItem.as("i")

View File

@ -64,7 +64,7 @@ object RAttachmentArchive {
def findByIdAndCollective( def findByIdAndCollective(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Option[RAttachmentArchive]] = { ): ConnectionIO[Option[RAttachmentArchive]] = {
val b = RAttachment.as("b") val b = RAttachment.as("b")
val a = RAttachmentArchive.as("a") val a = RAttachmentArchive.as("a")
@ -81,7 +81,7 @@ object RAttachmentArchive {
def findByMessageIdAndCollective( def findByMessageIdAndCollective(
messageIds: NonEmptyList[String], messageIds: NonEmptyList[String],
collective: Ident collective: CollectiveId
): ConnectionIO[Vector[RAttachmentArchive]] = { ): ConnectionIO[Vector[RAttachmentArchive]] = {
val b = RAttachment.as("b") val b = RAttachment.as("b")
val a = RAttachmentArchive.as("a") val a = RAttachmentArchive.as("a")

View File

@ -70,7 +70,7 @@ object RAttachmentPreview {
def findByIdAndCollective( def findByIdAndCollective(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Option[RAttachmentPreview]] = { ): ConnectionIO[Option[RAttachmentPreview]] = {
val b = RAttachment.as("b") val b = RAttachment.as("b")
val a = RAttachmentPreview.as("a") val a = RAttachmentPreview.as("a")
@ -98,7 +98,7 @@ object RAttachmentPreview {
def findByItemAndCollective( def findByItemAndCollective(
itemId: Ident, itemId: Ident,
coll: Ident coll: CollectiveId
): ConnectionIO[Option[RAttachmentPreview]] = { ): ConnectionIO[Option[RAttachmentPreview]] = {
val s = RAttachmentPreview.as("s") val s = RAttachmentPreview.as("s")
val a = RAttachment.as("a") val a = RAttachment.as("a")

View File

@ -71,7 +71,7 @@ object RAttachmentSource {
def findByIdAndCollective( def findByIdAndCollective(
attachId: Ident, attachId: Ident,
collective: Ident collective: CollectiveId
): ConnectionIO[Option[RAttachmentSource]] = { ): ConnectionIO[Option[RAttachmentSource]] = {
val b = RAttachment.as("b") val b = RAttachment.as("b")
val a = RAttachmentSource.as("a") val a = RAttachmentSource.as("a")

View File

@ -19,7 +19,7 @@ import doobie.implicits._
final case class RClassifierModel( final case class RClassifierModel(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
name: String, name: String,
fileId: FileKey, fileId: FileKey,
created: Timestamp created: Timestamp
@ -28,7 +28,7 @@ final case class RClassifierModel(
object RClassifierModel { object RClassifierModel {
def createNew[F[_]: Sync]( def createNew[F[_]: Sync](
cid: Ident, cid: CollectiveId,
name: String, name: String,
fileId: FileKey fileId: FileKey
): F[RClassifierModel] = ): F[RClassifierModel] =
@ -41,7 +41,7 @@ object RClassifierModel {
val tableName = "classifier_model" val tableName = "classifier_model"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val fileId = Column[FileKey]("file_id", this) val fileId = Column[FileKey]("file_id", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -61,7 +61,7 @@ object RClassifierModel {
fr"${v.id},${v.cid},${v.name},${v.fileId},${v.created}" fr"${v.id},${v.cid},${v.name},${v.fileId},${v.created}"
) )
def updateFile(coll: Ident, name: String, fid: FileKey): ConnectionIO[Int] = def updateFile(coll: CollectiveId, name: String, fid: FileKey): ConnectionIO[Int] =
for { for {
now <- Timestamp.current[ConnectionIO] now <- Timestamp.current[ConnectionIO]
n <- DML.update( n <- DML.update(
@ -85,13 +85,16 @@ object RClassifierModel {
0.pure[ConnectionIO] 0.pure[ConnectionIO]
} }
def findByName(cid: Ident, name: String): ConnectionIO[Option[RClassifierModel]] = def findByName(
cid: CollectiveId,
name: String
): ConnectionIO[Option[RClassifierModel]] =
Select(select(T.all), from(T), T.cid === cid && T.name === name).build Select(select(T.all), from(T), T.cid === cid && T.name === name).build
.query[RClassifierModel] .query[RClassifierModel]
.option .option
def findAllByName( def findAllByName(
cid: Ident, cid: CollectiveId,
names: NonEmptyList[String] names: NonEmptyList[String]
): ConnectionIO[List[RClassifierModel]] = ): ConnectionIO[List[RClassifierModel]] =
Select(select(T.all), from(T), T.cid === cid && T.name.in(names)).build Select(select(T.all), from(T), T.cid === cid && T.name.in(names)).build
@ -99,7 +102,7 @@ object RClassifierModel {
.to[List] .to[List]
def findAllByQuery( def findAllByQuery(
cid: Ident, cid: CollectiveId,
nameQuery: String nameQuery: String
): ConnectionIO[List[RClassifierModel]] = ): ConnectionIO[List[RClassifierModel]] =
Select(select(T.all), from(T), T.cid === cid && T.name.like(nameQuery)).build Select(select(T.all), from(T), T.cid === cid && T.name.like(nameQuery)).build

View File

@ -18,7 +18,7 @@ import doobie._
import doobie.implicits._ import doobie.implicits._
case class RClassifierSetting( case class RClassifierSetting(
cid: Ident, cid: CollectiveId,
schedule: CalEvent, schedule: CalEvent,
itemCount: Int, itemCount: Int,
created: Timestamp, created: Timestamp,
@ -43,7 +43,7 @@ object RClassifierSetting {
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "classifier_setting" val tableName = "classifier_setting"
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val schedule = Column[CalEvent]("schedule", this) val schedule = Column[CalEvent]("schedule", this)
val itemCount = Column[Int]("item_count", this) val itemCount = Column[Int]("item_count", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -79,19 +79,19 @@ object RClassifierSetting {
n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO]
} yield n1 + n2 } yield n1 + n2
def findById(id: Ident): ConnectionIO[Option[RClassifierSetting]] = { def findById(id: CollectiveId): ConnectionIO[Option[RClassifierSetting]] = {
val sql = run(select(T.all), from(T), T.cid === id) val sql = run(select(T.all), from(T), T.cid === id)
sql.query[RClassifierSetting].option sql.query[RClassifierSetting].option
} }
def delete(coll: Ident): ConnectionIO[Int] = def delete(coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.cid === coll) DML.delete(T, T.cid === coll)
/** Finds tag categories that exist and match the classifier setting. If the setting /** Finds tag categories that exist and match the classifier setting. If the setting
* contains a black list, they are removed from the existing categories. If it is a * contains a black list, they are removed from the existing categories. If it is a
* whitelist, the intersection is returned. * whitelist, the intersection is returned.
*/ */
def getActiveCategories(coll: Ident): ConnectionIO[List[String]] = def getActiveCategories(coll: CollectiveId): ConnectionIO[List[String]] =
(for { (for {
sett <- OptionT(findById(coll)) sett <- OptionT(findById(coll))
cats <- OptionT.liftF(RTag.listCategories(coll)) cats <- OptionT.liftF(RTag.listCategories(coll))
@ -106,7 +106,7 @@ object RClassifierSetting {
/** Checks the json array of tag categories and removes those that are not present /** Checks the json array of tag categories and removes those that are not present
* anymore. * anymore.
*/ */
def fixCategoryList(coll: Ident): ConnectionIO[Int] = def fixCategoryList(coll: CollectiveId): ConnectionIO[Int] =
(for { (for {
sett <- OptionT(findById(coll)) sett <- OptionT(findById(coll))
cats <- OptionT.liftF(RTag.listCategories(coll)) cats <- OptionT.liftF(RTag.listCategories(coll))
@ -131,7 +131,7 @@ object RClassifierSetting {
categories.nonEmpty categories.nonEmpty
} }
def toRecord(coll: Ident, created: Timestamp): RClassifierSetting = def toRecord(coll: CollectiveId, created: Timestamp): RClassifierSetting =
RClassifierSetting( RClassifierSetting(
coll, coll,
schedule, schedule,
@ -145,5 +145,4 @@ object RClassifierSetting {
def fromRecord(r: RClassifierSetting): Classifier = def fromRecord(r: RClassifierSetting): Classifier =
Classifier(r.schedule, r.itemCount, r.categoryList, r.listType) Classifier(r.schedule, r.itemCount, r.categoryList, r.listType)
} }
} }

View File

@ -20,7 +20,7 @@ import io.circe.Json
case class RClientSettingsCollective( case class RClientSettingsCollective(
id: Ident, id: Ident,
clientId: Ident, clientId: Ident,
cid: Ident, cid: CollectiveId,
settingsData: Json, settingsData: Json,
updated: Timestamp, updated: Timestamp,
created: Timestamp created: Timestamp
@ -33,7 +33,7 @@ object RClientSettingsCollective {
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val clientId = Column[Ident]("client_id", this) val clientId = Column[Ident]("client_id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val settingsData = Column[Json]("settings_data", this) val settingsData = Column[Json]("settings_data", this)
val updated = Column[Timestamp]("updated", this) val updated = Column[Timestamp]("updated", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -55,7 +55,7 @@ object RClientSettingsCollective {
def updateSettings( def updateSettings(
clientId: Ident, clientId: Ident,
cid: Ident, cid: CollectiveId,
data: Json, data: Json,
updateTs: Timestamp updateTs: Timestamp
): ConnectionIO[Int] = ): ConnectionIO[Int] =
@ -65,7 +65,7 @@ object RClientSettingsCollective {
DML.set(T.settingsData.setTo(data), T.updated.setTo(updateTs)) DML.set(T.settingsData.setTo(data), T.updated.setTo(updateTs))
) )
def upsert(clientId: Ident, cid: Ident, data: Json): ConnectionIO[Int] = def upsert(clientId: Ident, cid: CollectiveId, data: Json): ConnectionIO[Int] =
for { for {
id <- Ident.randomId[ConnectionIO] id <- Ident.randomId[ConnectionIO]
now <- Timestamp.current[ConnectionIO] now <- Timestamp.current[ConnectionIO]
@ -75,10 +75,13 @@ object RClientSettingsCollective {
else 0.pure[ConnectionIO] else 0.pure[ConnectionIO]
} yield nup + nin } yield nup + nin
def delete(clientId: Ident, cid: Ident): ConnectionIO[Int] = def delete(clientId: Ident, cid: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.clientId === clientId && T.cid === cid) DML.delete(T, T.clientId === clientId && T.cid === cid)
def find(clientId: Ident, cid: Ident): ConnectionIO[Option[RClientSettingsCollective]] = def find(
clientId: Ident,
cid: CollectiveId
): ConnectionIO[Option[RClientSettingsCollective]] =
run(select(T.all), from(T), T.clientId === clientId && T.cid === cid) run(select(T.all), from(T), T.clientId === clientId && T.cid === cid)
.query[RClientSettingsCollective] .query[RClientSettingsCollective]
.option .option

View File

@ -17,7 +17,8 @@ import doobie._
import doobie.implicits._ import doobie.implicits._
case class RCollective( case class RCollective(
id: Ident, id: CollectiveId,
name: Ident,
state: CollectiveState, state: CollectiveState,
language: Language, language: Language,
integrationEnabled: Boolean, integrationEnabled: Boolean,
@ -28,17 +29,25 @@ object RCollective {
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "collective" val tableName = "collective"
val id = Column[Ident]("cid", this) val id = Column[CollectiveId]("id", this)
val name = Column[Ident]("name", this)
val state = Column[CollectiveState]("state", this) val state = Column[CollectiveState]("state", this)
val language = Column[Language]("doclang", this) val language = Column[Language]("doclang", this)
val integration = Column[Boolean]("integration_enabled", this) val integration = Column[Boolean]("integration_enabled", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
val all = NonEmptyList.of[Column[_]](id, state, language, integration, created) val all = NonEmptyList.of[Column[_]](id, name, state, language, integration, created)
} }
def makeDefault(collName: Ident, created: Timestamp): RCollective = def makeDefault(collName: Ident, created: Timestamp): RCollective =
RCollective(collName, CollectiveState.Active, Language.German, true, created) RCollective(
CollectiveId.unknown,
collName,
CollectiveState.Active,
Language.German,
true,
created
)
val T = Table(None) val T = Table(None)
def as(alias: String): Table = def as(alias: String): Table =
@ -48,25 +57,23 @@ object RCollective {
DML.insert( DML.insert(
T, T,
T.all, T.all,
fr"${value.id},${value.state},${value.language},${value.integrationEnabled},${value.created}" fr"${value.id},${value.name},${value.state},${value.language},${value.integrationEnabled},${value.created}"
) )
def update(value: RCollective): ConnectionIO[Int] = def update(value: RCollective): ConnectionIO[Int] =
DML.update( DML.update(
T, T,
T.id === value.id, T.id === value.id,
DML.set( DML.set(T.state.setTo(value.state))
T.state.setTo(value.state)
)
) )
def findLanguage(cid: Ident): ConnectionIO[Option[Language]] = def findLanguage(cid: CollectiveId): ConnectionIO[Option[Language]] =
Select(T.language.s, from(T), T.id === cid).build.query[Option[Language]].unique Select(T.language.s, from(T), T.id === cid).build.query[Option[Language]].unique
def updateLanguage(cid: Ident, lang: Language): ConnectionIO[Int] = def updateLanguage(cid: CollectiveId, lang: Language): ConnectionIO[Int] =
DML.update(T, T.id === cid, DML.set(T.language.setTo(lang))) DML.update(T, T.id === cid, DML.set(T.language.setTo(lang)))
def updateSettings(cid: Ident, settings: Settings): ConnectionIO[Int] = def updateSettings(cid: CollectiveId, settings: Settings): ConnectionIO[Int] =
for { for {
n1 <- DML.update( n1 <- DML.update(
T, T,
@ -94,7 +101,7 @@ object RCollective {
// this hides categories that have been deleted in the meantime // this hides categories that have been deleted in the meantime
// they are finally removed from the json array once the learn classifier task is run // they are finally removed from the json array once the learn classifier task is run
def getSettings(coll: Ident): ConnectionIO[Option[Settings]] = def getSettings(coll: CollectiveId): ConnectionIO[Option[Settings]] =
(for { (for {
sett <- OptionT(getRawSettings(coll)) sett <- OptionT(getRawSettings(coll))
prev <- OptionT.pure[ConnectionIO](sett.classifier) prev <- OptionT.pure[ConnectionIO](sett.classifier)
@ -103,7 +110,7 @@ object RCollective {
pws <- OptionT.liftF(RCollectivePassword.findAll(coll)) pws <- OptionT.liftF(RCollectivePassword.findAll(coll))
} yield sett.copy(classifier = next, passwords = pws.map(_.password))).value } yield sett.copy(classifier = next, passwords = pws.map(_.password))).value
private def getRawSettings(coll: Ident): ConnectionIO[Option[Settings]] = { private def getRawSettings(coll: CollectiveId): ConnectionIO[Option[Settings]] = {
import RClassifierSetting.stringListMeta import RClassifierSetting.stringListMeta
val c = RCollective.as("c") val c = RCollective.as("c")
@ -127,7 +134,7 @@ object RCollective {
).build.query[Settings].option ).build.query[Settings].option
} }
def findById(cid: Ident): ConnectionIO[Option[RCollective]] = { def findById(cid: CollectiveId): ConnectionIO[Option[RCollective]] = {
val sql = run(select(T.all), from(T), T.id === cid) val sql = run(select(T.all), from(T), T.id === cid)
sql.query[RCollective].option sql.query[RCollective].option
} }
@ -142,7 +149,7 @@ object RCollective {
).build.query[RCollective].option ).build.query[RCollective].option
} }
def existsById(cid: Ident): ConnectionIO[Boolean] = { def existsById(cid: CollectiveId): ConnectionIO[Boolean] = {
val sql = Select(count(T.id).s, from(T), T.id === cid).build val sql = Select(count(T.id).s, from(T), T.id === cid).build
sql.query[Int].unique.map(_ > 0) sql.query[Int].unique.map(_ > 0)
} }

View File

@ -19,7 +19,7 @@ import doobie.implicits._
final case class RCollectivePassword( final case class RCollectivePassword(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
password: Password, password: Password,
created: Timestamp created: Timestamp
) {} ) {}
@ -29,7 +29,7 @@ object RCollectivePassword {
val tableName: String = "collective_password" val tableName: String = "collective_password"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val password = Column[Password]("pass", this) val password = Column[Password]("pass", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -41,7 +41,7 @@ object RCollectivePassword {
def as(alias: String): Table = def as(alias: String): Table =
Table(Some(alias)) Table(Some(alias))
def createNew[F[_]: Sync](cid: Ident, pw: Password): F[RCollectivePassword] = def createNew[F[_]: Sync](cid: CollectiveId, pw: Password): F[RCollectivePassword] =
for { for {
id <- Ident.randomId[F] id <- Ident.randomId[F]
time <- Timestamp.current[F] time <- Timestamp.current[F]
@ -63,15 +63,15 @@ object RCollectivePassword {
def deleteById(id: Ident): ConnectionIO[Int] = def deleteById(id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id) DML.delete(T, T.id === id)
def deleteByPassword(cid: Ident, pw: Password): ConnectionIO[Int] = def deleteByPassword(cid: CollectiveId, pw: Password): ConnectionIO[Int] =
DML.delete(T, T.password === pw && T.cid === cid) DML.delete(T, T.password === pw && T.cid === cid)
def findAll(cid: Ident): ConnectionIO[List[RCollectivePassword]] = def findAll(cid: CollectiveId): ConnectionIO[List[RCollectivePassword]] =
Select(select(T.all), from(T), T.cid === cid).build Select(select(T.all), from(T), T.cid === cid).build
.query[RCollectivePassword] .query[RCollectivePassword]
.to[List] .to[List]
def replaceAll(cid: Ident, pws: List[Password]): ConnectionIO[Int] = def replaceAll(cid: CollectiveId, pws: List[Password]): ConnectionIO[Int] =
for { for {
k <- DML.delete(T, T.cid === cid) k <- DML.delete(T, T.cid === cid)
pw <- pws.traverse(p => createNew[ConnectionIO](cid, p)) pw <- pws.traverse(p => createNew[ConnectionIO](cid, p))

View File

@ -20,7 +20,7 @@ case class RCustomField(
id: Ident, id: Ident,
name: Ident, name: Ident,
label: Option[String], label: Option[String],
cid: Ident, cid: CollectiveId,
ftype: CustomFieldType, ftype: CustomFieldType,
created: Timestamp created: Timestamp
) )
@ -32,7 +32,7 @@ object RCustomField {
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val name = Column[Ident]("name", this) val name = Column[Ident]("name", this)
val label = Column[String]("label", this) val label = Column[String]("label", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val ftype = Column[CustomFieldType]("ftype", this) val ftype = Column[CustomFieldType]("ftype", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -50,26 +50,29 @@ object RCustomField {
fr"${value.id},${value.name},${value.label},${value.cid},${value.ftype},${value.created}" fr"${value.id},${value.name},${value.label},${value.cid},${value.ftype},${value.created}"
) )
def exists(fname: Ident, coll: Ident): ConnectionIO[Boolean] = def exists(fname: Ident, coll: CollectiveId): ConnectionIO[Boolean] =
run(select(count(T.id)), from(T), T.name === fname && T.cid === coll) run(select(count(T.id)), from(T), T.name === fname && T.cid === coll)
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
def findById(fid: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] = def findById(fid: Ident, coll: CollectiveId): ConnectionIO[Option[RCustomField]] =
run(select(T.all), from(T), T.id === fid && T.cid === coll).query[RCustomField].option run(select(T.all), from(T), T.id === fid && T.cid === coll).query[RCustomField].option
def findByIdOrName(idOrName: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] = def findByIdOrName(
idOrName: Ident,
coll: CollectiveId
): ConnectionIO[Option[RCustomField]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
T.cid === coll && (T.id === idOrName || T.name === idOrName) T.cid === coll && (T.id === idOrName || T.name === idOrName)
).build.query[RCustomField].option ).build.query[RCustomField].option
def deleteById(fid: Ident, coll: Ident): ConnectionIO[Int] = def deleteById(fid: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.id === fid && T.cid === coll) DML.delete(T, T.id === fid && T.cid === coll)
def findAll(coll: Ident): ConnectionIO[Vector[RCustomField]] = def findAll(coll: CollectiveId): ConnectionIO[Vector[RCustomField]] =
run(select(T.all), from(T), T.cid === coll).query[RCustomField].to[Vector] run(select(T.all), from(T), T.cid === coll).query[RCustomField].to[Vector]
def update(value: RCustomField): ConnectionIO[Int] = def update(value: RCustomField): ConnectionIO[Int] =
@ -97,5 +100,4 @@ object RCustomField {
) )
else 0.pure[ConnectionIO] else 0.pure[ConnectionIO]
} yield n + k } yield n + k
} }

View File

@ -17,7 +17,7 @@ import doobie.implicits._
final case class RDownloadQuery( final case class RDownloadQuery(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
fileId: FileKey, fileId: FileKey,
fileCount: Int, fileCount: Int,
created: Timestamp, created: Timestamp,
@ -31,7 +31,7 @@ object RDownloadQuery {
val tableName = "download_query" val tableName = "download_query"
val id: Column[Ident] = Column("id", this) val id: Column[Ident] = Column("id", this)
val cid: Column[Ident] = Column("cid", this) val cid: Column[CollectiveId] = Column("coll_id", this)
val fileId: Column[FileKey] = Column("file_id", this) val fileId: Column[FileKey] = Column("file_id", this)
val fileCount: Column[Int] = Column("file_count", this) val fileCount: Column[Int] = Column("file_count", this)
val created: Column[Timestamp] = Column("created", this) val created: Column[Timestamp] = Column("created", this)

View File

@ -19,7 +19,7 @@ import doobie._
import doobie.implicits._ import doobie.implicits._
final case class REmptyTrashSetting( final case class REmptyTrashSetting(
cid: Ident, cid: CollectiveId,
schedule: CalEvent, schedule: CalEvent,
minAge: Duration, minAge: Duration,
created: Timestamp created: Timestamp
@ -30,7 +30,7 @@ object REmptyTrashSetting {
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "empty_trash_setting" val tableName = "empty_trash_setting"
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val schedule = Column[CalEvent]("schedule", this) val schedule = Column[CalEvent]("schedule", this)
val minAge = Column[Duration]("min_age", this) val minAge = Column[Duration]("min_age", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -61,7 +61,7 @@ object REmptyTrashSetting {
n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO]
} yield n1 + n2 } yield n1 + n2
def findById(id: Ident): ConnectionIO[Option[REmptyTrashSetting]] = { def findById(id: CollectiveId): ConnectionIO[Option[REmptyTrashSetting]] = {
val sql = run(select(T.all), from(T), T.cid === id) val sql = run(select(T.all), from(T), T.cid === id)
sql.query[REmptyTrashSetting].option sql.query[REmptyTrashSetting].option
} }
@ -84,11 +84,11 @@ object REmptyTrashSetting {
sql.query[REmptyTrashSetting].streamWithChunkSize(chunkSize) sql.query[REmptyTrashSetting].streamWithChunkSize(chunkSize)
} }
def delete(coll: Ident): ConnectionIO[Int] = def delete(coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.cid === coll) DML.delete(T, T.cid === coll)
final case class EmptyTrash(schedule: CalEvent, minAge: Duration) { final case class EmptyTrash(schedule: CalEvent, minAge: Duration) {
def toRecord(coll: Ident, created: Timestamp): REmptyTrashSetting = def toRecord(coll: CollectiveId, created: Timestamp): REmptyTrashSetting =
REmptyTrashSetting(coll, schedule, minAge, created) REmptyTrashSetting(coll, schedule, minAge, created)
} }
object EmptyTrash { object EmptyTrash {

View File

@ -17,7 +17,7 @@ import doobie.implicits._
case class REquipment( case class REquipment(
eid: Ident, eid: Ident,
cid: Ident, cid: CollectiveId,
name: String, name: String,
created: Timestamp, created: Timestamp,
updated: Timestamp, updated: Timestamp,
@ -30,7 +30,7 @@ object REquipment {
val tableName = "equipment" val tableName = "equipment"
val eid = Column[Ident]("eid", this) val eid = Column[Ident]("eid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
val updated = Column[Timestamp]("updated", this) val updated = Column[Timestamp]("updated", this)
@ -72,7 +72,7 @@ object REquipment {
} yield n } yield n
} }
def existsByName(coll: Ident, ename: String): ConnectionIO[Boolean] = { def existsByName(coll: CollectiveId, ename: String): ConnectionIO[Boolean] = {
val t = Table(None) val t = Table(None)
val sql = run(select(count(t.eid)), from(t), where(t.cid === coll, t.name === ename)) val sql = run(select(count(t.eid)), from(t), where(t.cid === coll, t.name === ename))
sql.query[Int].unique.map(_ > 0) sql.query[Int].unique.map(_ > 0)
@ -85,7 +85,7 @@ object REquipment {
} }
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
nameQ: Option[String], nameQ: Option[String],
order: Table => NonEmptyList[OrderBy] order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[REquipment]] = { ): ConnectionIO[Vector[REquipment]] = {
@ -100,7 +100,7 @@ object REquipment {
} }
def findLike( def findLike(
coll: Ident, coll: CollectiveId,
equipName: String, equipName: String,
use: NonEmptyList[EquipmentUse] use: NonEmptyList[EquipmentUse]
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
@ -114,7 +114,7 @@ object REquipment {
.to[Vector] .to[Vector]
} }
def delete(id: Ident, coll: Ident): ConnectionIO[Int] = { def delete(id: Ident, coll: CollectiveId): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
DML.delete(t, t.eid === id && t.cid === coll) DML.delete(t, t.eid === id && t.cid === coll)
} }

View File

@ -20,25 +20,29 @@ import doobie.implicits._
case class RFolder( case class RFolder(
id: Ident, id: Ident,
name: String, name: String,
collectiveId: Ident, collectiveId: CollectiveId,
owner: Ident, owner: Ident,
created: Timestamp created: Timestamp
) )
object RFolder { object RFolder {
def newFolder[F[_]: Sync](name: String, account: AccountId): F[RFolder] = def newFolder[F[_]: Sync](
name: String,
collective: CollectiveId,
user: Ident
): F[RFolder] =
for { for {
nId <- Ident.randomId[F] nId <- Ident.randomId[F]
now <- Timestamp.current[F] now <- Timestamp.current[F]
} yield RFolder(nId, name, account.collective, account.user, now) } yield RFolder(nId, name, collective, user, now)
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "folder" val tableName = "folder"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val collective = Column[Ident]("cid", this) val collective = Column[CollectiveId]("coll_id", this)
val owner = Column[Ident]("owner", this) val owner = Column[Ident]("owner", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -63,7 +67,7 @@ object RFolder {
DML.set(T.name.setTo(v.name)) DML.set(T.name.setTo(v.name))
) )
def existsByName(coll: Ident, folderName: String): ConnectionIO[Boolean] = def existsByName(coll: CollectiveId, folderName: String): ConnectionIO[Boolean] =
run(select(count(T.id)), from(T), T.collective === coll && T.name === folderName) run(select(count(T.id)), from(T), T.collective === coll && T.name === folderName)
.query[Int] .query[Int]
.unique .unique
@ -77,7 +81,7 @@ object RFolder {
def requireIdByIdOrName( def requireIdByIdOrName(
folderId: Ident, folderId: Ident,
name: String, name: String,
collective: Ident collective: CollectiveId
): ConnectionIO[Ident] = { ): ConnectionIO[Ident] = {
val sql = run( val sql = run(
select(T.id), select(T.id),
@ -94,7 +98,7 @@ object RFolder {
} }
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
nameQ: Option[String], nameQ: Option[String],
order: Table => Column[_] order: Table => Column[_]
): ConnectionIO[Vector[RFolder]] = { ): ConnectionIO[Vector[RFolder]] = {

View File

@ -20,7 +20,7 @@ import doobie.implicits._
case class RItem( case class RItem(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
name: String, name: String,
itemDate: Option[Timestamp], itemDate: Option[Timestamp],
source: String, source: String,
@ -40,7 +40,7 @@ case class RItem(
object RItem { object RItem {
def newItem[F[_]: Sync]( def newItem[F[_]: Sync](
cid: Ident, cid: CollectiveId,
name: String, name: String,
source: String, source: String,
direction: Direction, direction: Direction,
@ -73,7 +73,7 @@ object RItem {
val tableName = "item" val tableName = "item"
val id = Column[Ident]("itemid", this) val id = Column[Ident]("itemid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val itemDate = Column[Timestamp]("itemdate", this) val itemDate = Column[Timestamp]("itemdate", this)
val source = Column[String]("source", this) val source = Column[String]("source", this)
@ -166,7 +166,7 @@ object RItem {
def updateStateForCollective( def updateStateForCollective(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
itemState: ItemState, itemState: ItemState,
coll: Ident coll: CollectiveId
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
@ -180,7 +180,7 @@ object RItem {
def restoreStateForCollective( def restoreStateForCollective(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
itemState: ItemState, itemState: ItemState,
coll: Ident coll: CollectiveId
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
@ -193,7 +193,7 @@ object RItem {
def updateDirection( def updateDirection(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
dir: Direction dir: Direction
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -207,7 +207,7 @@ object RItem {
def updateCorrOrg( def updateCorrOrg(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
org: Option[Ident] org: Option[Ident]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -219,7 +219,7 @@ object RItem {
) )
} yield n } yield n
def removeCorrOrg(coll: Ident, currentOrg: Ident): ConnectionIO[Int] = def removeCorrOrg(coll: CollectiveId, currentOrg: Ident): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -231,7 +231,7 @@ object RItem {
def updateCorrPerson( def updateCorrPerson(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
person: Option[Ident] person: Option[Ident]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -243,7 +243,7 @@ object RItem {
) )
} yield n } yield n
def removeCorrPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = def removeCorrPerson(coll: CollectiveId, currentPerson: Ident): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -255,7 +255,7 @@ object RItem {
def updateConcPerson( def updateConcPerson(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
person: Option[Ident] person: Option[Ident]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -267,7 +267,7 @@ object RItem {
) )
} yield n } yield n
def removeConcPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = def removeConcPerson(coll: CollectiveId, currentPerson: Ident): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -279,7 +279,7 @@ object RItem {
def updateConcEquip( def updateConcEquip(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
equip: Option[Ident] equip: Option[Ident]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -291,7 +291,7 @@ object RItem {
) )
} yield n } yield n
def removeConcEquip(coll: Ident, currentEquip: Ident): ConnectionIO[Int] = def removeConcEquip(coll: CollectiveId, currentEquip: Ident): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -303,7 +303,7 @@ object RItem {
def updateFolder( def updateFolder(
itemId: Ident, itemId: Ident,
coll: Ident, coll: CollectiveId,
folderIdOrName: Option[String] folderIdOrName: Option[String]
): ConnectionIO[(Int, Option[Ident])] = ): ConnectionIO[(Int, Option[Ident])] =
for { for {
@ -321,7 +321,11 @@ object RItem {
) )
} yield (n, fid) } yield (n, fid)
def updateNotes(itemId: Ident, coll: Ident, text: Option[String]): ConnectionIO[Int] = def updateNotes(
itemId: Ident,
coll: CollectiveId,
text: Option[String]
): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -333,7 +337,7 @@ object RItem {
def appendNotes( def appendNotes(
itemId: Ident, itemId: Ident,
cid: Ident, cid: CollectiveId,
text: String, text: String,
sep: Option[String] sep: Option[String]
): ConnectionIO[Option[String]] = { ): ConnectionIO[Option[String]] = {
@ -351,7 +355,7 @@ object RItem {
} }
} }
def updateName(itemId: Ident, coll: Ident, itemName: String): ConnectionIO[Int] = def updateName(itemId: Ident, coll: CollectiveId, itemName: String): ConnectionIO[Int] =
for { for {
t <- currentTime t <- currentTime
n <- DML.update( n <- DML.update(
@ -363,7 +367,7 @@ object RItem {
def updateDate( def updateDate(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
date: Option[Timestamp] date: Option[Timestamp]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -377,7 +381,7 @@ object RItem {
def updateDueDate( def updateDueDate(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
date: Option[Timestamp] date: Option[Timestamp]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -389,12 +393,12 @@ object RItem {
) )
} yield n } yield n
def deleteByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Int] = def deleteByIdAndCollective(itemId: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.id === itemId && T.cid === coll) DML.delete(T, T.id === itemId && T.cid === coll)
def setState( def setState(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident, coll: CollectiveId,
state: ItemState state: ItemState
): ConnectionIO[Int] = ): ConnectionIO[Int] =
for { for {
@ -409,7 +413,7 @@ object RItem {
def existsById(itemId: Ident): ConnectionIO[Boolean] = def existsById(itemId: Ident): ConnectionIO[Boolean] =
Select(count(T.id).s, from(T), T.id === itemId).build.query[Int].unique.map(_ > 0) Select(count(T.id).s, from(T), T.id === itemId).build.query[Int].unique.map(_ > 0)
def existsByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Boolean] = def existsByIdAndCollective(itemId: Ident, coll: CollectiveId): ConnectionIO[Boolean] =
Select(count(T.id).s, from(T), T.id === itemId && T.cid === coll).build Select(count(T.id).s, from(T), T.id === itemId && T.cid === coll).build
.query[Int] .query[Int]
.unique .unique
@ -417,19 +421,22 @@ object RItem {
def existsByIdsAndCollective( def existsByIdsAndCollective(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident coll: CollectiveId
): ConnectionIO[Boolean] = ): ConnectionIO[Boolean] =
Select(count(T.id).s, from(T), T.id.in(itemIds) && T.cid === coll).build Select(count(T.id).s, from(T), T.id.in(itemIds) && T.cid === coll).build
.query[Int] .query[Int]
.unique .unique
.map(_ == itemIds.size) .map(_ == itemIds.size)
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] = def findByIdAndCollective(
itemId: Ident,
coll: CollectiveId
): ConnectionIO[Option[RItem]] =
run(select(T.all), from(T), T.id === itemId && T.cid === coll).query[RItem].option run(select(T.all), from(T), T.id === itemId && T.cid === coll).query[RItem].option
def findAllByIdAndCollective( def findAllByIdAndCollective(
itemIds: NonEmptyList[Ident], itemIds: NonEmptyList[Ident],
coll: Ident coll: CollectiveId
): ConnectionIO[Vector[RItem]] = ): ConnectionIO[Vector[RItem]] =
run(select(T.all), from(T), T.id.in(itemIds) && T.cid === coll) run(select(T.all), from(T), T.id.in(itemIds) && T.cid === coll)
.query[RItem] .query[RItem]
@ -439,7 +446,7 @@ object RItem {
run(select(T.all), from(T), T.id === itemId).query[RItem].option run(select(T.all), from(T), T.id === itemId).query[RItem].option
def findDeleted( def findDeleted(
collective: Ident, collective: CollectiveId,
maxUpdated: Timestamp, maxUpdated: Timestamp,
chunkSize: Int chunkSize: Int
): Stream[ConnectionIO, RItem] = ): Stream[ConnectionIO, RItem] =
@ -451,7 +458,10 @@ object RItem {
.query[RItem] .query[RItem]
.streamWithChunkSize(chunkSize) .streamWithChunkSize(chunkSize)
def checkByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[Ident]] = def checkByIdAndCollective(
itemId: Ident,
coll: CollectiveId
): ConnectionIO[Option[Ident]] =
Select(T.id.s, from(T), T.id === itemId && T.cid === coll).build.query[Ident].option Select(T.id.s, from(T), T.id === itemId && T.cid === coll).build.query[Ident].option
def removeFolder(folderId: Ident): ConnectionIO[Int] = { def removeFolder(folderId: Ident): ConnectionIO[Int] = {
@ -459,9 +469,12 @@ object RItem {
DML.update(T, T.folder === folderId, DML.set(T.folder.setTo(empty))) DML.update(T, T.folder === folderId, DML.set(T.folder.setTo(empty)))
} }
def filterItemsFragment(items: NonEmptyList[Ident], coll: Ident): Select = def filterItemsFragment(items: NonEmptyList[Ident], coll: CollectiveId): Select =
Select(select(T.id), from(T), T.cid === coll && T.id.in(items)) Select(select(T.id), from(T), T.cid === coll && T.id.in(items))
def filterItems(items: NonEmptyList[Ident], coll: Ident): ConnectionIO[Vector[Ident]] = def filterItems(
items: NonEmptyList[Ident],
coll: CollectiveId
): ConnectionIO[Vector[Ident]] =
filterItemsFragment(items, coll).build.query[Ident].to[Vector] filterItemsFragment(items, coll).build.query[Ident].to[Vector]
} }

View File

@ -20,14 +20,14 @@ import doobie.implicits._
final case class RItemLink( final case class RItemLink(
id: Ident, id: Ident,
cid: Ident, cid: CollectiveId,
item1: Ident, item1: Ident,
item2: Ident, item2: Ident,
created: Timestamp created: Timestamp
) )
object RItemLink { object RItemLink {
def create[F[_]: Sync](cid: Ident, item1: Ident, item2: Ident): F[RItemLink] = def create[F[_]: Sync](cid: CollectiveId, item1: Ident, item2: Ident): F[RItemLink] =
for { for {
id <- Ident.randomId[F] id <- Ident.randomId[F]
now <- Timestamp.current[F] now <- Timestamp.current[F]
@ -37,7 +37,7 @@ object RItemLink {
val tableName = "item_link" val tableName = "item_link"
val id: Column[Ident] = Column("id", this) val id: Column[Ident] = Column("id", this)
val cid: Column[Ident] = Column("cid", this) val cid: Column[CollectiveId] = Column("coll_id", this)
val item1: Column[Ident] = Column("item1", this) val item1: Column[Ident] = Column("item1", this)
val item2: Column[Ident] = Column("item2", this) val item2: Column[Ident] = Column("item2", this)
val created: Column[Timestamp] = Column("created", this) val created: Column[Timestamp] = Column("created", this)
@ -62,7 +62,7 @@ object RItemLink {
DML.insertSilent(T, T.all, sql"${r.id},${r.cid},$i1,$i2,${r.created}") DML.insertSilent(T, T.all, sql"${r.id},${r.cid},$i1,$i2,${r.created}")
} }
def insertNew(cid: Ident, item1: Ident, item2: Ident): ConnectionIO[Int] = def insertNew(cid: CollectiveId, item1: Ident, item2: Ident): ConnectionIO[Int] =
create[ConnectionIO](cid, item1, item2).flatMap(insert) create[ConnectionIO](cid, item1, item2).flatMap(insert)
def update(r: RItemLink): ConnectionIO[Int] = { def update(r: RItemLink): ConnectionIO[Int] = {
@ -77,7 +77,7 @@ object RItemLink {
) )
} }
def exists(cid: Ident, item1: Ident, item2: Ident): ConnectionIO[Boolean] = { def exists(cid: CollectiveId, item1: Ident, item2: Ident): ConnectionIO[Boolean] = {
val (i1, i2) = orderIds(item1, item2) val (i1, i2) = orderIds(item1, item2)
Select( Select(
select(count(T.id)), select(count(T.id)),
@ -86,7 +86,7 @@ object RItemLink {
).build.query[Int].unique.map(_ > 0) ).build.query[Int].unique.map(_ > 0)
} }
def findLinked(cid: Ident, item: Ident): ConnectionIO[Vector[Ident]] = def findLinked(cid: CollectiveId, item: Ident): ConnectionIO[Vector[Ident]] =
union( union(
Select( Select(
select(T.item1), select(T.item1),
@ -101,7 +101,7 @@ object RItemLink {
).build.query[Ident].to[Vector] ).build.query[Ident].to[Vector]
def deleteAll( def deleteAll(
cid: Ident, cid: CollectiveId,
item: Ident, item: Ident,
related: NonEmptyList[Ident] related: NonEmptyList[Ident]
): ConnectionIO[Int] = ): ConnectionIO[Int] =
@ -113,7 +113,7 @@ object RItemLink {
) )
) )
def delete(cid: Ident, item1: Ident, item2: Ident): ConnectionIO[Int] = { def delete(cid: CollectiveId, item1: Ident, item2: Ident): ConnectionIO[Int] = {
val (i1, i2) = orderIds(item1, item2) val (i1, i2) = orderIds(item1, item2)
DML.delete(T, T.cid === cid && T.item1 === i1 && T.item2 === i2) DML.delete(T, T.cid === cid && T.item1 === i1 && T.item2 === i2)
} }

View File

@ -95,12 +95,12 @@ object RNotificationChannel {
RNotificationChannelHttp.update RNotificationChannelHttp.update
) )
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannel]] = def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannel]] =
for { for {
mail <- RNotificationChannelMail.getByAccount(account) mail <- RNotificationChannelMail.getByAccount(userId)
gotify <- RNotificationChannelGotify.getByAccount(account) gotify <- RNotificationChannelGotify.getByAccount(userId)
matrix <- RNotificationChannelMatrix.getByAccount(account) matrix <- RNotificationChannelMatrix.getByAccount(userId)
http <- RNotificationChannelHttp.getByAccount(account) http <- RNotificationChannelHttp.getByAccount(userId)
} yield mail.map(Email.apply) ++ gotify.map(Gotify.apply) ++ matrix.map( } yield mail.map(Email.apply) ++ gotify.map(Gotify.apply) ++ matrix.map(
Matrix.apply Matrix.apply
) ++ http.map(Http.apply) ) ++ http.map(Http.apply)
@ -177,12 +177,12 @@ object RNotificationChannel {
.flatMap(_.flatTraverse(find)) .flatMap(_.flatTraverse(find))
} }
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
for { for {
n1 <- RNotificationChannelMail.deleteByAccount(id, account) n1 <- RNotificationChannelMail.deleteByAccount(id, userId)
n2 <- RNotificationChannelGotify.deleteByAccount(id, account) n2 <- RNotificationChannelGotify.deleteByAccount(id, userId)
n3 <- RNotificationChannelMatrix.deleteByAccount(id, account) n3 <- RNotificationChannelMatrix.deleteByAccount(id, userId)
n4 <- RNotificationChannelHttp.deleteByAccount(id, account) n4 <- RNotificationChannelHttp.deleteByAccount(id, userId)
} yield n1 + n2 + n3 + n4 } yield n1 + n2 + n3 + n4
def fromChannel( def fromChannel(

View File

@ -77,27 +77,21 @@ object RNotificationChannelGotify {
) )
def getByAccount( def getByAccount(
account: AccountId userId: Ident
): ConnectionIO[Vector[RNotificationChannelGotify]] = { ): ConnectionIO[Vector[RNotificationChannelGotify]] = {
val user = RUser.as("u")
val gotify = as("c") val gotify = as("c")
Select( Select(
select(gotify.all), select(gotify.all),
from(gotify).innerJoin(user, user.uid === gotify.uid), from(gotify),
user.cid === account.collective && user.login === account.user gotify.uid === userId
).build.query[RNotificationChannelGotify].to[Vector] ).build.query[RNotificationChannelGotify].to[Vector]
} }
def deleteById(id: Ident): ConnectionIO[Int] = def deleteById(id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id) DML.delete(T, T.id === id)
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
val u = RUser.as("u") DML.delete(T, T.id === id && T.uid === userId)
DML.delete(
T,
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
)
}
def findRefs(ids: NonEmptyList[Ident]): Select = def findRefs(ids: NonEmptyList[Ident]): Select =
Select( Select(

View File

@ -61,26 +61,23 @@ object RNotificationChannelHttp {
DML.set(T.url.setTo(r.url), T.name.setTo(r.name)) DML.set(T.url.setTo(r.url), T.name.setTo(r.name))
) )
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = { def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannelHttp]] = {
val user = RUser.as("u")
val http = as("c") val http = as("c")
Select( Select(
select(http.all), select(http.all),
from(http).innerJoin(user, user.uid === http.uid), from(http),
user.cid === account.collective && user.login === account.user http.uid === userId
).build.query[RNotificationChannelHttp].to[Vector] ).build.query[RNotificationChannelHttp].to[Vector]
} }
def deleteById(id: Ident): ConnectionIO[Int] = def deleteById(id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id) DML.delete(T, T.id === id)
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
val u = RUser.as("u")
DML.delete( DML.delete(
T, T,
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account))) T.id === id && T.uid === userId
) )
}
def findRefs(ids: NonEmptyList[Ident]): Select = def findRefs(ids: NonEmptyList[Ident]): Select =
Select( Select(

View File

@ -71,26 +71,20 @@ object RNotificationChannelMail {
.query[RNotificationChannelMail] .query[RNotificationChannelMail]
.option .option
def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = { def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannelMail]] = {
val user = RUser.as("u") val mail = as("c")
val gotify = as("c")
Select( Select(
select(gotify.all), select(mail.all),
from(gotify).innerJoin(user, user.uid === gotify.uid), from(mail),
user.cid === account.collective && user.login === account.user mail.uid === userId
).build.query[RNotificationChannelMail].to[Vector] ).build.query[RNotificationChannelMail].to[Vector]
} }
def deleteById(id: Ident): ConnectionIO[Int] = def deleteById(id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id) DML.delete(T, T.id === id)
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
val u = RUser.as("u") DML.delete(T, T.id === id && T.uid === userId)
DML.delete(
T,
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account)))
)
}
def findRefs(ids: NonEmptyList[Ident]): Select = def findRefs(ids: NonEmptyList[Ident]): Select =
Select( Select(

View File

@ -86,27 +86,25 @@ object RNotificationChannelMatrix {
.option .option
def getByAccount( def getByAccount(
account: AccountId userId: Ident
): ConnectionIO[Vector[RNotificationChannelMatrix]] = { ): ConnectionIO[Vector[RNotificationChannelMatrix]] = {
val user = RUser.as("u")
val gotify = as("c") val matrix = as("c")
Select( Select(
select(gotify.all), select(matrix.all),
from(gotify).innerJoin(user, user.uid === gotify.uid), from(matrix),
user.cid === account.collective && user.login === account.user matrix.uid === userId
).build.query[RNotificationChannelMatrix].to[Vector] ).build.query[RNotificationChannelMatrix].to[Vector]
} }
def deleteById(id: Ident): ConnectionIO[Int] = def deleteById(id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id) DML.delete(T, T.id === id)
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
val u = RUser.as("u")
DML.delete( DML.delete(
T, T,
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account))) T.id === id && T.uid === userId
) )
}
def findRefs(ids: NonEmptyList[Ident]): Select = def findRefs(ids: NonEmptyList[Ident]): Select =
Select( Select(

View File

@ -58,13 +58,11 @@ object RNotificationHook {
sql"${r.id},${r.uid},${r.enabled},${r.allEvents},${r.eventFilter},${r.created}" sql"${r.id},${r.uid},${r.enabled},${r.allEvents},${r.eventFilter},${r.created}"
) )
def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] =
val u = RUser.as("u")
DML.delete( DML.delete(
T, T,
T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account))) T.id === id && T.uid === userId
) )
}
def update(r: RNotificationHook): ConnectionIO[Int] = def update(r: RNotificationHook): ConnectionIO[Int] =
DML.update( DML.update(
@ -77,11 +75,11 @@ object RNotificationHook {
) )
) )
def findByAccount(account: AccountId): ConnectionIO[Vector[RNotificationHook]] = def findByAccount(userId: Ident): ConnectionIO[Vector[RNotificationHook]] =
Select( Select(
select(T.all), select(T.all),
from(T), from(T),
T.uid.in(Select(select(RUser.T.uid), from(RUser.T), RUser.T.isAccount(account))) T.uid === userId
).build.query[RNotificationHook].to[Vector] ).build.query[RNotificationHook].to[Vector]
def getById(id: Ident, userId: Ident): ConnectionIO[Option[RNotificationHook]] = def getById(id: Ident, userId: Ident): ConnectionIO[Option[RNotificationHook]] =
@ -92,17 +90,15 @@ object RNotificationHook {
).build.query[RNotificationHook].option ).build.query[RNotificationHook].option
def findAllByAccount( def findAllByAccount(
account: AccountId userId: Ident
): ConnectionIO[Vector[(RNotificationHook, List[EventType])]] = { ): ConnectionIO[Vector[(RNotificationHook, List[EventType])]] = {
val h = RNotificationHook.as("h") val h = RNotificationHook.as("h")
val e = RNotificationHookEvent.as("e") val e = RNotificationHookEvent.as("e")
val userSelect =
Select(select(RUser.T.uid), from(RUser.T), RUser.T.isAccount(account))
val withEvents = Select( val withEvents = Select(
select(h.all :+ e.eventType), select(h.all :+ e.eventType),
from(h).innerJoin(e, e.hookId === h.id), from(h).innerJoin(e, e.hookId === h.id),
h.uid.in(userSelect) h.uid === userId
).orderBy(h.id) ).orderBy(h.id)
.build .build
.query[(RNotificationHook, EventType)] .query[(RNotificationHook, EventType)]
@ -113,7 +109,7 @@ object RNotificationHook {
Select( Select(
select(h.all), select(h.all),
from(h), from(h),
h.id.notIn(Select(select(e.hookId), from(e))) && h.uid.in(userSelect) h.id.notIn(Select(select(e.hookId), from(e))) && h.uid === userId
).build ).build
.query[RNotificationHook] .query[RNotificationHook]
.to[Vector] .to[Vector]

View File

@ -19,7 +19,7 @@ import doobie.implicits._
case class ROrganization( case class ROrganization(
oid: Ident, oid: Ident,
cid: Ident, cid: CollectiveId,
name: String, name: String,
street: String, street: String,
zip: String, zip: String,
@ -40,7 +40,7 @@ object ROrganization {
val tableName = "organization" val tableName = "organization"
val oid = Column[Ident]("oid", this) val oid = Column[Ident]("oid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val street = Column[String]("street", this) val street = Column[String]("street", this)
val zip = Column[String]("zip", this) val zip = Column[String]("zip", this)
@ -103,24 +103,24 @@ object ROrganization {
} yield n } yield n
} }
def existsByName(coll: Ident, oname: String): ConnectionIO[Boolean] = def existsByName(coll: CollectiveId, oname: String): ConnectionIO[Boolean] =
run(select(count(T.oid)), from(T), T.cid === coll && T.name === oname) run(select(count(T.oid)), from(T), T.cid === coll && T.name === oname)
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
def findById(id: Ident): ConnectionIO[Option[ROrganization]] = { def findById(id: Ident): ConnectionIO[Option[ROrganization]] = {
val sql = run(select(T.all), from(T), T.cid === id) val sql = run(select(T.all), from(T), T.oid === id)
sql.query[ROrganization].option sql.query[ROrganization].option
} }
def find(coll: Ident, orgName: String): ConnectionIO[Option[ROrganization]] = { def find(coll: CollectiveId, orgName: String): ConnectionIO[Option[ROrganization]] = {
val sql = run(select(T.all), from(T), T.cid === coll && T.name === orgName) val sql = run(select(T.all), from(T), T.cid === coll && T.name === orgName)
sql.query[ROrganization].option sql.query[ROrganization].option
} }
def findLike( def findLike(
coll: Ident, coll: CollectiveId,
orgName: String, orgName: String,
use: Nel[OrgUse] use: Nel[OrgUse]
): ConnectionIO[Vector[IdRef]] = ): ConnectionIO[Vector[IdRef]] =
@ -135,7 +135,7 @@ object ROrganization {
.to[Vector] .to[Vector]
def findLike( def findLike(
coll: Ident, coll: CollectiveId,
contactKind: ContactKind, contactKind: ContactKind,
value: String value: String
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
@ -153,7 +153,7 @@ object ROrganization {
} }
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
order: Table => Column[_] order: Table => Column[_]
): Stream[ConnectionIO, ROrganization] = { ): Stream[ConnectionIO, ROrganization] = {
val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T))
@ -161,7 +161,7 @@ object ROrganization {
} }
def findAllRef( def findAllRef(
coll: Ident, coll: CollectiveId,
nameQ: Option[String], nameQ: Option[String],
order: Table => Nel[OrderBy] order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
@ -173,6 +173,6 @@ object ROrganization {
sql.build.query[IdRef].to[Vector] sql.build.query[IdRef].to[Vector]
} }
def delete(id: Ident, coll: Ident): ConnectionIO[Int] = def delete(id: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.oid === id && T.cid === coll) DML.delete(T, T.oid === id && T.cid === coll)
} }

View File

@ -20,7 +20,7 @@ import doobie.implicits._
case class RPerson( case class RPerson(
pid: Ident, pid: Ident,
cid: Ident, cid: CollectiveId,
name: String, name: String,
street: String, street: String,
zip: String, zip: String,
@ -41,7 +41,7 @@ object RPerson {
val tableName = "person" val tableName = "person"
val pid = Column[Ident]("pid", this) val pid = Column[Ident]("pid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val street = Column[String]("street", this) val street = Column[String]("street", this)
val zip = Column[String]("zip", this) val zip = Column[String]("zip", this)
@ -103,24 +103,24 @@ object RPerson {
} yield n } yield n
} }
def existsByName(coll: Ident, pname: String): ConnectionIO[Boolean] = def existsByName(coll: CollectiveId, pname: String): ConnectionIO[Boolean] =
run(select(count(T.pid)), from(T), T.cid === coll && T.name === pname) run(select(count(T.pid)), from(T), T.cid === coll && T.name === pname)
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
def findById(id: Ident): ConnectionIO[Option[RPerson]] = { def findById(id: Ident): ConnectionIO[Option[RPerson]] = {
val sql = run(select(T.all), from(T), T.cid === id) val sql = run(select(T.all), from(T), T.pid === id)
sql.query[RPerson].option sql.query[RPerson].option
} }
def find(coll: Ident, personName: String): ConnectionIO[Option[RPerson]] = { def find(coll: CollectiveId, personName: String): ConnectionIO[Option[RPerson]] = {
val sql = run(select(T.all), from(T), T.cid === coll && T.name === personName) val sql = run(select(T.all), from(T), T.cid === coll && T.name === personName)
sql.query[RPerson].option sql.query[RPerson].option
} }
def findLike( def findLike(
coll: Ident, coll: CollectiveId,
personName: String, personName: String,
use: Nel[PersonUse] use: Nel[PersonUse]
): ConnectionIO[Vector[IdRef]] = ): ConnectionIO[Vector[IdRef]] =
@ -131,7 +131,7 @@ object RPerson {
).query[IdRef].to[Vector] ).query[IdRef].to[Vector]
def findLike( def findLike(
coll: Ident, coll: CollectiveId,
contactKind: ContactKind, contactKind: ContactKind,
value: String, value: String,
use: Nel[PersonUse] use: Nel[PersonUse]
@ -152,7 +152,7 @@ object RPerson {
} }
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
order: Table => Column[_] order: Table => Column[_]
): Stream[ConnectionIO, RPerson] = { ): Stream[ConnectionIO, RPerson] = {
val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T))
@ -160,7 +160,7 @@ object RPerson {
} }
def findAllRef( def findAllRef(
coll: Ident, coll: CollectiveId,
nameQ: Option[String], nameQ: Option[String],
order: Table => Nel[OrderBy] order: Table => Nel[OrderBy]
): ConnectionIO[Vector[IdRef]] = { ): ConnectionIO[Vector[IdRef]] = {
@ -172,7 +172,7 @@ object RPerson {
sql.build.query[IdRef].to[Vector] sql.build.query[IdRef].to[Vector]
} }
def delete(personId: Ident, coll: Ident): ConnectionIO[Int] = def delete(personId: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.pid === personId && T.cid === coll) DML.delete(T, T.pid === personId && T.cid === coll)
def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] = def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] =

View File

@ -23,7 +23,7 @@ final case class RQueryBookmark(
name: String, name: String,
label: Option[String], label: Option[String],
userId: Option[Ident], userId: Option[Ident],
cid: Ident, cid: CollectiveId,
query: ItemQuery, query: ItemQuery,
created: Timestamp created: Timestamp
) { ) {
@ -39,13 +39,13 @@ final case class RQueryBookmark(
object RQueryBookmark { object RQueryBookmark {
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "query_bookmark"; val tableName = "query_bookmark"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val label = Column[String]("label", this) val label = Column[String]("label", this)
val userId = Column[Ident]("user_id", this) val userId = Column[Ident]("user_id", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val query = Column[ItemQuery]("query", this) val query = Column[ItemQuery]("query", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -59,22 +59,21 @@ object RQueryBookmark {
def as(alias: String): Table = Table(Some(alias)) def as(alias: String): Table = Table(Some(alias))
def createNew( def createNew(
account: AccountId, collective: CollectiveId,
userId: Option[Ident],
name: String, name: String,
label: Option[String], label: Option[String],
query: ItemQuery, query: ItemQuery
personal: Boolean
): ConnectionIO[RQueryBookmark] = ): ConnectionIO[RQueryBookmark] =
for { for {
userId <- RUser.getIdByAccount(account)
curTime <- Timestamp.current[ConnectionIO] curTime <- Timestamp.current[ConnectionIO]
id <- Ident.randomId[ConnectionIO] id <- Ident.randomId[ConnectionIO]
} yield RQueryBookmark( } yield RQueryBookmark(
id, id,
name, name,
label, label,
if (personal) userId.some else None, userId,
account.collective, collective,
query, query,
curTime curTime
) )
@ -100,23 +99,20 @@ object RQueryBookmark {
) )
) )
def deleteById(cid: Ident, id: Ident): ConnectionIO[Int] = def deleteById(cid: CollectiveId, id: Ident): ConnectionIO[Int] =
DML.delete(T, T.id === id && T.cid === cid) DML.delete(T, T.id === id && T.cid === cid)
def nameExists(account: AccountId, name: String): ConnectionIO[Boolean] = { def nameExists(
val user = RUser.as("u") collId: CollectiveId,
userId: Ident,
name: String
): ConnectionIO[Boolean] = {
val bm = RQueryBookmark.as("bm") val bm = RQueryBookmark.as("bm")
val users = Select(
user.uid.s,
from(user),
user.cid === account.collective && user.login === account.user
)
Select( Select(
select(count(bm.id)), select(count(bm.id)),
from(bm), from(bm),
bm.name === name && bm.cid === account.collective && (bm.userId.isNull || bm.userId bm.name === name && bm.cid === collId && (bm.userId.isNull || bm.userId === userId)
.in(users))
).build.query[Int].unique.map(_ > 0) ).build.query[Int].unique.map(_ > 0)
} }
@ -127,13 +123,14 @@ object RQueryBookmark {
// checked before (and therefore subject to race conditions, but is // checked before (and therefore subject to race conditions, but is
// neglected here) // neglected here)
def insertIfNotExists( def insertIfNotExists(
account: AccountId, collId: CollectiveId,
userId: Ident,
r: ConnectionIO[RQueryBookmark] r: ConnectionIO[RQueryBookmark]
): ConnectionIO[AddResult] = ): ConnectionIO[AddResult] =
for { for {
bm <- r bm <- r
res <- res <-
nameExists(account, bm.name).flatMap { nameExists(collId, userId, bm.name).flatMap {
case true => case true =>
AddResult AddResult
.entityExists(s"A bookmark '${bm.name}' already exists.") .entityExists(s"A bookmark '${bm.name}' already exists.")
@ -142,39 +139,31 @@ object RQueryBookmark {
} }
} yield res } yield res
def allForUser(account: AccountId): ConnectionIO[Vector[RQueryBookmark]] = { def allForUser(
val user = RUser.as("u") collId: CollectiveId,
userId: Ident
): ConnectionIO[Vector[RQueryBookmark]] = {
val bm = RQueryBookmark.as("bm") val bm = RQueryBookmark.as("bm")
val users = Select(
user.uid.s,
from(user),
user.cid === account.collective && user.login === account.user
)
Select( Select(
select(bm.all), select(bm.all),
from(bm), from(bm),
bm.cid === account.collective && (bm.userId.isNull || bm.userId.in(users)) bm.cid === collId && (bm.userId.isNull || bm.userId === userId)
).build.query[RQueryBookmark].to[Vector] ).build.query[RQueryBookmark].to[Vector]
} }
def findByNameOrId( def findByNameOrId(
account: AccountId, collId: CollectiveId,
userId: Ident,
nameOrId: String nameOrId: String
): ConnectionIO[Option[RQueryBookmark]] = { ): ConnectionIO[Option[RQueryBookmark]] = {
val user = RUser.as("u")
val bm = RQueryBookmark.as("bm") val bm = RQueryBookmark.as("bm")
val users = Select(
user.uid.s,
from(user),
user.cid === account.collective && user.login === account.user
)
Select( Select(
select(bm.all), select(bm.all),
from(bm), from(bm),
bm.cid === account.collective && bm.cid === collId &&
(bm.userId.isNull || bm.userId.in(users)) && (bm.userId.isNull || bm.userId === userId) &&
(bm.name === nameOrId || bm.id ==== nameOrId) (bm.name === nameOrId || bm.id ==== nameOrId)
).build.query[RQueryBookmark].option ).build.query[RQueryBookmark].option
} }

View File

@ -32,7 +32,7 @@ final case class RShare(
object RShare { object RShare {
final case class Table(alias: Option[String]) extends TableDef { final case class Table(alias: Option[String]) extends TableDef {
val tableName = "item_share"; val tableName = "item_share"
val id = Column[Ident]("id", this) val id = Column[Ident]("id", this)
val userId = Column[Ident]("user_id", this) val userId = Column[Ident]("user_id", this)
@ -94,7 +94,7 @@ object RShare {
else Nil) else Nil)
) )
def findOne(id: Ident, cid: Ident): OptionT[ConnectionIO, (RShare, RUser)] = { def findOne(id: Ident, cid: CollectiveId): OptionT[ConnectionIO, (RShare, RUser)] = {
val s = RShare.as("s") val s = RShare.as("s")
val u = RUser.as("u") val u = RUser.as("u")
@ -139,7 +139,7 @@ object RShare {
}) })
def findOneByCollective( def findOneByCollective(
cid: Ident, cid: CollectiveId,
enabled: Option[Boolean], enabled: Option[Boolean],
nameOrId: String nameOrId: String
): ConnectionIO[Option[RShare]] = { ): ConnectionIO[Option[RShare]] = {
@ -156,7 +156,7 @@ object RShare {
} }
def findAllByCollective( def findAllByCollective(
cid: Ident, cid: CollectiveId,
ownerLogin: Option[Ident], ownerLogin: Option[Ident],
q: Option[String] q: Option[String]
): ConnectionIO[List[(RShare, RUser)]] = { ): ConnectionIO[List[(RShare, RUser)]] = {
@ -177,7 +177,7 @@ object RShare {
.to[List] .to[List]
} }
def deleteByIdAndCid(id: Ident, cid: Ident): ConnectionIO[Int] = { def deleteByIdAndCid(id: Ident, cid: CollectiveId): ConnectionIO[Int] = {
val u = RUser.T val u = RUser.T
DML.delete(T, T.id === id && T.userId.in(Select(u.uid.s, from(u), u.cid === cid))) DML.delete(T, T.id === id && T.userId.in(Select(u.uid.s, from(u), u.cid === cid)))
} }

View File

@ -17,7 +17,7 @@ import doobie.implicits._
case class RSource( case class RSource(
sid: Ident, sid: Ident,
cid: Ident, cid: CollectiveId,
abbrev: String, abbrev: String,
description: Option[String], description: Option[String],
counter: Int, counter: Int,
@ -40,7 +40,7 @@ object RSource {
val tableName = "source" val tableName = "source"
val sid = Column[Ident]("sid", this) val sid = Column[Ident]("sid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val abbrev = Column[String]("abbrev", this) val abbrev = Column[String]("abbrev", this)
val description = Column[String]("description", this) val description = Column[String]("description", this)
val counter = Column[Int]("counter", this) val counter = Column[Int]("counter", this)
@ -98,7 +98,7 @@ object RSource {
) )
) )
def incrementCounter(source: String, coll: Ident): ConnectionIO[Int] = def incrementCounter(source: String, coll: CollectiveId): ConnectionIO[Int] =
DML.update( DML.update(
table, table,
where(table.abbrev === source, table.cid === coll), where(table.abbrev === source, table.cid === coll),
@ -110,7 +110,7 @@ object RSource {
sql.query[Int].unique.map(_ > 0) sql.query[Int].unique.map(_ > 0)
} }
def existsByAbbrev(coll: Ident, abb: String): ConnectionIO[Boolean] = { def existsByAbbrev(coll: CollectiveId, abb: String): ConnectionIO[Boolean] = {
val sql = run( val sql = run(
select(count(table.sid)), select(count(table.sid)),
from(table), from(table),
@ -129,20 +129,20 @@ object RSource {
run(select(table.cid), from(table), table.sid === sourceId).query[Ident].option run(select(table.cid), from(table), table.sid === sourceId).query[Ident].option
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
order: Table => Column[_] order: Table => Column[_]
): ConnectionIO[Vector[RSource]] = ): ConnectionIO[Vector[RSource]] =
findAllSql(coll, order).query[RSource].to[Vector] findAllSql(coll, order).query[RSource].to[Vector]
private[records] def findAllSql( private[records] def findAllSql(
coll: Ident, coll: CollectiveId,
order: Table => Column[_] order: Table => Column[_]
): Fragment = { ): Fragment = {
val t = RSource.as("s") val t = RSource.as("s")
Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build
} }
def delete(sourceId: Ident, coll: Ident): ConnectionIO[Int] = def delete(sourceId: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(table, where(table.sid === sourceId, table.cid === coll)) DML.delete(table, where(table.sid === sourceId, table.cid === coll))
def removeFolder(folderId: Ident): ConnectionIO[Int] = { def removeFolder(folderId: Ident): ConnectionIO[Int] = {

View File

@ -18,7 +18,7 @@ import doobie.implicits._
case class RTag( case class RTag(
tagId: Ident, tagId: Ident,
collective: Ident, collective: CollectiveId,
name: String, name: String,
category: Option[String], category: Option[String],
created: Timestamp created: Timestamp
@ -29,7 +29,7 @@ object RTag {
val tableName = "tag" val tableName = "tag"
val tid = Column[Ident]("tid", this) val tid = Column[Ident]("tid", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val name = Column[String]("name", this) val name = Column[String]("name", this)
val category = Column[String]("category", this) val category = Column[String]("category", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
@ -62,7 +62,7 @@ object RTag {
sql.query[RTag].option sql.query[RTag].option
} }
def findByIdAndCollective(id: Ident, coll: Ident): ConnectionIO[Option[RTag]] = { def findByIdAndCollective(id: Ident, coll: CollectiveId): ConnectionIO[Option[RTag]] = {
val sql = run(select(T.all), from(T), T.tid === id && T.cid === coll) val sql = run(select(T.all), from(T), T.tid === id && T.cid === coll)
sql.query[RTag].option sql.query[RTag].option
} }
@ -74,7 +74,7 @@ object RTag {
} }
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
query: Option[String], query: Option[String],
order: Table => NonEmptyList[OrderBy] order: Table => NonEmptyList[OrderBy]
): ConnectionIO[Vector[RTag]] = { ): ConnectionIO[Vector[RTag]] = {
@ -121,7 +121,7 @@ object RTag {
def findAllByNameOrId( def findAllByNameOrId(
nameOrIds: List[String], nameOrIds: List[String],
coll: Ident coll: CollectiveId
): ConnectionIO[Vector[RTag]] = { ): ConnectionIO[Vector[RTag]] = {
val idList = val idList =
NonEmptyList.fromList(nameOrIds.flatMap(s => Ident.fromString(s).toOption)) NonEmptyList.fromList(nameOrIds.flatMap(s => Ident.fromString(s).toOption))
@ -142,7 +142,10 @@ object RTag {
} }
} }
def findOthers(coll: Ident, excludeTags: List[Ident]): ConnectionIO[List[RTag]] = { def findOthers(
coll: CollectiveId,
excludeTags: List[Ident]
): ConnectionIO[List[RTag]] = {
val excl = val excl =
NonEmptyList NonEmptyList
.fromList(excludeTags) .fromList(excludeTags)
@ -155,14 +158,14 @@ object RTag {
).orderBy(T.name.asc).build.query[RTag].to[List] ).orderBy(T.name.asc).build.query[RTag].to[List]
} }
def listCategories(coll: Ident): ConnectionIO[List[String]] = def listCategories(coll: CollectiveId): ConnectionIO[List[String]] =
Select( Select(
T.category.s, T.category.s,
from(T), from(T),
T.cid === coll && T.category.isNotNull T.cid === coll && T.category.isNotNull
).distinct.build.query[String].to[List] ).distinct.build.query[String].to[List]
def delete(tagId: Ident, coll: Ident): ConnectionIO[Int] = def delete(tagId: Ident, coll: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.tid === tagId && T.cid === coll) DML.delete(T, T.tid === tagId && T.cid === coll)
def sort(tags: List[RTag]): List[RTag] = def sort(tags: List[RTag]): List[RTag] =

View File

@ -53,7 +53,7 @@ object RTagItem {
def deleteItemTags(item: Ident): ConnectionIO[Int] = def deleteItemTags(item: Ident): ConnectionIO[Int] =
DML.delete(T, T.itemId === item) DML.delete(T, T.itemId === item)
def deleteItemTags(items: NonEmptyList[Ident], cid: Ident): ConnectionIO[Int] = def deleteItemTags(items: NonEmptyList[Ident], cid: CollectiveId): ConnectionIO[Int] =
DML.delete(T, T.itemId.in(RItem.filterItemsFragment(items, cid))) DML.delete(T, T.itemId.in(RItem.filterItemsFragment(items, cid)))
def deleteTag(tid: Ident): ConnectionIO[Int] = def deleteTag(tid: Ident): ConnectionIO[Int] =

View File

@ -59,24 +59,15 @@ object RTotp {
) )
) )
def setEnabled(account: AccountId, enabled: Boolean): ConnectionIO[Int] = def setEnabled(userId: Ident, enabled: Boolean): ConnectionIO[Int] =
for { DML.update(T, T.userId === userId, DML.set(T.enabled.setTo(enabled)))
userId <- RUser.findIdByAccount(account)
n <- userId match {
case Some(id) =>
DML.update(T, T.userId === id, DML.set(T.enabled.setTo(enabled)))
case None =>
0.pure[ConnectionIO]
}
} yield n
def isEnabled(accountId: AccountId): ConnectionIO[Boolean] = { def isEnabled(userId: Ident): ConnectionIO[Boolean] = {
val t = RTotp.as("t") val t = RTotp.as("t")
val u = RUser.as("u")
Select( Select(
select(count(t.userId)), select(count(t.userId)),
from(t).innerJoin(u, t.userId === u.uid), from(t),
u.login === accountId.user && u.cid === accountId.collective && t.enabled === true t.userId === userId && t.enabled === true
).build.query[Int].unique.map(_ > 0) ).build.query[Int].unique.map(_ > 0)
} }
@ -86,24 +77,45 @@ object RTotp {
): ConnectionIO[Option[RTotp]] = { ): ConnectionIO[Option[RTotp]] = {
val t = RTotp.as("t") val t = RTotp.as("t")
val u = RUser.as("u") val u = RUser.as("u")
val c = RCollective.as("c")
Select( Select(
select(t.all), select(t.all),
from(t).innerJoin(u, t.userId === u.uid), from(t).innerJoin(u, t.userId === u.uid).innerJoin(c, c.id === u.cid),
u.login === accountId.user && u.cid === accountId.collective && t.enabled === enabled u.login === accountId.user && c.name === accountId.collective && t.enabled === enabled
).build.query[RTotp].option
}
def findEnabledByUserId(
userId: Ident,
enabled: Boolean
): ConnectionIO[Option[RTotp]] = {
val t = RTotp.as("t")
Select(
select(t.all),
from(t),
t.userId === userId && t.enabled === enabled
).build.query[RTotp].option ).build.query[RTotp].option
} }
def existsByLogin(accountId: AccountId): ConnectionIO[Boolean] = { def existsByLogin(accountId: AccountId): ConnectionIO[Boolean] = {
val t = RTotp.as("t") val t = RTotp.as("t")
val u = RUser.as("u") val u = RUser.as("u")
val c = RCollective.as("c")
Select( Select(
select(count(t.userId)), select(count(t.userId)),
from(t).innerJoin(u, t.userId === u.uid), from(t).innerJoin(u, t.userId === u.uid).innerJoin(c, c.id === u.cid),
u.login === accountId.user && u.cid === accountId.collective u.login === accountId.user && c.name === accountId.collective
).build ).build
.query[Int] .query[Int]
.unique .unique
.map(_ > 0) .map(_ > 0)
} }
def existsByUserId(userId: Ident): ConnectionIO[Boolean] = {
val t = RTotp.as("t")
Select(select(count(t.userId)), from(t), t.userId === userId).build
.query[Int]
.unique
.map(_ > 0)
}
} }

View File

@ -7,8 +7,6 @@
package docspell.store.records package docspell.store.records
import cats.data.NonEmptyList import cats.data.NonEmptyList
import cats.data.OptionT
import cats.effect.Sync
import docspell.common._ import docspell.common._
import docspell.store.qb.DSL._ import docspell.store.qb.DSL._
@ -20,7 +18,7 @@ import doobie.implicits._
case class RUser( case class RUser(
uid: Ident, uid: Ident,
login: Ident, login: Ident,
cid: Ident, cid: CollectiveId,
password: Password, password: Password,
state: UserState, state: UserState,
source: AccountSource, source: AccountSource,
@ -29,8 +27,6 @@ case class RUser(
lastLogin: Option[Timestamp], lastLogin: Option[Timestamp],
created: Timestamp created: Timestamp
) { ) {
def accountId: AccountId =
AccountId(cid, login)
def idRef: IdRef = def idRef: IdRef =
IdRef(uid, login.id) IdRef(uid, login.id)
@ -41,7 +37,7 @@ object RUser {
def makeDefault( def makeDefault(
id: Ident, id: Ident,
login: Ident, login: Ident,
collName: Ident, collId: CollectiveId,
password: Password, password: Password,
source: AccountSource, source: AccountSource,
created: Timestamp created: Timestamp
@ -49,7 +45,7 @@ object RUser {
RUser( RUser(
id, id,
login, login,
collName, collId,
password, password,
UserState.Active, UserState.Active,
source, source,
@ -64,7 +60,7 @@ object RUser {
val uid = Column[Ident]("uid", this) val uid = Column[Ident]("uid", this)
val login = Column[Ident]("login", this) val login = Column[Ident]("login", this)
val cid = Column[Ident]("cid", this) val cid = Column[CollectiveId]("coll_id", this)
val password = Column[Password]("password", this) val password = Column[Password]("password", this)
val state = Column[UserState]("state", this) val state = Column[UserState]("state", this)
val source = Column[AccountSource]("account_source", this) val source = Column[AccountSource]("account_source", this)
@ -73,9 +69,6 @@ object RUser {
val lastLogin = Column[Timestamp]("lastlogin", this) val lastLogin = Column[Timestamp]("lastlogin", this)
val created = Column[Timestamp]("created", this) val created = Column[Timestamp]("created", this)
def isAccount(aid: AccountId) =
cid === aid.collective && login === aid.user
val all = val all =
NonEmptyList.of[Column[_]]( NonEmptyList.of[Column[_]](
uid, uid,
@ -125,9 +118,14 @@ object RUser {
} }
def findByAccount(aid: AccountId): ConnectionIO[Option[RUser]] = { def findByAccount(aid: AccountId): ConnectionIO[Option[RUser]] = {
val t = Table(None) val t = RUser.as("u")
val c = RCollective.as("c")
val sql = val sql =
run(select(t.all), from(t), t.cid === aid.collective && t.login === aid.user) run(
select(t.all),
from(t).innerJoin(c, c.id === t.cid),
c.name === aid.collective && t.login === aid.user
)
sql.query[RUser].option sql.query[RUser].option
} }
@ -137,20 +135,26 @@ object RUser {
sql.query[RUser].option sql.query[RUser].option
} }
def findAll(coll: Ident, order: Table => Column[_]): ConnectionIO[Vector[RUser]] = { def findAll(
coll: CollectiveId,
order: Table => Column[_]
): ConnectionIO[Vector[RUser]] = {
val t = Table(None) val t = Table(None)
val sql = Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build val sql = Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build
sql.query[RUser].to[Vector] sql.query[RUser].to[Vector]
} }
def findIdByAccount(accountId: AccountId): ConnectionIO[Option[Ident]] = def findIdByAccountId(accountId: AccountId): ConnectionIO[Option[Ident]] = {
val u = RUser.as("u")
val c = RCollective.as("c")
run( run(
select(T.uid), select(u.uid),
from(T), from(u).innerJoin(c, c.id === u.cid),
T.login === accountId.user && T.cid === accountId.collective u.login === accountId.user && c.name === accountId.collective
) )
.query[Ident] .query[Ident]
.option .option
}
case class IdAndLogin(uid: Ident, login: Ident) case class IdAndLogin(uid: Ident, login: Ident)
def getIdByIdOrLogin(idOrLogin: Ident): ConnectionIO[Option[IdAndLogin]] = def getIdByIdOrLogin(idOrLogin: Ident): ConnectionIO[Option[IdAndLogin]] =
@ -160,19 +164,19 @@ object RUser {
T.uid === idOrLogin || T.login === idOrLogin T.uid === idOrLogin || T.login === idOrLogin
).build.query[IdAndLogin].option ).build.query[IdAndLogin].option
def getIdByAccount(account: AccountId): ConnectionIO[Ident] = // def getIdByAccount(account: AccountId): ConnectionIO[Ident] =
OptionT(findIdByAccount(account)).getOrElseF( // OptionT(findIdByAccount(account)).getOrElseF(
Sync[ConnectionIO].raiseError( // Sync[ConnectionIO].raiseError(
new Exception(s"No user found for: ${account.asString}") // new Exception(s"No user found for: ${account.asString}")
) // )
) // )
def updateLogin(accountId: AccountId): ConnectionIO[Int] = { def updateLogin(accountId: AccountInfo): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
def stmt(now: Timestamp) = def stmt(now: Timestamp) =
DML.update( DML.update(
t, t,
t.cid === accountId.collective && t.login === accountId.user, t.cid === accountId.collectiveId && t.login === accountId.login,
DML.set( DML.set(
t.loginCount.increment(1), t.loginCount.increment(1),
t.lastLogin.setTo(now) t.lastLogin.setTo(now)
@ -181,16 +185,20 @@ object RUser {
Timestamp.current[ConnectionIO].flatMap(stmt) Timestamp.current[ConnectionIO].flatMap(stmt)
} }
def updatePassword(accountId: AccountId, hashedPass: Password): ConnectionIO[Int] = { def updatePassword(
collId: CollectiveId,
userId: Ident,
hashedPass: Password
): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
DML.update( DML.update(
t, t,
t.cid === accountId.collective && t.login === accountId.user && t.source === AccountSource.Local, t.cid === collId && t.uid === userId && t.source === AccountSource.Local,
DML.set(t.password.setTo(hashedPass)) DML.set(t.password.setTo(hashedPass))
) )
} }
def delete(user: Ident, coll: Ident): ConnectionIO[Int] = { def delete(user: Ident, coll: CollectiveId): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
DML.delete(t, t.cid === coll && t.login === user) DML.delete(t, t.cid === coll && t.login === user)
} }

View File

@ -184,11 +184,10 @@ object RUserEmail {
} }
private def findByAccount0( private def findByAccount0(
accId: AccountId, userId: Ident,
nameQ: Option[String], nameQ: Option[String],
exact: Boolean exact: Boolean
): Query0[RUserEmail] = { ): Query0[RUserEmail] = {
val user = RUser.as("u")
val email = as("m") val email = as("m")
val nameFilter = nameQ.map(s => val nameFilter = nameQ.map(s =>
@ -197,43 +196,32 @@ object RUserEmail {
val sql = Select( val sql = Select(
select(email.all), select(email.all),
from(email).innerJoin(user, email.uid === user.uid), from(email),
user.cid === accId.collective && user.login === accId.user &&? nameFilter email.uid === userId &&? nameFilter
).orderBy(email.name) ).orderBy(email.name)
sql.build.query[RUserEmail] sql.build.query[RUserEmail]
} }
def findByAccount( def findByAccount(
accId: AccountId, userId: Ident,
nameQ: Option[String] nameQ: Option[String]
): ConnectionIO[Vector[RUserEmail]] = ): ConnectionIO[Vector[RUserEmail]] =
findByAccount0(accId, nameQ, false).to[Vector] findByAccount0(userId, nameQ, false).to[Vector]
def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserEmail]] = def getByName(userId: Ident, name: Ident): ConnectionIO[Option[RUserEmail]] =
findByAccount0(accId, Some(name.id), true).option findByAccount0(userId, Some(name.id), true).option
def getById(id: Ident): ConnectionIO[Option[RUserEmail]] = { def getById(id: Ident): ConnectionIO[Option[RUserEmail]] = {
val t = Table(None) val t = Table(None)
run(select(t.all), from(t), t.id === id).query[RUserEmail].option run(select(t.all), from(t), t.id === id).query[RUserEmail].option
} }
def delete(accId: AccountId, connName: Ident): ConnectionIO[Int] = { def delete(userId: Ident, connName: Ident): ConnectionIO[Int] = {
val user = RUser.as("u")
val subsel = Select(
select(user.uid),
from(user),
user.cid === accId.collective && user.login === accId.user
)
val t = Table(None) val t = Table(None)
DML.delete(t, t.uid.in(subsel) && t.name === connName) DML.delete(t, t.uid === userId && t.name === connName)
} }
def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] =
getByName(accId, name).map(_.isDefined)
def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = { def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = {
val t = Table(None) val t = Table(None)
run(select(count(t.id)), from(t), t.uid === userId && t.name === connName) run(select(count(t.id)), from(t), t.uid === userId && t.name === connName)

View File

@ -171,12 +171,11 @@ object RUserImap {
} }
private def findByAccount0( private def findByAccount0(
accId: AccountId, userId: Ident,
nameQ: Option[String], nameQ: Option[String],
exact: Boolean exact: Boolean
): Query0[RUserImap] = { ): Query0[RUserImap] = {
val m = RUserImap.as("m") val m = RUserImap.as("m")
val u = RUser.as("u")
val nameFilter = val nameFilter =
nameQ.map { str => nameQ.map { str =>
@ -186,37 +185,37 @@ object RUserImap {
val sql = Select( val sql = Select(
select(m.all), select(m.all),
from(m).innerJoin(u, m.uid === u.uid), from(m),
u.cid === accId.collective && u.login === accId.user &&? nameFilter m.uid === userId &&? nameFilter
).orderBy(m.name).build ).orderBy(m.name).build
sql.query[RUserImap] sql.query[RUserImap]
} }
def findByAccount( def findByAccount(
accId: AccountId, userId: Ident,
nameQ: Option[String] nameQ: Option[String]
): ConnectionIO[Vector[RUserImap]] = ): ConnectionIO[Vector[RUserImap]] =
findByAccount0(accId, nameQ, false).to[Vector] findByAccount0(userId, nameQ, false).to[Vector]
def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserImap]] = def getByName(
findByAccount0(accId, Some(name.id), true).option userId: Ident,
name: Ident
): ConnectionIO[Option[RUserImap]] =
findByAccount0(userId, Some(name.id), true).option
def delete(accId: AccountId, connName: Ident): ConnectionIO[Int] = { def delete(
userId: Ident,
connName: Ident
): ConnectionIO[Int] = {
val t = Table(None) val t = Table(None)
val u = RUser.as("u")
val subsel =
Select(select(u.uid), from(u), u.cid === accId.collective && u.login === accId.user)
DML.delete( DML.delete(
t, t,
t.uid.in(subsel) && t.name === connName t.uid === userId && t.name === connName
) )
} }
def exists(accId: AccountId, name: Ident): ConnectionIO[Boolean] =
getByName(accId, name).map(_.isDefined)
def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = { def exists(userId: Ident, connName: Ident): ConnectionIO[Boolean] = {
val t = Table(None) val t = Table(None)
run(select(count(t.id)), from(t), t.uid === userId && t.name === connName) run(select(count(t.id)), from(t), t.uid === userId && t.name === connName)

View File

@ -26,7 +26,7 @@ object SourceData {
SourceData(s, Vector.empty) SourceData(s, Vector.empty)
def findAll( def findAll(
coll: Ident, coll: CollectiveId,
order: RSource.Table => Column[_] order: RSource.Table => Column[_]
): Stream[ConnectionIO, SourceData] = ): Stream[ConnectionIO, SourceData] =
findAllWithTags(RSource.findAllSql(coll, order).query[RSource].stream) findAllWithTags(RSource.findAllSql(coll, order).query[RSource].stream)
@ -83,7 +83,7 @@ object SourceData {
) )
} yield n0 + n1.sum } yield n0 + n1.sum
def delete(source: Ident, coll: Ident): ConnectionIO[Int] = def delete(source: Ident, coll: CollectiveId): ConnectionIO[Int] =
for { for {
n0 <- RTagSource.deleteSourceTags(source) n0 <- RTagSource.deleteSourceTags(source)
n1 <- RSource.delete(source, coll) n1 <- RSource.delete(source, coll)

View File

@ -6,22 +6,19 @@
package docspell.store.fts package docspell.store.fts
import java.time.LocalDate import java.time.{Instant, LocalDate}
import cats.effect.IO import cats.effect.IO
import cats.syntax.option._ import cats.syntax.option._
import cats.syntax.traverse._ import cats.syntax.traverse._
import fs2.Stream import fs2.Stream
import docspell.common._ import docspell.common._
import docspell.ftsclient.FtsResult import docspell.ftsclient.FtsResult
import docspell.ftsclient.FtsResult.{AttachmentData, ItemMatch} import docspell.ftsclient.FtsResult.{AttachmentData, ItemMatch}
import docspell.store._ import docspell.store._
import docspell.store.qb.DSL._ import docspell.store.qb.DSL._
import docspell.store.qb._ import docspell.store.qb._
import docspell.store.queries.{QItem, Query} import docspell.store.queries.{QItem, QLogin, Query}
import docspell.store.records.{RCollective, RItem} import docspell.store.records.{RCollective, RItem, RUser}
import doobie._ import doobie._
class TempFtsOpsTest extends DatabaseTest { class TempFtsOpsTest extends DatabaseTest {
@ -60,9 +57,10 @@ class TempFtsOpsTest extends DatabaseTest {
def prepareItems(store: Store[IO]) = def prepareItems(store: Store[IO]) =
for { for {
_ <- store.transact(RCollective.insert(makeCollective(DocspellSystem.user))) _ <- store.transact(RCollective.insert(makeCollective(CollectiveId(2))))
_ <- store.transact(RUser.insert(makeUser(CollectiveId(2))))
items = (0 until 200) items = (0 until 200)
.map(makeItem(_, DocspellSystem.user)) .map(makeItem(_, CollectiveId(2)))
.toList .toList
_ <- items.traverse(i => store.transact(RItem.insert(i))) _ <- items.traverse(i => store.transact(RItem.insert(i)))
} yield () } yield ()
@ -100,7 +98,9 @@ class TempFtsOpsTest extends DatabaseTest {
def assertQueryItem(store: Store[IO], ftsResults: Stream[ConnectionIO, FtsResult]) = def assertQueryItem(store: Store[IO], ftsResults: Stream[ConnectionIO, FtsResult]) =
for { for {
today <- IO(LocalDate.now()) today <- IO(LocalDate.now())
account = DocspellSystem.account account <- store
.transact(QLogin.findUser(DocspellSystem.account))
.map(_.get.account)
tempTable = ftsResults tempTable = ftsResults
.through(TempFtsOps.prepareTable(store.dbms, "fts_result")) .through(TempFtsOps.prepareTable(store.dbms, "fts_result"))
.compile .compile
@ -170,10 +170,31 @@ class TempFtsOpsTest extends DatabaseTest {
} }
} }
def makeCollective(cid: Ident): RCollective = def makeUser(cid: CollectiveId): RUser =
RCollective(cid, CollectiveState.Active, Language.English, true, ts) RUser(
Ident.unsafe("uid1"),
DocspellSystem.account.user,
cid,
Password("test"),
UserState.Active,
AccountSource.Local,
None,
0,
None,
Timestamp(Instant.now)
)
def makeItem(n: Int, cid: Ident): RItem = def makeCollective(cid: CollectiveId): RCollective =
RCollective(
cid,
DocspellSystem.account.collective,
CollectiveState.Active,
Language.English,
true,
ts
)
def makeItem(n: Int, cid: CollectiveId): RItem =
RItem( RItem(
id(s"item-$n"), id(s"item-$n"),
cid, cid,

View File

@ -40,7 +40,7 @@ class ItemQueryGeneratorTest extends FunSuite {
test("basic test") { test("basic test") {
val q = ItemQueryParser val q = ItemQueryParser
.parseUnsafe("(& name:hello date>=2020-02-01 (| source:expense* folder=test ))") .parseUnsafe("(& name:hello date>=2020-02-01 (| source:expense* folder=test ))")
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q) val cond = ItemQueryGenerator(now, tables, CollectiveId(1))(q)
val expect = val expect =
tables.item.name.like("hello") && tables.item.name.like("hello") &&
coalesce(tables.item.itemDate.s, tables.item.created.s) >= coalesce(tables.item.itemDate.s, tables.item.created.s) >=
@ -52,14 +52,14 @@ class ItemQueryGeneratorTest extends FunSuite {
test("!conc:*") { test("!conc:*") {
val q = ItemQueryParser.parseUnsafe("!conc:*") val q = ItemQueryParser.parseUnsafe("!conc:*")
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q) val cond = ItemQueryGenerator(now, tables, CollectiveId(1))(q)
val expect = not(tables.concPers.name.like("%") || tables.concEquip.name.like("%")) val expect = not(tables.concPers.name.like("%") || tables.concEquip.name.like("%"))
assertEquals(cond, expect) assertEquals(cond, expect)
} }
test("attach.id with wildcard") { test("attach.id with wildcard") {
val q = ItemQueryParser.parseUnsafe("attach.id=abcde*") val q = ItemQueryParser.parseUnsafe("attach.id=abcde*")
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q) val cond = ItemQueryGenerator(now, tables, CollectiveId(1))(q)
val expect = tables.item.id.in( val expect = tables.item.id.in(
Select( Select(
select(RAttachment.T.itemId), select(RAttachment.T.itemId),
@ -73,7 +73,7 @@ class ItemQueryGeneratorTest extends FunSuite {
test("attach.id with equals") { test("attach.id with equals") {
val q = ItemQueryParser.parseUnsafe("attach.id=abcde") val q = ItemQueryParser.parseUnsafe("attach.id=abcde")
val cond = ItemQueryGenerator(now, tables, Ident.unsafe("coll"))(q) val cond = ItemQueryGenerator(now, tables, CollectiveId(1))(q)
val expect = tables.item.id.in( val expect = tables.item.id.in(
Select( Select(
select(RAttachment.T.itemId), select(RAttachment.T.itemId),

View File

@ -7,9 +7,7 @@
package docspell.store.migrate package docspell.store.migrate
import cats.effect._ import cats.effect._
import docspell.store.{DatabaseTest, SchemaMigrateConfig, StoreFixture} import docspell.store.{DatabaseTest, SchemaMigrateConfig, StoreFixture}
import org.flywaydb.core.api.output.MigrateResult import org.flywaydb.core.api.output.MigrateResult
class MigrateTest extends DatabaseTest { class MigrateTest extends DatabaseTest {