From 77f22bb5ea9d46262cf421d7e09b1b103423f455 Mon Sep 17 00:00:00 2001 From: eikek Date: Tue, 5 Jul 2022 21:17:18 +0200 Subject: [PATCH] Adopt store module to new collective table --- .../docspell/backend/auth/AuthToken.scala | 9 +- .../scala/docspell/backend/auth/Login.scala | 37 ++++---- .../backend/ops/OQueryBookmarks.scala | 58 ++++++------ .../scala/docspell/backend/ops/OTotp.scala | 93 +++++++++---------- .../scala/docspell/common/AccountInfo.scala | 48 ++++++++++ .../scala/docspell/common/CollectiveId.scala | 26 ++++++ .../docspell/notification/api/Event.scala | 40 ++++---- .../notification/api/EventContext.scala | 2 +- .../restserver/routes/TotpRoutes.scala | 2 +- .../scala/db/migration/MigrationTasks.scala | 10 +- .../docspell/store/impl/DoobieMeta.scala | 3 + .../qb/generator/ItemQueryGenerator.scala | 8 +- .../docspell/store/queries/ItemData.scala | 2 +- .../docspell/store/queries/QAttachment.scala | 16 ++-- .../docspell/store/queries/QCollective.scala | 8 +- .../docspell/store/queries/QCustomField.scala | 10 +- .../docspell/store/queries/QFolder.scala | 74 ++++++++------- .../scala/docspell/store/queries/QItem.scala | 76 +++++++++------ .../scala/docspell/store/queries/QLogin.scala | 15 ++- .../scala/docspell/store/queries/QMails.scala | 13 ++- .../store/queries/QNotification.scala | 4 +- .../store/queries/QOrganization.scala | 22 ++--- .../scala/docspell/store/queries/QUser.scala | 25 ++--- .../scala/docspell/store/queries/Query.scala | 4 +- .../store/records/AddonRunConfigData.scala | 8 +- .../records/AddonRunConfigResolved.scala | 4 +- .../store/records/RAddonArchive.scala | 25 ++--- .../store/records/RAddonRunConfig.scala | 10 +- .../docspell/store/records/RAttachment.scala | 20 ++-- .../store/records/RAttachmentArchive.scala | 4 +- .../store/records/RAttachmentPreview.scala | 4 +- .../store/records/RAttachmentSource.scala | 2 +- .../store/records/RClassifierModel.scala | 17 ++-- .../store/records/RClassifierSetting.scala | 15 ++- .../records/RClientSettingsCollective.scala | 15 +-- .../docspell/store/records/RCollective.scala | 37 +++++--- .../store/records/RCollectivePassword.scala | 12 +-- .../docspell/store/records/RCustomField.scala | 18 ++-- .../store/records/RDownloadQuery.scala | 4 +- .../store/records/REmptyTrashSetting.scala | 10 +- .../docspell/store/records/REquipment.scala | 12 +-- .../docspell/store/records/RFolder.scala | 18 ++-- .../scala/docspell/store/records/RItem.scala | 73 +++++++++------ .../docspell/store/records/RItemLink.scala | 16 ++-- .../store/records/RNotificationChannel.scala | 20 ++-- .../records/RNotificationChannelGotify.scala | 16 +--- .../records/RNotificationChannelHttp.scala | 13 +-- .../records/RNotificationChannelMail.scala | 20 ++-- .../records/RNotificationChannelMatrix.scala | 18 ++-- .../store/records/RNotificationHook.scala | 18 ++-- .../store/records/ROrganization.scala | 20 ++-- .../docspell/store/records/RPerson.scala | 20 ++-- .../store/records/RQueryBookmark.scala | 65 ++++++------- .../scala/docspell/store/records/RShare.scala | 10 +- .../docspell/store/records/RSource.scala | 14 +-- .../scala/docspell/store/records/RTag.scala | 19 ++-- .../docspell/store/records/RTagItem.scala | 2 +- .../scala/docspell/store/records/RTotp.scala | 48 ++++++---- .../scala/docspell/store/records/RUser.scala | 66 +++++++------ .../docspell/store/records/RUserEmail.scala | 30 ++---- .../docspell/store/records/RUserImap.scala | 31 +++---- .../docspell/store/records/SourceData.scala | 4 +- .../docspell/store/fts/TempFtsOpsTest.scala | 45 ++++++--- .../generator/ItemQueryGeneratorTest.scala | 8 +- .../docspell/store/migrate/MigrateTest.scala | 2 - 65 files changed, 783 insertions(+), 635 deletions(-) create mode 100644 modules/common/src/main/scala/docspell/common/AccountInfo.scala create mode 100644 modules/common/src/main/scala/docspell/common/CollectiveId.scala diff --git a/modules/backend/src/main/scala/docspell/backend/auth/AuthToken.scala b/modules/backend/src/main/scala/docspell/backend/auth/AuthToken.scala index 45a34c9a..1c5a2c73 100644 --- a/modules/backend/src/main/scala/docspell/backend/auth/AuthToken.scala +++ b/modules/backend/src/main/scala/docspell/backend/auth/AuthToken.scala @@ -18,12 +18,13 @@ import scodec.bits.ByteVector case class AuthToken( nowMillis: Long, - account: AccountId, + account: AccountInfo, requireSecondFactor: Boolean, valid: Option[Duration], salt: String, sig: String ) { + def asString = valid match { case Some(v) => @@ -63,7 +64,7 @@ object AuthToken { for { millis <- TokenUtil.asInt(ms).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)) valid <- TokenUtil .asInt(vs) @@ -75,7 +76,7 @@ object AuthToken { for { millis <- TokenUtil.asInt(ms).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)) } yield AuthToken(millis, accId, twofac, None, salt, sig) @@ -84,7 +85,7 @@ object AuthToken { } def user[F[_]: Sync]( - accountId: AccountId, + accountId: AccountInfo, requireSecondFactor: Boolean, key: ByteVector, valid: Option[Duration] diff --git a/modules/backend/src/main/scala/docspell/backend/auth/Login.scala b/modules/backend/src/main/scala/docspell/backend/auth/Login.scala index 7d986cc7..0b36f733 100644 --- a/modules/backend/src/main/scala/docspell/backend/auth/Login.scala +++ b/modules/backend/src/main/scala/docspell/backend/auth/Login.scala @@ -96,10 +96,12 @@ object Login { for { data <- store.transact(QLogin.findUser(accountId)) _ <- logF.trace(s"Account lookup: $data") - res <- - if (data.exists(checkNoPassword(_, Set(AccountSource.OpenId)))) - doLogin(config, accountId, false) - else Result.invalidAuth.pure[F] + res <- data match { + case Some(d) if checkNoPassword(d, Set(AccountSource.OpenId)) => + doLogin(config, d.account, false) + case _ => + Result.invalidAuth.pure[F] + } } yield res def loginSession(config: Config)(sessionKey: String): F[Result] = @@ -122,9 +124,12 @@ object Login { for { data <- store.transact(QLogin.findUser(acc)) _ <- logF.trace(s"Account lookup: $data") - res <- - if (data.exists(check(up.pass))) doLogin(config, acc, up.rememberMe) - else Result.invalidAuth.pure[F] + res <- data match { + case Some(d) if check(up.pass)(d) => + doLogin(config, d.account, up.rememberMe) + case _ => + Result.invalidAuth.pure[F] + } } yield res case Left(_) => logF.info(s"User authentication failed for: ${up.hidePass}") *> @@ -162,7 +167,7 @@ object Login { (for { _ <- validateToken key <- EitherT.fromOptionF( - store.transact(RTotp.findEnabledByLogin(sf.token.account, true)), + store.transact(RTotp.findEnabledByLogin(sf.token.account.userId, true)), Result.invalidAuth ) now <- EitherT.right[Result](Timestamp.current[F]) @@ -175,13 +180,13 @@ object Login { } def loginRememberMe(config: Config)(token: String): F[Result] = { - def okResult(acc: AccountId) = + def okResult(acc: AccountInfo) = for { _ <- store.transact(RUser.updateLogin(acc)) token <- AuthToken.user(acc, false, config.serverSecret, None) } yield Result.ok(token, None) - def doLogin(rid: Ident) = + def rememberedLogin(rid: Ident) = (for { now <- OptionT.liftF(Timestamp.current[F]) minTime = now - config.rememberMe.valid @@ -214,7 +219,7 @@ object Login { else if (rt.isExpired(config.rememberMe.valid)) logF.info(s"RememberMe cookie expired ($rt).") *> Result.invalidTime .pure[F] - else doLogin(rt.rememberId) + else rememberedLogin(rt.rememberId) case Left(err) => logF.info(s"RememberMe cookie was invalid: $err") *> Result.invalidAuth .pure[F] @@ -245,11 +250,11 @@ object Login { private def doLogin( config: Config, - acc: AccountId, + acc: AccountInfo, rememberMe: Boolean ): F[Result] = for { - require2FA <- store.transact(RTotp.isEnabled(acc)) + require2FA <- store.transact(RTotp.isEnabled(acc.userId)) _ <- if (require2FA) ().pure[F] else store.transact(RUser.updateLogin(acc)) @@ -263,13 +268,11 @@ object Login { private def insertRememberToken( store: Store[F], - acc: AccountId, + acc: AccountInfo, config: Config ): F[RememberToken] = for { - uid <- OptionT(store.transact(RUser.findIdByAccount(acc))) - .getOrRaise(new IllegalStateException(s"No user_id found for account: $acc")) - rme <- RRememberMe.generate[F](uid) + rme <- RRememberMe.generate[F](acc.userId) _ <- store.transact(RRememberMe.insert(rme)) token <- RememberToken.user(rme.id, config.serverSecret) } yield token diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala b/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala index f1ef2396..a22a33e3 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OQueryBookmarks.scala @@ -6,7 +6,6 @@ package docspell.backend.ops -import cats.data.OptionT import cats.effect._ import cats.implicits._ @@ -19,19 +18,19 @@ import docspell.store.records._ 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( - account: AccountId, + account: AccountInfo, id: Ident, bookmark: OQueryBookmarks.NewBookmark ): F[UpdateResult] - def delete(account: AccountId, bookmark: Ident): F[Unit] + def delete(account: AccountInfo, bookmark: Ident): F[Unit] } object OQueryBookmarks { @@ -53,39 +52,43 @@ object OQueryBookmarks { def apply[F[_]: Sync](store: Store[F]): Resource[F, OQueryBookmarks[F]] = Resource.pure(new OQueryBookmarks[F] { - def getAll(account: AccountId): F[Vector[Bookmark]] = + def getAll(account: AccountInfo): F[Vector[Bookmark]] = store - .transact(RQueryBookmark.allForUser(account)) + .transact(RQueryBookmark.allForUser(account.collectiveId, account.userId)) .map(_.map(convert.toModel)) def findOne( - account: AccountId, + account: AccountInfo, nameOrId: String ): F[Option[OQueryBookmarks.Bookmark]] = store - .transact(RQueryBookmark.findByNameOrId(account, nameOrId)) + .transact( + RQueryBookmark.findByNameOrId(account.collectiveId, account.userId, nameOrId) + ) .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 = - RQueryBookmark.createNew(account, b.name, b.label, b.query, b.personal) - store.transact(RQueryBookmark.insertIfNotExists(account, record)) + RQueryBookmark.createNew( + 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( - store.transact { - (for { - userId <- OptionT(RUser.findIdByAccount(account)) - n <- OptionT.liftF( - RQueryBookmark.update(convert.toRecord(account, id, userId, b)) - ) - } yield n).getOrElse(0) - } + store.transact(RQueryBookmark.update(convert.toRecord(acc, id, b))) ) - def delete(account: AccountId, bookmark: Ident): F[Unit] = - store.transact(RQueryBookmark.deleteById(account.collective, bookmark)).as(()) + def delete(account: AccountInfo, bookmark: Ident): F[Unit] = + store.transact(RQueryBookmark.deleteById(account.collectiveId, bookmark)).as(()) }) private object convert { @@ -94,17 +97,16 @@ object OQueryBookmarks { Bookmark(r.id, r.name, r.label, r.query, r.isPersonal, r.created) def toRecord( - account: AccountId, + account: AccountInfo, id: Ident, - userId: Ident, b: NewBookmark ): RQueryBookmark = RQueryBookmark( id, b.name, b.label, - if (b.personal) userId.some else None, - account.collective, + if (b.personal) account.userId.some else None, + account.collectiveId, b.query, Timestamp.Epoch ) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OTotp.scala b/modules/backend/src/main/scala/docspell/backend/ops/OTotp.scala index 67ffcdf1..ea11ca6e 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OTotp.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OTotp.scala @@ -6,29 +6,29 @@ package docspell.backend.ops +import cats.data.OptionT import cats.effect._ import cats.implicits._ - import docspell.backend.ops.OTotp.{ConfirmResult, InitResult, OtpState} import docspell.common._ -import docspell.store.records.{RTotp, RUser} +import docspell.store.records.RTotp import docspell.store.{AddResult, Store, UpdateResult} import docspell.totp.{Key, OnetimePassword, Totp} trait OTotp[F[_]] { /** 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 * 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 * 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 * valid. @@ -57,7 +57,7 @@ object OTotp { sealed trait 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 = LenientUri.unsafe( s"otpauth://totp/$issuer:${accountId.asString}?secret=${key.data.toBase32}&issuer=$issuer" @@ -67,7 +67,7 @@ object OTotp { case object NotFound 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) def alreadyExists: InitResult = AlreadyExists @@ -85,47 +85,41 @@ object OTotp { Resource.pure[F, OTotp[F]](new OTotp[F] { val log = docspell.logging.getLogger[F] - def initialize(accountId: AccountId): F[InitResult] = + def initialize(accountId: AccountInfo): F[InitResult] = for { _ <- log.info(s"Initializing TOTP for account ${accountId.asString}") - userId <- store.transact(RUser.findIdByAccount(accountId)) - result <- userId match { - case Some(uid) => - for { - record <- RTotp.generate[F](uid, totp.settings.mac) - un <- store.transact(RTotp.updateDisabled(record)) - an <- - if (un != 0) - AddResult.entityExists("Entity exists, but update was ok").pure[F] - else store.add(RTotp.insert(record), RTotp.existsByLogin(accountId)) - innerResult <- - if (un != 0) InitResult.success(accountId, record.secret).pure[F] - else - an match { - case AddResult.EntityExists(msg) => - log.warn( - s"A totp record already exists for account '${accountId.asString}': $msg!" - ) *> - InitResult.alreadyExists.pure[F] - case AddResult.Failure(ex) => - log.warn( - s"Failed to setup totp record for '${accountId.asString}': ${ex.getMessage}" - ) *> - InitResult.failed(ex).pure[F] - case AddResult.Success => - 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] - } + result <- for { + record <- RTotp.generate[F](accountId.userId, totp.settings.mac) + un <- store.transact(RTotp.updateDisabled(record)) + an <- + if (un != 0) + AddResult.entityExists("Entity exists, but update was ok").pure[F] + else store.add(RTotp.insert(record), RTotp.existsByUserId(accountId.userId)) + innerResult <- + if (un != 0) InitResult.success(accountId, record.secret).pure[F] + else + an match { + case AddResult.EntityExists(msg) => + log.warn( + s"A totp record already exists for account '${accountId.asString}': $msg!" + ) *> + InitResult.alreadyExists.pure[F] + case AddResult.Failure(ex) => + log.warn( + s"Failed to setup totp record for '${accountId.asString}': ${ex.getMessage}" + ) *> + InitResult.failed(ex).pure[F] + case AddResult.Success => + InitResult.success(accountId, record.secret).pure[F] + } + } yield innerResult + } yield result - def confirmInit(accountId: AccountId, otp: OnetimePassword): F[ConfirmResult] = + def confirmInit(accountId: AccountInfo, otp: OnetimePassword): F[ConfirmResult] = for { _ <- 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] res <- key match { case None => @@ -134,7 +128,7 @@ object OTotp { val check = totp.checkPassword(r.secret, otp, now.value) if (check) store - .transact(RTotp.setEnabled(accountId, true)) + .transact(RTotp.setEnabled(accountId.userId, true)) .map(_ => ConfirmResult.Success) else ConfirmResult.Failed.pure[F] } @@ -154,7 +148,7 @@ object OTotp { val check = totp.checkPassword(r.secret, pw, now.value) if (check) UpdateResult.fromUpdate( - store.transact(RTotp.setEnabled(accountId, false)) + store.transact(RTotp.setEnabled(r.userId, false)) ) else log.info(s"TOTP code was invalid. Not disabling it.") *> UpdateResult @@ -163,12 +157,17 @@ object OTotp { } } yield res 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 { - record <- store.transact(RTotp.findEnabledByLogin(accountId, true)) + record <- store.transact(RTotp.findEnabledByUserId(acc.userId, true)) result = record match { case Some(r) => OtpState.Enabled(r.created) diff --git a/modules/common/src/main/scala/docspell/common/AccountInfo.scala b/modules/common/src/main/scala/docspell/common/AccountInfo.scala new file mode 100644 index 00000000..de02a6a5 --- /dev/null +++ b/modules/common/src/main/scala/docspell/common/AccountInfo.scala @@ -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 + } + } +} diff --git a/modules/common/src/main/scala/docspell/common/CollectiveId.scala b/modules/common/src/main/scala/docspell/common/CollectiveId.scala new file mode 100644 index 00000000..4a8fdfe4 --- /dev/null +++ b/modules/common/src/main/scala/docspell/common/CollectiveId.scala @@ -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) +} diff --git a/modules/notification/api/src/main/scala/docspell/notification/api/Event.scala b/modules/notification/api/src/main/scala/docspell/notification/api/Event.scala index f5aaee15..47519dfc 100644 --- a/modules/notification/api/src/main/scala/docspell/notification/api/Event.scala +++ b/modules/notification/api/src/main/scala/docspell/notification/api/Event.scala @@ -21,7 +21,7 @@ sealed trait Event { def eventType: EventType /** The user who caused it. */ - def account: AccountId + def account: AccountInfo /** The base url for generating links. This is dynamic. */ def baseUrl: Option[LenientUri] @@ -62,7 +62,7 @@ object Event { /** Event triggered when tags of one or more items have changed */ final case class TagsChanged( - account: AccountId, + account: AccountInfo, items: Nel[Ident], added: List[String], removed: List[String], @@ -75,11 +75,11 @@ object Event { items: Nel[Ident], added: List[String], removed: List[String] - ): (AccountId, Option[LenientUri]) => TagsChanged = + ): (AccountInfo, Option[LenientUri]) => TagsChanged = (acc, url) => TagsChanged(acc, items, added, removed, url) def sample[F[_]: Sync]( - account: AccountId, + account: AccountInfo, baseUrl: Option[LenientUri] ): F[TagsChanged] = for { @@ -91,7 +91,7 @@ object Event { /** Event triggered when a custom field on an item changes. */ final case class SetFieldValue( - account: AccountId, + account: AccountInfo, items: Nel[Ident], field: Ident, value: String, @@ -104,11 +104,11 @@ object Event { items: Nel[Ident], field: Ident, value: String - ): (AccountId, Option[LenientUri]) => SetFieldValue = + ): (AccountInfo, Option[LenientUri]) => SetFieldValue = (acc, url) => SetFieldValue(acc, items, field, value, url) def sample[F[_]: Sync]( - account: AccountId, + account: AccountInfo, baseUrl: Option[LenientUri] ): F[SetFieldValue] = for { @@ -118,7 +118,7 @@ object Event { } final case class DeleteFieldValue( - account: AccountId, + account: AccountInfo, items: Nel[Ident], field: Ident, baseUrl: Option[LenientUri] @@ -129,11 +129,11 @@ object Event { def partial( items: Nel[Ident], field: Ident - ): (AccountId, Option[LenientUri]) => DeleteFieldValue = + ): (AccountInfo, Option[LenientUri]) => DeleteFieldValue = (acc, url) => DeleteFieldValue(acc, items, field, url) def sample[F[_]: Sync]( - account: AccountId, + account: AccountInfo, baseUrl: Option[LenientUri] ): F[DeleteFieldValue] = for { @@ -147,7 +147,7 @@ object Event { * search results. */ final case class ItemSelection( - account: AccountId, + account: AccountInfo, items: Nel[Ident], more: Boolean, baseUrl: Option[LenientUri], @@ -158,7 +158,7 @@ object Event { case object ItemSelection extends EventType { def sample[F[_]: Sync]( - account: AccountId, + account: AccountInfo, baseUrl: Option[LenientUri] ): F[ItemSelection] = for { @@ -169,6 +169,7 @@ object Event { /** Event when a new job is added to the queue */ final case class JobSubmitted( + account: AccountInfo, jobId: Ident, group: Ident, task: Ident, @@ -179,26 +180,27 @@ object Event { ) extends Event { val eventType = JobSubmitted val baseUrl = None - def account: AccountId = AccountId(group, submitter) } case object JobSubmitted extends EventType { - def sample[F[_]: Sync](account: AccountId): F[JobSubmitted] = + def sample[F[_]: Sync](account: AccountInfo): F[JobSubmitted] = for { id <- Ident.randomId[F] ev = JobSubmitted( + account, id, account.collective, Ident.unsafe("process-something-task"), "", JobState.running, "Process 3 files", - account.user + account.login ) } yield ev } /** Event when a job is finished (in final state). */ final case class JobDone( + account: AccountInfo, jobId: Ident, group: Ident, task: Ident, @@ -211,20 +213,20 @@ object Event { ) extends Event { val eventType = JobDone val baseUrl = None - def account: AccountId = AccountId(group, submitter) } case object JobDone extends EventType { - def sample[F[_]: Sync](account: AccountId): F[JobDone] = + def sample[F[_]: Sync](account: AccountInfo): F[JobDone] = for { id <- Ident.randomId[F] ev = JobDone( + account, id, account.collective, Ident.unsafe("process-something-task"), "", JobState.running, "Process 3 files", - account.user, + account.login, Json.Null, None ) @@ -233,7 +235,7 @@ object Event { def sample[F[_]: Sync]( evt: EventType, - account: AccountId, + account: AccountInfo, baseUrl: Option[LenientUri] ): F[Event] = evt match { diff --git a/modules/notification/api/src/main/scala/docspell/notification/api/EventContext.scala b/modules/notification/api/src/main/scala/docspell/notification/api/EventContext.scala index e1bd8d27..c51497ad 100644 --- a/modules/notification/api/src/main/scala/docspell/notification/api/EventContext.scala +++ b/modules/notification/api/src/main/scala/docspell/notification/api/EventContext.scala @@ -25,7 +25,7 @@ trait EventContext { "eventType" -> event.eventType.asJson, "account" -> Json.obj( "collective" -> event.account.collective.asJson, - "user" -> event.account.user.asJson, + "user" -> event.account.login.asJson, "login" -> event.account.asJson ), "content" -> content diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/TotpRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/TotpRoutes.scala index 9b1838eb..1199eb06 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/TotpRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/TotpRoutes.scala @@ -72,7 +72,7 @@ object TotpRoutes { for { data <- req.as[OtpConfirm] result <- backend.totp.disable( - user.account, + user.account.asAccountId, OnetimePassword(data.otp.pass).some ) resp <- Ok(Conversions.basicResult(result, "TOTP setup disabled.")) diff --git a/modules/store/src/main/scala/db/migration/MigrationTasks.scala b/modules/store/src/main/scala/db/migration/MigrationTasks.scala index 39f53f58..7cda04d2 100644 --- a/modules/store/src/main/scala/db/migration/MigrationTasks.scala +++ b/modules/store/src/main/scala/db/migration/MigrationTasks.scala @@ -13,6 +13,7 @@ import cats.implicits._ import docspell.common._ import docspell.common.syntax.StringSyntax._ import docspell.notification.api._ +import docspell.store.queries.QLogin import docspell.store.records._ import db.migration.data._ @@ -122,7 +123,8 @@ trait MigrationTasks { private def saveChannel(ch: Channel, account: AccountId): ConnectionIO[ChannelRef] = (for { 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) _ <- OptionT.liftF(RNotificationChannel.insert(r)) _ <- OptionT.liftF( @@ -172,7 +174,8 @@ trait MigrationTasks { } for { - userId <- OptionT(RUser.findIdByAccount(old.account)) + userData <- OptionT(QLogin.findUser(old.account)) + userId = userData.account.userId id <- OptionT.liftF(Ident.randomId[ConnectionIO]) now <- OptionT.liftF(Timestamp.current[ConnectionIO]) chName = Some("migrate notify items") @@ -198,8 +201,7 @@ trait MigrationTasks { } 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 } - } diff --git a/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala b/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala index 6e919330..06c6f913 100644 --- a/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala +++ b/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala @@ -42,6 +42,9 @@ trait DoobieMeta extends EmilDoobieMeta { e.apply(a).noSpaces ) + implicit val metaCollectiveId: Meta[CollectiveId] = + Meta[Long].timap(CollectiveId.apply)(_.value) + implicit val metaAddonTriggerType: Meta[AddonTriggerType] = Meta[String].timap(AddonTriggerType.unsafeFromString)(_.name) diff --git a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala index f5e63394..d5aa6462 100644 --- a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala +++ b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala @@ -24,12 +24,12 @@ import doobie.util.Put 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] ): Condition = 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 )(implicit PT: Put[Timestamp]): Condition = expr match { @@ -217,7 +217,7 @@ object ItemQueryGenerator { case Date.Local(date) => date case Date.Millis(ms) => - Instant.ofEpochMilli(ms).atZone(Timestamp.UTC).toLocalDate() + Instant.ofEpochMilli(ms).atZone(Timestamp.UTC).toLocalDate case Date.Today => today } @@ -285,7 +285,7 @@ object ItemQueryGenerator { private def itemsWithCustomField( sel: RCustomField.Table => Condition - )(coll: Ident, op: QOp, value: String): Select = { + )(coll: CollectiveId, op: QOp, value: String): Select = { val cf = RCustomField.as("cf") val cfv = RCustomFieldValue.as("cfv") diff --git a/modules/store/src/main/scala/docspell/store/queries/ItemData.scala b/modules/store/src/main/scala/docspell/store/queries/ItemData.scala index ad5028da..9c47ab84 100644 --- a/modules/store/src/main/scala/docspell/store/queries/ItemData.scala +++ b/modules/store/src/main/scala/docspell/store/queries/ItemData.scala @@ -24,6 +24,6 @@ case class ItemData( relatedItems: Vector[ListItem] ) { - def filterCollective(coll: Ident): Option[ItemData] = + def filterCollective(coll: CollectiveId): Option[ItemData] = if (item.cid == coll) Some(this) else None } diff --git a/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala b/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala index 6e90cc86..ebaae377 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QAttachment.scala @@ -75,7 +75,7 @@ object QAttachment { */ def deleteSingleAttachment[F[_]: Sync]( store: Store[F] - )(attachId: Ident, coll: Ident): F[Int] = { + )(attachId: Ident, coll: CollectiveId): F[Int] = { val loadFiles = for { ra <- RAttachment.findByIdAndCollective(attachId, coll).map(_.map(_.fileId)) rs <- RAttachmentSource.findByIdAndCollective(attachId, coll).map(_.map(_.fileId)) @@ -138,7 +138,7 @@ object QAttachment { def deleteItemAttachments[F[_]: Sync]( store: Store[F] - )(itemId: Ident, coll: Ident): F[Int] = { + )(itemId: Ident, coll: CollectiveId): F[Int] = { val logger = docspell.logging.getLogger[F] for { ras <- store.transact(RAttachment.findByItemAndCollective(itemId, coll)) @@ -151,7 +151,10 @@ object QAttachment { } yield ns.sum } - def getMetaProposals(itemId: Ident, coll: Ident): ConnectionIO[MetaProposalList] = { + def getMetaProposals( + itemId: Ident, + coll: CollectiveId + ): ConnectionIO[MetaProposalList] = { val qa = Select( select(am.proposals), from(am) @@ -177,7 +180,7 @@ object QAttachment { def getAttachmentMeta( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Option[RAttachmentMeta]] = { val q = Select( select(am.all), @@ -204,14 +207,14 @@ object QAttachment { case class ContentAndName( id: Ident, item: Ident, - collective: Ident, + collective: CollectiveId, folder: Option[Ident], lang: Language, name: Option[String], content: Option[String] ) def allAttachmentMetaAndName( - coll: Option[Ident], + coll: Option[CollectiveId], itemIds: Option[Nel[Ident]], itemStates: Nel[ItemState], chunkSize: Int @@ -237,5 +240,4 @@ object QAttachment { ).build .query[ContentAndName] .streamWithChunkSize(chunkSize) - } diff --git a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala index 84d9c8fe..fc2740c3 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCollective.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCollective.scala @@ -30,7 +30,7 @@ object QCollective { 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("")) union( Select( @@ -70,7 +70,7 @@ object QCollective { tags: List[TagCount] ) - def getInsights(coll: Ident): ConnectionIO[InsightData] = { + def getInsights(coll: CollectiveId): ConnectionIO[InsightData] = { val q0 = Select( count(i.id).s, from(i), @@ -120,7 +120,7 @@ object QCollective { } 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 = Select( select(t.all).append(count(ti.itemId).s), @@ -132,7 +132,7 @@ object QCollective { } def getContacts( - coll: Ident, + coll: CollectiveId, query: Option[String], kind: Option[ContactKind] ): Stream[ConnectionIO, RContact] = { diff --git a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala index b7a2230f..6b6a3042 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QCustomField.scala @@ -23,17 +23,20 @@ object QCustomField { final case class CustomFieldData(field: RCustomField, usageCount: Int) def findAllLike( - coll: Ident, + coll: CollectiveId, nameQuery: Option[String], order: RCustomField.Table => Nel[OrderBy] ): ConnectionIO[Vector[CustomFieldData]] = 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 private def findFragment( - coll: Ident, + coll: CollectiveId, nameQuery: Option[String], fieldId: Option[Ident], order: RCustomField.Table => Nel[OrderBy] = t => Nel.of(t.name.asc) @@ -69,5 +72,4 @@ object QCustomField { .query[FieldValue] .to[List] } - } diff --git a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala index e5f5a1e8..2f025709 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QFolder.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QFolder.scala @@ -54,7 +54,7 @@ object QFolder { def exists: FolderChangeResult = Exists } - def delete(id: Ident, account: AccountId): ConnectionIO[FolderChangeResult] = { + def delete(id: Ident, userId: Ident): ConnectionIO[FolderChangeResult] = { def tryDelete = for { _ <- RItem.removeFolder(id) @@ -64,10 +64,9 @@ object QFolder { } yield FolderChangeResult.success (for { - uid <- OptionT(findUserId(account)) folder <- OptionT(RFolder.findById(id)) res <- OptionT.liftF( - if (folder.owner == uid) tryDelete + if (folder.owner == userId) tryDelete else FolderChangeResult.forbidden.pure[ConnectionIO] ) } yield res).getOrElse(FolderChangeResult.notFound) @@ -75,7 +74,7 @@ object QFolder { def changeName( folder: Ident, - account: AccountId, + userId: Ident, name: String ): ConnectionIO[FolderChangeResult] = { def tryUpdate(ns: RFolder): ConnectionIO[FolderChangeResult] = @@ -87,10 +86,9 @@ object QFolder { } yield res (for { - uid <- OptionT(findUserId(account)) folder <- OptionT(RFolder.findById(folder)) 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] ) } yield res).getOrElse(FolderChangeResult.notFound) @@ -98,7 +96,7 @@ object QFolder { def removeMember( folder: Ident, - account: AccountId, + userId: Ident, member: Ident ): ConnectionIO[FolderChangeResult] = { def tryRemove: ConnectionIO[FolderChangeResult] = @@ -110,10 +108,9 @@ object QFolder { } yield res (for { - uid <- OptionT(findUserId(account)) folder <- OptionT(RFolder.findById(folder)) res <- OptionT.liftF( - if (folder.owner == uid) tryRemove + if (folder.owner == userId) tryRemove else FolderChangeResult.forbidden.pure[ConnectionIO] ) } yield res).getOrElse(FolderChangeResult.notFound) @@ -121,7 +118,7 @@ object QFolder { def addMember( folder: Ident, - account: AccountId, + userId: Ident, member: Ident ): ConnectionIO[FolderChangeResult] = { def tryAdd: ConnectionIO[FolderChangeResult] = @@ -134,16 +131,19 @@ object QFolder { } yield res (for { - uid <- OptionT(findUserId(account)) folder <- OptionT(RFolder.findById(folder)) res <- OptionT.liftF( - if (folder.owner == uid) tryAdd + if (folder.owner == userId) tryAdd else FolderChangeResult.forbidden.pure[ConnectionIO] ) } 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 member = RFolderMember.as("m") val folder = RFolder.as("s") @@ -153,12 +153,19 @@ object QFolder { from(member) .innerJoin(user, member.user === user.uid) .innerJoin(folder, member.folder === folder.id), - member.folder === id && folder.collective === account.collective + member.folder === id && folder.collective === collectiveId ).query[IdRef].to[Vector] (for { 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) ) memb <- OptionT.liftF(memberQ) @@ -166,7 +173,8 @@ object QFolder { } def findAll( - account: AccountId, + collectiveId: CollectiveId, + userId: Ident, idQ: Option[Ident], ownerLogin: Option[Ident], nameQ: Option[String], @@ -199,22 +207,20 @@ object QFolder { val folder = RFolder.as("s") val memlogin = TableDef("memberlogin") val mlFolder = Column[Ident]("folder", memlogin) - val mlLogin = Column[Ident]("login", memlogin) + val mlUser = Column[Ident]("user_id", memlogin) withCte( memlogin -> union( Select( - select(member.folder.as(mlFolder), user.login.as(mlLogin)), + select(member.folder.as(mlFolder), member.user.as(mlUser)), from(member) - .innerJoin(user, user.uid === member.user) .innerJoin(folder, folder.id === member.folder), - folder.collective === account.collective + folder.collective === collectiveId ), Select( - select(folder.id.as(mlFolder), user.login.as(mlLogin)), - from(folder) - .innerJoin(user, user.uid === folder.owner), - folder.collective === account.collective + select(folder.id.as(mlFolder), folder.owner.as(mlUser)), + from(folder), + folder.collective === collectiveId ) ) )( @@ -228,7 +234,7 @@ object QFolder { Select( select(countAll > 0), from(memlogin), - mlFolder === folder.id && mlLogin === account.user + mlFolder === folder.id && mlUser === userId ).as("member"), Select( select(countAll - 1), @@ -239,7 +245,7 @@ object QFolder { from(folder) .innerJoin(user, user.uid === folder.owner), where( - folder.collective === account.collective &&? + folder.collective === collectiveId &&? idQ.map(id => folder.id === id) &&? nameQ.map(q => folder.name.like(s"%${q.toLowerCase}%")) &&? ownerLogin.map(login => user.login === login) @@ -249,7 +255,7 @@ object QFolder { } /** 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 f = RFolder.as("f") val m = RFolderMember.as("m") @@ -257,21 +263,21 @@ object QFolder { Select( select(f.id), from(f).innerJoin(user, f.owner === user.uid), - f.collective === account.collective && user.login === account.user + f.collective === cid && user.uid === userId ), Select( select(m.folder), from(m) .innerJoin(f, f.id === m.folder) .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]] = - findMemberFolderIds(account).build.query[Ident].to[Set] - - private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] = - RUser.findByAccount(account).map(_.map(_.uid)) + def getMemberFolders( + collectiveId: CollectiveId, + userId: Ident + ): ConnectionIO[Set[Ident]] = + findMemberFolderIds(collectiveId, userId).build.query[Ident].to[Set] } diff --git a/modules/store/src/main/scala/docspell/store/queries/QItem.scala b/modules/store/src/main/scala/docspell/store/queries/QItem.scala index 172d38a9..5234940a 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QItem.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QItem.scala @@ -64,7 +64,7 @@ object QItem extends FtsSupport { val cteFts = ftsTable.map(cteTable) val sql = 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) .limit(batch) .build @@ -73,7 +73,7 @@ object QItem extends FtsSupport { sql.query[ListItem].stream } - def findItem(id: Ident, collective: Ident): ConnectionIO[Option[ItemData]] = { + def findItem(id: Ident, collective: CollectiveId): ConnectionIO[Option[ItemData]] = { val cq = Select( 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 .findLinked(collective, id) .map(v => Nel.fromList(v.toList)) @@ -131,7 +134,8 @@ object QItem extends FtsSupport { case Some(nel) => val expr = 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( Query.Fix(account, Some(expr), None), @@ -159,7 +163,7 @@ object QItem extends FtsSupport { noteMaxLen: Int, ftsTable: Option[RFtsResult.Table] ): Select.Ordered = { - val coll = q.account.collective + val coll = q.account.collectiveId Select( select( @@ -197,7 +201,9 @@ object QItem extends FtsSupport { i.cid === coll &&? q.query.map(qs => queryCondFromExpr(today, coll, qs)) && or( i.folder.isNull, - i.folder.in(QFolder.findMemberFolderIds(q.account)) + i.folder.in( + QFolder.findMemberFolderIds(q.account.collectiveId, q.account.userId) + ) ) ) ).orderBy( @@ -223,7 +229,7 @@ object QItem extends FtsSupport { 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) def findFiles( @@ -288,12 +294,20 @@ object QItem extends FtsSupport { .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")) 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 { case Query.QueryExpr(Some(expr)) => queryCondFromExpr(today, coll, expr) @@ -340,7 +354,7 @@ object QItem extends FtsSupport { .joinFtsIdOnly(i, ftsTable) .withSelect(select(tag.category).append(countDistinct(i.id).as("num"))) .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) .build .query[CategoryCount] @@ -348,7 +362,7 @@ object QItem extends FtsSupport { for { existing <- catCloud - allCats <- RTag.listCategories(q.fix.account.collective) + allCats <- RTag.listCategories(q.fix.account.collectiveId) other = allCats.diff(existing.flatMap(_.category)) } yield existing ++ other.map(n => CategoryCount(n.some, 0)) } @@ -366,7 +380,7 @@ object QItem extends FtsSupport { .joinFtsIdOnly(i, ftsTable) .withSelect(select(tag.all).append(countDistinct(i.id).as("num"))) .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) .build .query[TagCount] @@ -376,7 +390,7 @@ object QItem extends FtsSupport { // are not included they are fetched separately for { 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)) } @@ -386,7 +400,7 @@ object QItem extends FtsSupport { findItemsBase(q.fix, today, 0, None).unwrap .joinFtsIdOnly(i, ftsTable) .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 .query[Int] .unique @@ -422,7 +436,7 @@ object QItem extends FtsSupport { .joinFtsIdOnly(i, ftsTable) .withSelect(select(idCol, nameCol).append(count(idCol).as("num"))) .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) .build @@ -437,7 +451,7 @@ object QItem extends FtsSupport { .joinFtsIdOnly(i, ftsTable) .withSelect(select(f.id, f.name, f.owner, fu.login).append(count(i.id).as("num"))) .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) .build .query[FolderCount] @@ -455,7 +469,7 @@ object QItem extends FtsSupport { val base = findItemsBase(q.fix, today, 0, None).unwrap .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) .groupBy(GroupBy(cf.all)) @@ -507,7 +521,7 @@ object QItem extends FtsSupport { * implemented by running an additional query per item. */ def findItemsWithTags( - collective: Ident, + collective: CollectiveId, search: Stream[ConnectionIO, ListItem] ): Stream[ConnectionIO, ListItemWithTags] = { def findTag( @@ -555,7 +569,9 @@ object QItem extends FtsSupport { a.itemId === item ).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 { rn <- QAttachment.deleteItemAttachments(store)(itemId, collective) tn <- store.transact(RTagItem.deleteItemTags(itemId)) @@ -607,7 +623,7 @@ object QItem extends FtsSupport { def findByChecksum( checksum: String, - collective: Ident, + collective: CollectiveId, excludeFileMeta: Set[FileKey] ): ConnectionIO[Vector[RItem]] = { val qq = findByChecksumQuery(checksum, collective, excludeFileMeta).build @@ -617,7 +633,7 @@ object QItem extends FtsSupport { def findByChecksumQuery( checksum: String, - collective: Ident, + collective: CollectiveId, excludeFileMeta: Set[FileKey] ): Select = { val m1 = RFileMeta.as("m1") @@ -657,7 +673,7 @@ object QItem extends FtsSupport { language: Language ) def allNameAndNotes( - coll: Option[Ident], + coll: Option[CollectiveId], itemIds: Option[Nel[Ident]], chunkSize: Int ): Stream[ConnectionIO, NameAndNotes] = { @@ -677,7 +693,7 @@ object QItem extends FtsSupport { } def findAllNewesFirst( - collective: Ident, + collective: CollectiveId, chunkSize: Int, limit: Batch ): Stream[ConnectionIO, Ident] = { @@ -691,7 +707,7 @@ object QItem extends FtsSupport { } def resolveTextAndTag( - collective: Ident, + collective: CollectiveId, itemId: Ident, tagCategory: String, maxLen: Int, @@ -724,7 +740,7 @@ object QItem extends FtsSupport { } def resolveTextAndCorrOrg( - collective: Ident, + collective: CollectiveId, itemId: Ident, maxLen: Int, pageSep: String @@ -741,7 +757,7 @@ object QItem extends FtsSupport { } def resolveTextAndCorrPerson( - collective: Ident, + collective: CollectiveId, itemId: Ident, maxLen: Int, pageSep: String @@ -758,7 +774,7 @@ object QItem extends FtsSupport { } def resolveTextAndConcPerson( - collective: Ident, + collective: CollectiveId, itemId: Ident, maxLen: Int, pageSep: String @@ -775,7 +791,7 @@ object QItem extends FtsSupport { } def resolveTextAndConcEquip( - collective: Ident, + collective: CollectiveId, itemId: Ident, maxLen: Int, pageSep: String @@ -797,12 +813,12 @@ object QItem extends FtsSupport { m.content.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 ): ConnectionIO[TextAndTag] = for { _ <- 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] _ <- logger.trace( diff --git a/modules/store/src/main/scala/docspell/store/queries/QLogin.scala b/modules/store/src/main/scala/docspell/store/queries/QLogin.scala index 280ab131..c79c4a8d 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QLogin.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QLogin.scala @@ -21,7 +21,7 @@ object QLogin { private[this] val logger = docspell.logging.getLogger[ConnectionIO] case class Data( - account: AccountId, + account: AccountInfo, password: Password, collectiveState: CollectiveState, userState: UserState, @@ -35,7 +35,16 @@ object QLogin { val coll = RCollective.as("c") val sql = 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), where(user, coll) ).build @@ -44,7 +53,7 @@ object QLogin { } 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]] = findUser0((user, _) => user.uid === userId) diff --git a/modules/store/src/main/scala/docspell/store/queries/QMails.scala b/modules/store/src/main/scala/docspell/store/queries/QMails.scala index f0081492..43156254 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QMails.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QMails.scala @@ -23,21 +23,27 @@ object QMails { private val mailitem = RSentMailItem.as("mi") private val user = RUser.as("u") - def delete(coll: Ident, mailId: Ident): ConnectionIO[Int] = + def delete(coll: CollectiveId, mailId: Ident): ConnectionIO[Int] = (for { m <- OptionT(findMail(coll, mailId)) k <- OptionT.liftF(RSentMailItem.deleteMail(mailId)) n <- OptionT.liftF(RSentMail.delete(m._1.id)) } 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 .where(smail.id === mailId && item.cid === coll) .build .query[(RSentMail, Ident)] .option - def findMails(coll: Ident, itemId: Ident): ConnectionIO[Vector[(RSentMail, Ident)]] = + def findMails( + coll: CollectiveId, + itemId: Ident + ): ConnectionIO[Vector[(RSentMail, Ident)]] = partialFind .where(mailitem.itemId === itemId && item.cid === coll) .orderBy(smail.created.desc) @@ -53,5 +59,4 @@ object QMails { .innerJoin(item, mailitem.itemId === item.id) .innerJoin(user, user.uid === smail.uid) ) - } diff --git a/modules/store/src/main/scala/docspell/store/queries/QNotification.scala b/modules/store/src/main/scala/docspell/store/queries/QNotification.scala index e7f0175b..b03eb187 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QNotification.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QNotification.scala @@ -26,7 +26,7 @@ object QNotification { def findChannelsForEvent(event: Event): ConnectionIO[Vector[HookChannel]] = for { - hooks <- listHooks(event.account.collective, event.eventType) + hooks <- listHooks(event.account.collectiveId, event.eventType) chs <- hooks.traverse(h => listChannels(h.id) .flatMap(_.flatTraverse(hc => readHookChannel(h.uid, hc))) @@ -42,7 +42,7 @@ object QNotification { ) def listHooks( - collective: Ident, + collective: CollectiveId, eventType: EventType ): ConnectionIO[Vector[RNotificationHook]] = run( diff --git a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala index 10c457a8..9647f46c 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala @@ -25,7 +25,7 @@ object QOrganization { private val org = ROrganization.as("o") def findOrgAndContact( - coll: Ident, + coll: CollectiveId, query: Option[String], order: ROrganization.Table => Nel[OrderBy] ): Stream[ConnectionIO, (ROrganization, Vector[RContact])] = { @@ -50,7 +50,7 @@ object QOrganization { } def getOrgAndContact( - coll: Ident, + coll: CollectiveId, orgId: Ident ): ConnectionIO[Option[(ROrganization, Vector[RContact])]] = { val sql = run( @@ -72,7 +72,7 @@ object QOrganization { } def findPersonAndContact( - coll: Ident, + coll: CollectiveId, query: Option[String], order: (RPerson.Table, ROrganization.Table) => Nel[OrderBy] ): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = { @@ -99,7 +99,7 @@ object QOrganization { } def getPersonAndContact( - coll: Ident, + coll: CollectiveId, persId: Ident ): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = { val sql = @@ -125,7 +125,7 @@ object QOrganization { } def findPersonByContact( - coll: Ident, + coll: CollectiveId, value: String, ck: Option[ContactKind], use: Option[Nel[PersonUse]] @@ -141,7 +141,7 @@ object QOrganization { def addOrg[F[_]]( org: ROrganization, contacts: Seq[RContact], - cid: Ident + cid: CollectiveId ): Store[F] => F[AddResult] = { val insert = for { n <- ROrganization.insert(org) @@ -156,7 +156,7 @@ object QOrganization { def addPerson[F[_]]( person: RPerson, contacts: Seq[RContact], - cid: Ident + cid: CollectiveId ): Store[F] => F[AddResult] = { val insert = for { n <- RPerson.insert(person) @@ -171,7 +171,7 @@ object QOrganization { def updateOrg[F[_]]( org: ROrganization, contacts: Seq[RContact], - cid: Ident + cid: CollectiveId ): Store[F] => F[AddResult] = { val insert = for { n <- ROrganization.update(org) @@ -187,7 +187,7 @@ object QOrganization { def updatePerson[F[_]]( person: RPerson, contacts: Seq[RContact], - cid: Ident + cid: CollectiveId ): Store[F] => F[AddResult] = { val insert = for { n <- RPerson.update(person) @@ -200,7 +200,7 @@ object QOrganization { store => store.add(insert, exists) } - def deleteOrg(orgId: Ident, collective: Ident): ConnectionIO[Int] = + def deleteOrg(orgId: Ident, collective: CollectiveId): ConnectionIO[Int] = for { n0 <- RItem.removeCorrOrg(collective, orgId) n1 <- RContact.deleteOrg(orgId) @@ -208,7 +208,7 @@ object QOrganization { n3 <- ROrganization.delete(orgId, collective) } yield n0 + n1 + n2 + n3 - def deletePerson(personId: Ident, collective: Ident): ConnectionIO[Int] = + def deletePerson(personId: Ident, collective: CollectiveId): ConnectionIO[Int] = for { n0 <- RItem.removeCorrPerson(collective, personId) n1 <- RItem.removeConcPerson(collective, personId) diff --git a/modules/store/src/main/scala/docspell/store/queries/QUser.scala b/modules/store/src/main/scala/docspell/store/queries/QUser.scala index a9670c46..f366eb51 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QUser.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QUser.scala @@ -24,40 +24,35 @@ object QUser { shares: Int ) - def getUserData(accountId: AccountId): ConnectionIO[UserData] = { + def getUserData(cid: CollectiveId, uid: Ident): ConnectionIO[UserData] = { val folder = RFolder.as("f") val mail = RSentMail.as("m") val mitem = RSentMailItem.as("mi") - val user = RUser.as("u") val share = RShare.as("s") for { - uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe(""))) folders <- run( select(folder.name), from(folder), - folder.owner === uid && folder.collective === accountId.collective + folder.owner === uid && folder.collective === cid ).query[Ident].to[List] mails <- run( select(count(mail.id)), from(mail) - .innerJoin(mitem, mail.id === mitem.sentMailId) - .innerJoin(user, user.uid === mail.uid), - user.login === accountId.user && user.cid === accountId.collective + .innerJoin(mitem, mail.id === mitem.sentMailId), + mail.uid === uid ).query[Int].unique shares <- run( select(count(share.id)), - from(share) - .innerJoin(user, user.uid === share.userId), - user.login === accountId.user && user.cid === accountId.collective + from(share), + share.userId === uid ).query[Int].unique } yield UserData(folders, mails, shares) } - def deleteUserAndData(accountId: AccountId): ConnectionIO[Int] = + def deleteUserAndData(uid: Ident): ConnectionIO[Int] = for { - uid <- loadUserId(accountId).map(_.getOrElse(Ident.unsafe(""))) - _ <- logger.info(s"Remove user ${accountId.asString} (uid=${uid.id})") + _ <- logger.info(s"Remove user ${uid.id}") n1 <- deleteUserFolders(uid) @@ -125,8 +120,4 @@ object QUser { n2 <- DML.delete(imap, imap.uid === uid) } yield n1 + n2 } - - private def loadUserId(id: AccountId): ConnectionIO[Option[Ident]] = - RUser.findIdByAccount(id) - } diff --git a/modules/store/src/main/scala/docspell/store/queries/Query.scala b/modules/store/src/main/scala/docspell/store/queries/Query.scala index 98c86d23..074d654f 100644 --- a/modules/store/src/main/scala/docspell/store/queries/Query.scala +++ b/modules/store/src/main/scala/docspell/store/queries/Query.scala @@ -58,7 +58,7 @@ object Query { Query(fix, QueryExpr(None)) case class Fix( - account: AccountId, + account: AccountInfo, query: Option[ItemQuery.Expr], order: Option[OrderSelect => OrderBy] ) { @@ -87,7 +87,7 @@ object Query { QueryExpr(Some(q)) } - def all(account: AccountId): Query = + def all(account: AccountInfo): Query = Query(Fix(account, None, None), QueryExpr(None)) } diff --git a/modules/store/src/main/scala/docspell/store/records/AddonRunConfigData.scala b/modules/store/src/main/scala/docspell/store/records/AddonRunConfigData.scala index 69c6826a..39e4aff6 100644 --- a/modules/store/src/main/scala/docspell/store/records/AddonRunConfigData.scala +++ b/modules/store/src/main/scala/docspell/store/records/AddonRunConfigData.scala @@ -11,7 +11,7 @@ import cats.syntax.all._ import fs2.Stream import docspell.addons.AddonTriggerType -import docspell.common.{Ident, Timestamp} +import docspell.common.{CollectiveId, Ident, Timestamp} import docspell.store.qb.DSL._ import docspell.store.qb._ @@ -26,7 +26,7 @@ case class AddonRunConfigData( object AddonRunConfigData { def findAll( - cid: Ident, + cid: CollectiveId, enabled: Option[Boolean] = None, trigger: Set[AddonTriggerType] = Set.empty, configIds: Set[Ident] = Set.empty @@ -88,7 +88,7 @@ object AddonRunConfigData { } yield n1 + tts.sum + tas.sum def findEnabledRef( - cid: Ident, + cid: CollectiveId, taskId: Ident ): ConnectionIO[List[(RAddonArchive, RAddonRunConfigAddon)]] = { val run = RAddonRunConfig.as("run") @@ -108,7 +108,7 @@ object AddonRunConfigData { } def findEnabledRefs( - cid: Ident, + cid: CollectiveId, trigger: AddonTriggerType, addonTaskIds: Set[Ident] ): Stream[ConnectionIO, (RAddonRunConfig, List[(RAddonArchive, String)])] = { diff --git a/modules/store/src/main/scala/docspell/store/records/AddonRunConfigResolved.scala b/modules/store/src/main/scala/docspell/store/records/AddonRunConfigResolved.scala index 94ddb6bc..19eb474f 100644 --- a/modules/store/src/main/scala/docspell/store/records/AddonRunConfigResolved.scala +++ b/modules/store/src/main/scala/docspell/store/records/AddonRunConfigResolved.scala @@ -46,7 +46,7 @@ object AddonRunConfigResolved { def findById( configId: Ident, - collective: Ident, + collective: CollectiveId, enabled: Option[Boolean] ): ConnectionIO[Option[AddonRunConfigResolved]] = (for { @@ -56,7 +56,7 @@ object AddonRunConfigResolved { } yield AddonRunConfigResolved(cfg, refs, tri)).value def findAllForCollective( - cid: Ident, + cid: CollectiveId, enabled: Option[Boolean], trigger: Set[AddonTriggerType], configIds: Set[Ident] diff --git a/modules/store/src/main/scala/docspell/store/records/RAddonArchive.scala b/modules/store/src/main/scala/docspell/store/records/RAddonArchive.scala index 05e2fc15..3411743b 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAddonArchive.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAddonArchive.scala @@ -21,7 +21,7 @@ import io.circe.{Decoder, Encoder} final case class RAddonArchive( id: Ident, - cid: Ident, + cid: CollectiveId, fileId: FileKey, originalUrl: Option[LenientUri], name: String, @@ -32,7 +32,7 @@ final case class RAddonArchive( ) { def nameAndVersion: String = - s"${name}-${version}" + s"$name-$version" def isUnchanged(meta: AddonMeta): Boolean = name == meta.meta.name && @@ -60,7 +60,7 @@ object RAddonArchive { val tableName = "addon_archive" 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 originalUrl = Column[LenientUri]("original_url", this) val name = Column[String]("name", this) @@ -85,7 +85,7 @@ object RAddonArchive { def apply( id: Ident, - cid: Ident, + cid: CollectiveId, fileId: FileKey, originalUrl: Option[LenientUri], meta: AddonMeta, @@ -116,14 +116,14 @@ object RAddonArchive { 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(count(T.id)), from(T), T.cid === cid && T.originalUrl === url ).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(T.all), from(T), @@ -131,7 +131,7 @@ object RAddonArchive { ).build.query[RAddonArchive].option def findByNameAndVersion( - cid: Ident, + cid: CollectiveId, name: String, version: String ): ConnectionIO[Option[RAddonArchive]] = @@ -141,14 +141,17 @@ object RAddonArchive { T.cid === cid && T.name === name && T.version === version ).build.query[RAddonArchive].option - def findById(cid: Ident, id: Ident): ConnectionIO[Option[RAddonArchive]] = + def findById(cid: CollectiveId, id: Ident): ConnectionIO[Option[RAddonArchive]] = Select( select(T.all), from(T), T.cid === cid && T.id === id ).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(T.all), from(T), @@ -169,14 +172,14 @@ object RAddonArchive { ) ) - def listAll(cid: Ident): ConnectionIO[List[RAddonArchive]] = + def listAll(cid: CollectiveId): ConnectionIO[List[RAddonArchive]] = Select( select(T.all), from(T), T.cid === cid ).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) implicit val jsonDecoder: Decoder[RAddonArchive] = deriveDecoder diff --git a/modules/store/src/main/scala/docspell/store/records/RAddonRunConfig.scala b/modules/store/src/main/scala/docspell/store/records/RAddonRunConfig.scala index 70460aaa..d6b04ef7 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAddonRunConfig.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAddonRunConfig.scala @@ -18,7 +18,7 @@ import doobie.implicits._ final case class RAddonRunConfig( id: Ident, - cid: Ident, + cid: CollectiveId, userId: Option[Ident], name: String, enabled: Boolean, @@ -30,7 +30,7 @@ object RAddonRunConfig { val tableName = "addon_run_config" 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 name = Column[String]("name", 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 .query[RAddonRunConfig] .option def findByCollective( - cid: Ident, + cid: CollectiveId, enabled: Option[Boolean], trigger: Set[AddonTriggerType], configIds: Set[Ident] @@ -94,6 +94,6 @@ object RAddonRunConfig { 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) } diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala index b1a9dcc9..7ebd4a50 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachment.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachment.scala @@ -125,7 +125,7 @@ object RAttachment { def updateName( attachId: Ident, - collective: Ident, + collective: CollectiveId, aname: Option[String] ): ConnectionIO[Int] = { val update = DML.update(T, T.id === attachId, DML.set(T.name.setTo(aname))) @@ -137,7 +137,7 @@ object RAttachment { def findByIdAndCollective( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Option[RAttachment]] = { val a = RAttachment.as("a") val i = RItem.as("i") @@ -153,7 +153,7 @@ object RAttachment { def existsByIdAndCollective( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Boolean] = { val a = RAttachment.as("a") val i = RItem.as("i") @@ -167,7 +167,7 @@ object RAttachment { def findByItemAndCollective( id: Ident, - coll: Ident + coll: CollectiveId ): ConnectionIO[Vector[RAttachment]] = { val a = RAttachment.as("a") val i = RItem.as("i") @@ -181,7 +181,7 @@ object RAttachment { def findByItemCollectiveSource( id: Ident, - coll: Ident, + coll: CollectiveId, fileIds: NonEmptyList[FileKey] ): ConnectionIO[Vector[RAttachment]] = { val i = RItem.as("i") @@ -202,7 +202,7 @@ object RAttachment { def findByItemAndCollectiveWithMeta( id: Ident, - coll: Ident + coll: CollectiveId ): ConnectionIO[Vector[(RAttachment, RFileMeta)]] = { val a = RAttachment.as("a") val m = RFileMeta.as("m") @@ -250,7 +250,7 @@ object RAttachment { } def findAll( - coll: Option[Ident], + coll: Option[CollectiveId], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { val a = RAttachment.as("a") @@ -283,7 +283,7 @@ object RAttachment { } def findWithoutPreview( - coll: Option[Ident], + coll: Option[CollectiveId], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { val a = RAttachment.as("a") @@ -299,7 +299,7 @@ object RAttachment { } def findNonConvertedPdf( - coll: Option[Ident], + coll: Option[CollectiveId], chunkSize: Int ): Stream[ConnectionIO, RAttachment] = { val pdfType = "application/pdf%" @@ -322,7 +322,7 @@ object RAttachment { def filterAttachments( attachments: NonEmptyList[Ident], - coll: Ident + coll: CollectiveId ): ConnectionIO[Vector[Ident]] = { val a = RAttachment.as("a") val i = RItem.as("i") diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala index 3913c135..edda4830 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentArchive.scala @@ -64,7 +64,7 @@ object RAttachmentArchive { def findByIdAndCollective( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Option[RAttachmentArchive]] = { val b = RAttachment.as("b") val a = RAttachmentArchive.as("a") @@ -81,7 +81,7 @@ object RAttachmentArchive { def findByMessageIdAndCollective( messageIds: NonEmptyList[String], - collective: Ident + collective: CollectiveId ): ConnectionIO[Vector[RAttachmentArchive]] = { val b = RAttachment.as("b") val a = RAttachmentArchive.as("a") diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala index 6ca4bc8e..c3d25ada 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentPreview.scala @@ -70,7 +70,7 @@ object RAttachmentPreview { def findByIdAndCollective( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Option[RAttachmentPreview]] = { val b = RAttachment.as("b") val a = RAttachmentPreview.as("a") @@ -98,7 +98,7 @@ object RAttachmentPreview { def findByItemAndCollective( itemId: Ident, - coll: Ident + coll: CollectiveId ): ConnectionIO[Option[RAttachmentPreview]] = { val s = RAttachmentPreview.as("s") val a = RAttachment.as("a") diff --git a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala index a2e3f949..e3fdc922 100644 --- a/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala +++ b/modules/store/src/main/scala/docspell/store/records/RAttachmentSource.scala @@ -71,7 +71,7 @@ object RAttachmentSource { def findByIdAndCollective( attachId: Ident, - collective: Ident + collective: CollectiveId ): ConnectionIO[Option[RAttachmentSource]] = { val b = RAttachment.as("b") val a = RAttachmentSource.as("a") diff --git a/modules/store/src/main/scala/docspell/store/records/RClassifierModel.scala b/modules/store/src/main/scala/docspell/store/records/RClassifierModel.scala index 89fae4df..6455a74e 100644 --- a/modules/store/src/main/scala/docspell/store/records/RClassifierModel.scala +++ b/modules/store/src/main/scala/docspell/store/records/RClassifierModel.scala @@ -19,7 +19,7 @@ import doobie.implicits._ final case class RClassifierModel( id: Ident, - cid: Ident, + cid: CollectiveId, name: String, fileId: FileKey, created: Timestamp @@ -28,7 +28,7 @@ final case class RClassifierModel( object RClassifierModel { def createNew[F[_]: Sync]( - cid: Ident, + cid: CollectiveId, name: String, fileId: FileKey ): F[RClassifierModel] = @@ -41,7 +41,7 @@ object RClassifierModel { val tableName = "classifier_model" 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 fileId = Column[FileKey]("file_id", this) val created = Column[Timestamp]("created", this) @@ -61,7 +61,7 @@ object RClassifierModel { 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 { now <- Timestamp.current[ConnectionIO] n <- DML.update( @@ -85,13 +85,16 @@ object RClassifierModel { 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 .query[RClassifierModel] .option def findAllByName( - cid: Ident, + cid: CollectiveId, names: NonEmptyList[String] ): ConnectionIO[List[RClassifierModel]] = Select(select(T.all), from(T), T.cid === cid && T.name.in(names)).build @@ -99,7 +102,7 @@ object RClassifierModel { .to[List] def findAllByQuery( - cid: Ident, + cid: CollectiveId, nameQuery: String ): ConnectionIO[List[RClassifierModel]] = Select(select(T.all), from(T), T.cid === cid && T.name.like(nameQuery)).build diff --git a/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala b/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala index 1e908c03..f5683f8b 100644 --- a/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala +++ b/modules/store/src/main/scala/docspell/store/records/RClassifierSetting.scala @@ -18,7 +18,7 @@ import doobie._ import doobie.implicits._ case class RClassifierSetting( - cid: Ident, + cid: CollectiveId, schedule: CalEvent, itemCount: Int, created: Timestamp, @@ -43,7 +43,7 @@ object RClassifierSetting { final case class Table(alias: Option[String]) extends TableDef { val tableName = "classifier_setting" - val cid = Column[Ident]("cid", this) + val cid = Column[CollectiveId]("coll_id", this) val schedule = Column[CalEvent]("schedule", this) val itemCount = Column[Int]("item_count", this) val created = Column[Timestamp]("created", this) @@ -79,19 +79,19 @@ object RClassifierSetting { n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] } 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) sql.query[RClassifierSetting].option } - def delete(coll: Ident): ConnectionIO[Int] = + def delete(coll: CollectiveId): ConnectionIO[Int] = DML.delete(T, T.cid === coll) /** 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 * whitelist, the intersection is returned. */ - def getActiveCategories(coll: Ident): ConnectionIO[List[String]] = + def getActiveCategories(coll: CollectiveId): ConnectionIO[List[String]] = (for { sett <- OptionT(findById(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 * anymore. */ - def fixCategoryList(coll: Ident): ConnectionIO[Int] = + def fixCategoryList(coll: CollectiveId): ConnectionIO[Int] = (for { sett <- OptionT(findById(coll)) cats <- OptionT.liftF(RTag.listCategories(coll)) @@ -131,7 +131,7 @@ object RClassifierSetting { categories.nonEmpty } - def toRecord(coll: Ident, created: Timestamp): RClassifierSetting = + def toRecord(coll: CollectiveId, created: Timestamp): RClassifierSetting = RClassifierSetting( coll, schedule, @@ -145,5 +145,4 @@ object RClassifierSetting { def fromRecord(r: RClassifierSetting): Classifier = Classifier(r.schedule, r.itemCount, r.categoryList, r.listType) } - } diff --git a/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala b/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala index 659fde30..ec778044 100644 --- a/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala +++ b/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala @@ -20,7 +20,7 @@ import io.circe.Json case class RClientSettingsCollective( id: Ident, clientId: Ident, - cid: Ident, + cid: CollectiveId, settingsData: Json, updated: Timestamp, created: Timestamp @@ -33,7 +33,7 @@ object RClientSettingsCollective { val id = Column[Ident]("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 updated = Column[Timestamp]("updated", this) val created = Column[Timestamp]("created", this) @@ -55,7 +55,7 @@ object RClientSettingsCollective { def updateSettings( clientId: Ident, - cid: Ident, + cid: CollectiveId, data: Json, updateTs: Timestamp ): ConnectionIO[Int] = @@ -65,7 +65,7 @@ object RClientSettingsCollective { 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 { id <- Ident.randomId[ConnectionIO] now <- Timestamp.current[ConnectionIO] @@ -75,10 +75,13 @@ object RClientSettingsCollective { else 0.pure[ConnectionIO] } 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) - 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) .query[RClientSettingsCollective] .option diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala index 2bbecaa6..da31fb18 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala @@ -17,7 +17,8 @@ import doobie._ import doobie.implicits._ case class RCollective( - id: Ident, + id: CollectiveId, + name: Ident, state: CollectiveState, language: Language, integrationEnabled: Boolean, @@ -28,17 +29,25 @@ object RCollective { final case class Table(alias: Option[String]) extends TableDef { 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 language = Column[Language]("doclang", this) val integration = Column[Boolean]("integration_enabled", 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 = - RCollective(collName, CollectiveState.Active, Language.German, true, created) + RCollective( + CollectiveId.unknown, + collName, + CollectiveState.Active, + Language.German, + true, + created + ) val T = Table(None) def as(alias: String): Table = @@ -48,25 +57,23 @@ object RCollective { DML.insert( T, 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] = DML.update( T, T.id === value.id, - DML.set( - T.state.setTo(value.state) - ) + DML.set(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 - 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))) - def updateSettings(cid: Ident, settings: Settings): ConnectionIO[Int] = + def updateSettings(cid: CollectiveId, settings: Settings): ConnectionIO[Int] = for { n1 <- DML.update( T, @@ -94,7 +101,7 @@ object RCollective { // 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 - def getSettings(coll: Ident): ConnectionIO[Option[Settings]] = + def getSettings(coll: CollectiveId): ConnectionIO[Option[Settings]] = (for { sett <- OptionT(getRawSettings(coll)) prev <- OptionT.pure[ConnectionIO](sett.classifier) @@ -103,7 +110,7 @@ object RCollective { pws <- OptionT.liftF(RCollectivePassword.findAll(coll)) } 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 val c = RCollective.as("c") @@ -127,7 +134,7 @@ object RCollective { ).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) sql.query[RCollective].option } @@ -142,7 +149,7 @@ object RCollective { ).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 sql.query[Int].unique.map(_ > 0) } diff --git a/modules/store/src/main/scala/docspell/store/records/RCollectivePassword.scala b/modules/store/src/main/scala/docspell/store/records/RCollectivePassword.scala index c7931d20..cb97651c 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCollectivePassword.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCollectivePassword.scala @@ -19,7 +19,7 @@ import doobie.implicits._ final case class RCollectivePassword( id: Ident, - cid: Ident, + cid: CollectiveId, password: Password, created: Timestamp ) {} @@ -29,7 +29,7 @@ object RCollectivePassword { val tableName: String = "collective_password" 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 created = Column[Timestamp]("created", this) @@ -41,7 +41,7 @@ object RCollectivePassword { def as(alias: String): Table = Table(Some(alias)) - def createNew[F[_]: Sync](cid: Ident, pw: Password): F[RCollectivePassword] = + def createNew[F[_]: Sync](cid: CollectiveId, pw: Password): F[RCollectivePassword] = for { id <- Ident.randomId[F] time <- Timestamp.current[F] @@ -63,15 +63,15 @@ object RCollectivePassword { def deleteById(id: Ident): ConnectionIO[Int] = 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) - def findAll(cid: Ident): ConnectionIO[List[RCollectivePassword]] = + def findAll(cid: CollectiveId): ConnectionIO[List[RCollectivePassword]] = Select(select(T.all), from(T), T.cid === cid).build .query[RCollectivePassword] .to[List] - def replaceAll(cid: Ident, pws: List[Password]): ConnectionIO[Int] = + def replaceAll(cid: CollectiveId, pws: List[Password]): ConnectionIO[Int] = for { k <- DML.delete(T, T.cid === cid) pw <- pws.traverse(p => createNew[ConnectionIO](cid, p)) diff --git a/modules/store/src/main/scala/docspell/store/records/RCustomField.scala b/modules/store/src/main/scala/docspell/store/records/RCustomField.scala index 89db8192..06a70916 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCustomField.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCustomField.scala @@ -20,7 +20,7 @@ case class RCustomField( id: Ident, name: Ident, label: Option[String], - cid: Ident, + cid: CollectiveId, ftype: CustomFieldType, created: Timestamp ) @@ -32,7 +32,7 @@ object RCustomField { val id = Column[Ident]("id", this) val name = Column[Ident]("name", 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 created = Column[Timestamp]("created", this) @@ -50,26 +50,29 @@ object RCustomField { 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) .query[Int] .unique .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 - def findByIdOrName(idOrName: Ident, coll: Ident): ConnectionIO[Option[RCustomField]] = + def findByIdOrName( + idOrName: Ident, + coll: CollectiveId + ): ConnectionIO[Option[RCustomField]] = Select( select(T.all), from(T), T.cid === coll && (T.id === idOrName || T.name === idOrName) ).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) - 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] def update(value: RCustomField): ConnectionIO[Int] = @@ -97,5 +100,4 @@ object RCustomField { ) else 0.pure[ConnectionIO] } yield n + k - } diff --git a/modules/store/src/main/scala/docspell/store/records/RDownloadQuery.scala b/modules/store/src/main/scala/docspell/store/records/RDownloadQuery.scala index a6bc5f4c..1e89a783 100644 --- a/modules/store/src/main/scala/docspell/store/records/RDownloadQuery.scala +++ b/modules/store/src/main/scala/docspell/store/records/RDownloadQuery.scala @@ -17,7 +17,7 @@ import doobie.implicits._ final case class RDownloadQuery( id: Ident, - cid: Ident, + cid: CollectiveId, fileId: FileKey, fileCount: Int, created: Timestamp, @@ -31,7 +31,7 @@ object RDownloadQuery { val tableName = "download_query" 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 fileCount: Column[Int] = Column("file_count", this) val created: Column[Timestamp] = Column("created", this) diff --git a/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala index 24fcb0f7..0b5cf86e 100644 --- a/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala +++ b/modules/store/src/main/scala/docspell/store/records/REmptyTrashSetting.scala @@ -19,7 +19,7 @@ import doobie._ import doobie.implicits._ final case class REmptyTrashSetting( - cid: Ident, + cid: CollectiveId, schedule: CalEvent, minAge: Duration, created: Timestamp @@ -30,7 +30,7 @@ object REmptyTrashSetting { final case class Table(alias: Option[String]) extends TableDef { 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 minAge = Column[Duration]("min_age", this) val created = Column[Timestamp]("created", this) @@ -61,7 +61,7 @@ object REmptyTrashSetting { n2 <- if (n1 <= 0) insert(v) else 0.pure[ConnectionIO] } 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) sql.query[REmptyTrashSetting].option } @@ -84,11 +84,11 @@ object REmptyTrashSetting { sql.query[REmptyTrashSetting].streamWithChunkSize(chunkSize) } - def delete(coll: Ident): ConnectionIO[Int] = + def delete(coll: CollectiveId): ConnectionIO[Int] = DML.delete(T, T.cid === coll) 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) } object EmptyTrash { diff --git a/modules/store/src/main/scala/docspell/store/records/REquipment.scala b/modules/store/src/main/scala/docspell/store/records/REquipment.scala index 14e5cc46..a03ff73c 100644 --- a/modules/store/src/main/scala/docspell/store/records/REquipment.scala +++ b/modules/store/src/main/scala/docspell/store/records/REquipment.scala @@ -17,7 +17,7 @@ import doobie.implicits._ case class REquipment( eid: Ident, - cid: Ident, + cid: CollectiveId, name: String, created: Timestamp, updated: Timestamp, @@ -30,7 +30,7 @@ object REquipment { val tableName = "equipment" 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 created = Column[Timestamp]("created", this) val updated = Column[Timestamp]("updated", this) @@ -72,7 +72,7 @@ object REquipment { } yield n } - def existsByName(coll: Ident, ename: String): ConnectionIO[Boolean] = { + def existsByName(coll: CollectiveId, ename: String): ConnectionIO[Boolean] = { val t = Table(None) val sql = run(select(count(t.eid)), from(t), where(t.cid === coll, t.name === ename)) sql.query[Int].unique.map(_ > 0) @@ -85,7 +85,7 @@ object REquipment { } def findAll( - coll: Ident, + coll: CollectiveId, nameQ: Option[String], order: Table => NonEmptyList[OrderBy] ): ConnectionIO[Vector[REquipment]] = { @@ -100,7 +100,7 @@ object REquipment { } def findLike( - coll: Ident, + coll: CollectiveId, equipName: String, use: NonEmptyList[EquipmentUse] ): ConnectionIO[Vector[IdRef]] = { @@ -114,7 +114,7 @@ object REquipment { .to[Vector] } - def delete(id: Ident, coll: Ident): ConnectionIO[Int] = { + def delete(id: Ident, coll: CollectiveId): ConnectionIO[Int] = { val t = Table(None) DML.delete(t, t.eid === id && t.cid === coll) } diff --git a/modules/store/src/main/scala/docspell/store/records/RFolder.scala b/modules/store/src/main/scala/docspell/store/records/RFolder.scala index 562f324e..b4ef19b3 100644 --- a/modules/store/src/main/scala/docspell/store/records/RFolder.scala +++ b/modules/store/src/main/scala/docspell/store/records/RFolder.scala @@ -20,25 +20,29 @@ import doobie.implicits._ case class RFolder( id: Ident, name: String, - collectiveId: Ident, + collectiveId: CollectiveId, owner: Ident, created: Timestamp ) 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 { nId <- Ident.randomId[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 { val tableName = "folder" val id = Column[Ident]("id", 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 created = Column[Timestamp]("created", this) @@ -63,7 +67,7 @@ object RFolder { 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) .query[Int] .unique @@ -77,7 +81,7 @@ object RFolder { def requireIdByIdOrName( folderId: Ident, name: String, - collective: Ident + collective: CollectiveId ): ConnectionIO[Ident] = { val sql = run( select(T.id), @@ -94,7 +98,7 @@ object RFolder { } def findAll( - coll: Ident, + coll: CollectiveId, nameQ: Option[String], order: Table => Column[_] ): ConnectionIO[Vector[RFolder]] = { diff --git a/modules/store/src/main/scala/docspell/store/records/RItem.scala b/modules/store/src/main/scala/docspell/store/records/RItem.scala index 36eadefe..e4168f64 100644 --- a/modules/store/src/main/scala/docspell/store/records/RItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RItem.scala @@ -20,7 +20,7 @@ import doobie.implicits._ case class RItem( id: Ident, - cid: Ident, + cid: CollectiveId, name: String, itemDate: Option[Timestamp], source: String, @@ -40,7 +40,7 @@ case class RItem( object RItem { def newItem[F[_]: Sync]( - cid: Ident, + cid: CollectiveId, name: String, source: String, direction: Direction, @@ -73,7 +73,7 @@ object RItem { val tableName = "item" 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 itemDate = Column[Timestamp]("itemdate", this) val source = Column[String]("source", this) @@ -166,7 +166,7 @@ object RItem { def updateStateForCollective( itemIds: NonEmptyList[Ident], itemState: ItemState, - coll: Ident + coll: CollectiveId ): ConnectionIO[Int] = for { t <- currentTime @@ -180,7 +180,7 @@ object RItem { def restoreStateForCollective( itemIds: NonEmptyList[Ident], itemState: ItemState, - coll: Ident + coll: CollectiveId ): ConnectionIO[Int] = for { t <- currentTime @@ -193,7 +193,7 @@ object RItem { def updateDirection( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, dir: Direction ): ConnectionIO[Int] = for { @@ -207,7 +207,7 @@ object RItem { def updateCorrOrg( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, org: Option[Ident] ): ConnectionIO[Int] = for { @@ -219,7 +219,7 @@ object RItem { ) } yield n - def removeCorrOrg(coll: Ident, currentOrg: Ident): ConnectionIO[Int] = + def removeCorrOrg(coll: CollectiveId, currentOrg: Ident): ConnectionIO[Int] = for { t <- currentTime n <- DML.update( @@ -231,7 +231,7 @@ object RItem { def updateCorrPerson( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, person: Option[Ident] ): ConnectionIO[Int] = for { @@ -243,7 +243,7 @@ object RItem { ) } yield n - def removeCorrPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = + def removeCorrPerson(coll: CollectiveId, currentPerson: Ident): ConnectionIO[Int] = for { t <- currentTime n <- DML.update( @@ -255,7 +255,7 @@ object RItem { def updateConcPerson( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, person: Option[Ident] ): ConnectionIO[Int] = for { @@ -267,7 +267,7 @@ object RItem { ) } yield n - def removeConcPerson(coll: Ident, currentPerson: Ident): ConnectionIO[Int] = + def removeConcPerson(coll: CollectiveId, currentPerson: Ident): ConnectionIO[Int] = for { t <- currentTime n <- DML.update( @@ -279,7 +279,7 @@ object RItem { def updateConcEquip( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, equip: Option[Ident] ): ConnectionIO[Int] = for { @@ -291,7 +291,7 @@ object RItem { ) } yield n - def removeConcEquip(coll: Ident, currentEquip: Ident): ConnectionIO[Int] = + def removeConcEquip(coll: CollectiveId, currentEquip: Ident): ConnectionIO[Int] = for { t <- currentTime n <- DML.update( @@ -303,7 +303,7 @@ object RItem { def updateFolder( itemId: Ident, - coll: Ident, + coll: CollectiveId, folderIdOrName: Option[String] ): ConnectionIO[(Int, Option[Ident])] = for { @@ -321,7 +321,11 @@ object RItem { ) } 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 { t <- currentTime n <- DML.update( @@ -333,7 +337,7 @@ object RItem { def appendNotes( itemId: Ident, - cid: Ident, + cid: CollectiveId, text: String, sep: 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 { t <- currentTime n <- DML.update( @@ -363,7 +367,7 @@ object RItem { def updateDate( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, date: Option[Timestamp] ): ConnectionIO[Int] = for { @@ -377,7 +381,7 @@ object RItem { def updateDueDate( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, date: Option[Timestamp] ): ConnectionIO[Int] = for { @@ -389,12 +393,12 @@ object RItem { ) } 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) def setState( itemIds: NonEmptyList[Ident], - coll: Ident, + coll: CollectiveId, state: ItemState ): ConnectionIO[Int] = for { @@ -409,7 +413,7 @@ object RItem { def existsById(itemId: Ident): ConnectionIO[Boolean] = 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 .query[Int] .unique @@ -417,19 +421,22 @@ object RItem { def existsByIdsAndCollective( itemIds: NonEmptyList[Ident], - coll: Ident + coll: CollectiveId ): ConnectionIO[Boolean] = Select(count(T.id).s, from(T), T.id.in(itemIds) && T.cid === coll).build .query[Int] .unique .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 def findAllByIdAndCollective( itemIds: NonEmptyList[Ident], - coll: Ident + coll: CollectiveId ): ConnectionIO[Vector[RItem]] = run(select(T.all), from(T), T.id.in(itemIds) && T.cid === coll) .query[RItem] @@ -439,7 +446,7 @@ object RItem { run(select(T.all), from(T), T.id === itemId).query[RItem].option def findDeleted( - collective: Ident, + collective: CollectiveId, maxUpdated: Timestamp, chunkSize: Int ): Stream[ConnectionIO, RItem] = @@ -451,7 +458,10 @@ object RItem { .query[RItem] .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 def removeFolder(folderId: Ident): ConnectionIO[Int] = { @@ -459,9 +469,12 @@ object RItem { 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)) - 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] } diff --git a/modules/store/src/main/scala/docspell/store/records/RItemLink.scala b/modules/store/src/main/scala/docspell/store/records/RItemLink.scala index 8270a34a..f589a679 100644 --- a/modules/store/src/main/scala/docspell/store/records/RItemLink.scala +++ b/modules/store/src/main/scala/docspell/store/records/RItemLink.scala @@ -20,14 +20,14 @@ import doobie.implicits._ final case class RItemLink( id: Ident, - cid: Ident, + cid: CollectiveId, item1: Ident, item2: Ident, created: Timestamp ) 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 { id <- Ident.randomId[F] now <- Timestamp.current[F] @@ -37,7 +37,7 @@ object RItemLink { val tableName = "item_link" 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 item2: Column[Ident] = Column("item2", 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}") } - 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) 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) Select( select(count(T.id)), @@ -86,7 +86,7 @@ object RItemLink { ).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( Select( select(T.item1), @@ -101,7 +101,7 @@ object RItemLink { ).build.query[Ident].to[Vector] def deleteAll( - cid: Ident, + cid: CollectiveId, item: Ident, related: NonEmptyList[Ident] ): 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) DML.delete(T, T.cid === cid && T.item1 === i1 && T.item2 === i2) } diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationChannel.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationChannel.scala index 94aa89c4..4eb741f3 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationChannel.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationChannel.scala @@ -95,12 +95,12 @@ object RNotificationChannel { RNotificationChannelHttp.update ) - def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannel]] = + def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannel]] = for { - mail <- RNotificationChannelMail.getByAccount(account) - gotify <- RNotificationChannelGotify.getByAccount(account) - matrix <- RNotificationChannelMatrix.getByAccount(account) - http <- RNotificationChannelHttp.getByAccount(account) + mail <- RNotificationChannelMail.getByAccount(userId) + gotify <- RNotificationChannelGotify.getByAccount(userId) + matrix <- RNotificationChannelMatrix.getByAccount(userId) + http <- RNotificationChannelHttp.getByAccount(userId) } yield mail.map(Email.apply) ++ gotify.map(Gotify.apply) ++ matrix.map( Matrix.apply ) ++ http.map(Http.apply) @@ -177,12 +177,12 @@ object RNotificationChannel { .flatMap(_.flatTraverse(find)) } - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = for { - n1 <- RNotificationChannelMail.deleteByAccount(id, account) - n2 <- RNotificationChannelGotify.deleteByAccount(id, account) - n3 <- RNotificationChannelMatrix.deleteByAccount(id, account) - n4 <- RNotificationChannelHttp.deleteByAccount(id, account) + n1 <- RNotificationChannelMail.deleteByAccount(id, userId) + n2 <- RNotificationChannelGotify.deleteByAccount(id, userId) + n3 <- RNotificationChannelMatrix.deleteByAccount(id, userId) + n4 <- RNotificationChannelHttp.deleteByAccount(id, userId) } yield n1 + n2 + n3 + n4 def fromChannel( diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelGotify.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelGotify.scala index 40bb9528..c7909287 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelGotify.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelGotify.scala @@ -77,27 +77,21 @@ object RNotificationChannelGotify { ) def getByAccount( - account: AccountId + userId: Ident ): ConnectionIO[Vector[RNotificationChannelGotify]] = { - val user = RUser.as("u") val gotify = as("c") Select( select(gotify.all), - from(gotify).innerJoin(user, user.uid === gotify.uid), - user.cid === account.collective && user.login === account.user + from(gotify), + gotify.uid === userId ).build.query[RNotificationChannelGotify].to[Vector] } def deleteById(id: Ident): ConnectionIO[Int] = DML.delete(T, T.id === id) - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { - val u = RUser.as("u") - DML.delete( - T, - T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account))) - ) - } + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = + DML.delete(T, T.id === id && T.uid === userId) def findRefs(ids: NonEmptyList[Ident]): Select = Select( diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelHttp.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelHttp.scala index b06eaae7..3d9a9249 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelHttp.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelHttp.scala @@ -61,26 +61,23 @@ object RNotificationChannelHttp { DML.set(T.url.setTo(r.url), T.name.setTo(r.name)) ) - def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelHttp]] = { - val user = RUser.as("u") + def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannelHttp]] = { val http = as("c") Select( select(http.all), - from(http).innerJoin(user, user.uid === http.uid), - user.cid === account.collective && user.login === account.user + from(http), + http.uid === userId ).build.query[RNotificationChannelHttp].to[Vector] } def deleteById(id: Ident): ConnectionIO[Int] = DML.delete(T, T.id === id) - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { - val u = RUser.as("u") + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = DML.delete( 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 = Select( diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMail.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMail.scala index d73fe0c8..3309d02d 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMail.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMail.scala @@ -71,26 +71,20 @@ object RNotificationChannelMail { .query[RNotificationChannelMail] .option - def getByAccount(account: AccountId): ConnectionIO[Vector[RNotificationChannelMail]] = { - val user = RUser.as("u") - val gotify = as("c") + def getByAccount(userId: Ident): ConnectionIO[Vector[RNotificationChannelMail]] = { + val mail = as("c") Select( - select(gotify.all), - from(gotify).innerJoin(user, user.uid === gotify.uid), - user.cid === account.collective && user.login === account.user + select(mail.all), + from(mail), + mail.uid === userId ).build.query[RNotificationChannelMail].to[Vector] } def deleteById(id: Ident): ConnectionIO[Int] = DML.delete(T, T.id === id) - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { - val u = RUser.as("u") - DML.delete( - T, - T.id === id && T.uid.in(Select(select(u.uid), from(u), u.isAccount(account))) - ) - } + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = + DML.delete(T, T.id === id && T.uid === userId) def findRefs(ids: NonEmptyList[Ident]): Select = Select( diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMatrix.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMatrix.scala index f6965b88..ae50c3ba 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMatrix.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationChannelMatrix.scala @@ -86,27 +86,25 @@ object RNotificationChannelMatrix { .option def getByAccount( - account: AccountId + userId: Ident ): ConnectionIO[Vector[RNotificationChannelMatrix]] = { - val user = RUser.as("u") - val gotify = as("c") + + val matrix = as("c") Select( - select(gotify.all), - from(gotify).innerJoin(user, user.uid === gotify.uid), - user.cid === account.collective && user.login === account.user + select(matrix.all), + from(matrix), + matrix.uid === userId ).build.query[RNotificationChannelMatrix].to[Vector] } def deleteById(id: Ident): ConnectionIO[Int] = DML.delete(T, T.id === id) - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { - val u = RUser.as("u") + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = DML.delete( 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 = Select( diff --git a/modules/store/src/main/scala/docspell/store/records/RNotificationHook.scala b/modules/store/src/main/scala/docspell/store/records/RNotificationHook.scala index 75c22d85..da9425f5 100644 --- a/modules/store/src/main/scala/docspell/store/records/RNotificationHook.scala +++ b/modules/store/src/main/scala/docspell/store/records/RNotificationHook.scala @@ -58,13 +58,11 @@ object RNotificationHook { sql"${r.id},${r.uid},${r.enabled},${r.allEvents},${r.eventFilter},${r.created}" ) - def deleteByAccount(id: Ident, account: AccountId): ConnectionIO[Int] = { - val u = RUser.as("u") + def deleteByAccount(id: Ident, userId: Ident): ConnectionIO[Int] = DML.delete( 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] = DML.update( @@ -77,11 +75,11 @@ object RNotificationHook { ) ) - def findByAccount(account: AccountId): ConnectionIO[Vector[RNotificationHook]] = + def findByAccount(userId: Ident): ConnectionIO[Vector[RNotificationHook]] = Select( select(T.all), 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] def getById(id: Ident, userId: Ident): ConnectionIO[Option[RNotificationHook]] = @@ -92,17 +90,15 @@ object RNotificationHook { ).build.query[RNotificationHook].option def findAllByAccount( - account: AccountId + userId: Ident ): ConnectionIO[Vector[(RNotificationHook, List[EventType])]] = { val h = RNotificationHook.as("h") val e = RNotificationHookEvent.as("e") - val userSelect = - Select(select(RUser.T.uid), from(RUser.T), RUser.T.isAccount(account)) val withEvents = Select( select(h.all :+ e.eventType), from(h).innerJoin(e, e.hookId === h.id), - h.uid.in(userSelect) + h.uid === userId ).orderBy(h.id) .build .query[(RNotificationHook, EventType)] @@ -113,7 +109,7 @@ object RNotificationHook { Select( select(h.all), 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 .query[RNotificationHook] .to[Vector] diff --git a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala index c1fa07b2..843cdd27 100644 --- a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala +++ b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala @@ -19,7 +19,7 @@ import doobie.implicits._ case class ROrganization( oid: Ident, - cid: Ident, + cid: CollectiveId, name: String, street: String, zip: String, @@ -40,7 +40,7 @@ object ROrganization { val tableName = "organization" 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 street = Column[String]("street", this) val zip = Column[String]("zip", this) @@ -103,24 +103,24 @@ object ROrganization { } 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) .query[Int] .unique .map(_ > 0) 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 } - 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) sql.query[ROrganization].option } def findLike( - coll: Ident, + coll: CollectiveId, orgName: String, use: Nel[OrgUse] ): ConnectionIO[Vector[IdRef]] = @@ -135,7 +135,7 @@ object ROrganization { .to[Vector] def findLike( - coll: Ident, + coll: CollectiveId, contactKind: ContactKind, value: String ): ConnectionIO[Vector[IdRef]] = { @@ -153,7 +153,7 @@ object ROrganization { } def findAll( - coll: Ident, + coll: CollectiveId, order: Table => Column[_] ): Stream[ConnectionIO, ROrganization] = { val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) @@ -161,7 +161,7 @@ object ROrganization { } def findAllRef( - coll: Ident, + coll: CollectiveId, nameQ: Option[String], order: Table => Nel[OrderBy] ): ConnectionIO[Vector[IdRef]] = { @@ -173,6 +173,6 @@ object ROrganization { 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) } diff --git a/modules/store/src/main/scala/docspell/store/records/RPerson.scala b/modules/store/src/main/scala/docspell/store/records/RPerson.scala index 4d264c2d..496ad2f6 100644 --- a/modules/store/src/main/scala/docspell/store/records/RPerson.scala +++ b/modules/store/src/main/scala/docspell/store/records/RPerson.scala @@ -20,7 +20,7 @@ import doobie.implicits._ case class RPerson( pid: Ident, - cid: Ident, + cid: CollectiveId, name: String, street: String, zip: String, @@ -41,7 +41,7 @@ object RPerson { val tableName = "person" 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 street = Column[String]("street", this) val zip = Column[String]("zip", this) @@ -103,24 +103,24 @@ object RPerson { } 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) .query[Int] .unique .map(_ > 0) 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 } - 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) sql.query[RPerson].option } def findLike( - coll: Ident, + coll: CollectiveId, personName: String, use: Nel[PersonUse] ): ConnectionIO[Vector[IdRef]] = @@ -131,7 +131,7 @@ object RPerson { ).query[IdRef].to[Vector] def findLike( - coll: Ident, + coll: CollectiveId, contactKind: ContactKind, value: String, use: Nel[PersonUse] @@ -152,7 +152,7 @@ object RPerson { } def findAll( - coll: Ident, + coll: CollectiveId, order: Table => Column[_] ): Stream[ConnectionIO, RPerson] = { val sql = Select(select(T.all), from(T), T.cid === coll).orderBy(order(T)) @@ -160,7 +160,7 @@ object RPerson { } def findAllRef( - coll: Ident, + coll: CollectiveId, nameQ: Option[String], order: Table => Nel[OrderBy] ): ConnectionIO[Vector[IdRef]] = { @@ -172,7 +172,7 @@ object RPerson { 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) def findOrganization(ids: Set[Ident]): ConnectionIO[Vector[PersonRef]] = diff --git a/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala b/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala index 08d21b71..2d18cd13 100644 --- a/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala +++ b/modules/store/src/main/scala/docspell/store/records/RQueryBookmark.scala @@ -23,7 +23,7 @@ final case class RQueryBookmark( name: String, label: Option[String], userId: Option[Ident], - cid: Ident, + cid: CollectiveId, query: ItemQuery, created: Timestamp ) { @@ -39,13 +39,13 @@ final case class RQueryBookmark( object RQueryBookmark { final case class Table(alias: Option[String]) extends TableDef { - val tableName = "query_bookmark"; + val tableName = "query_bookmark" val id = Column[Ident]("id", this) val name = Column[String]("name", this) val label = Column[String]("label", 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 created = Column[Timestamp]("created", this) @@ -59,22 +59,21 @@ object RQueryBookmark { def as(alias: String): Table = Table(Some(alias)) def createNew( - account: AccountId, + collective: CollectiveId, + userId: Option[Ident], name: String, label: Option[String], - query: ItemQuery, - personal: Boolean + query: ItemQuery ): ConnectionIO[RQueryBookmark] = for { - userId <- RUser.getIdByAccount(account) curTime <- Timestamp.current[ConnectionIO] id <- Ident.randomId[ConnectionIO] } yield RQueryBookmark( id, name, label, - if (personal) userId.some else None, - account.collective, + userId, + collective, query, 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) - def nameExists(account: AccountId, name: String): ConnectionIO[Boolean] = { - val user = RUser.as("u") + def nameExists( + collId: CollectiveId, + userId: Ident, + name: String + ): ConnectionIO[Boolean] = { val bm = RQueryBookmark.as("bm") - val users = Select( - user.uid.s, - from(user), - user.cid === account.collective && user.login === account.user - ) Select( select(count(bm.id)), from(bm), - bm.name === name && bm.cid === account.collective && (bm.userId.isNull || bm.userId - .in(users)) + bm.name === name && bm.cid === collId && (bm.userId.isNull || bm.userId === userId) ).build.query[Int].unique.map(_ > 0) } @@ -127,13 +123,14 @@ object RQueryBookmark { // checked before (and therefore subject to race conditions, but is // neglected here) def insertIfNotExists( - account: AccountId, + collId: CollectiveId, + userId: Ident, r: ConnectionIO[RQueryBookmark] ): ConnectionIO[AddResult] = for { bm <- r res <- - nameExists(account, bm.name).flatMap { + nameExists(collId, userId, bm.name).flatMap { case true => AddResult .entityExists(s"A bookmark '${bm.name}' already exists.") @@ -142,39 +139,31 @@ object RQueryBookmark { } } yield res - def allForUser(account: AccountId): ConnectionIO[Vector[RQueryBookmark]] = { - val user = RUser.as("u") + def allForUser( + collId: CollectiveId, + userId: Ident + ): ConnectionIO[Vector[RQueryBookmark]] = { val bm = RQueryBookmark.as("bm") - val users = Select( - user.uid.s, - from(user), - user.cid === account.collective && user.login === account.user - ) Select( select(bm.all), 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] } def findByNameOrId( - account: AccountId, + collId: CollectiveId, + userId: Ident, nameOrId: String ): ConnectionIO[Option[RQueryBookmark]] = { - val user = RUser.as("u") val bm = RQueryBookmark.as("bm") - val users = Select( - user.uid.s, - from(user), - user.cid === account.collective && user.login === account.user - ) Select( select(bm.all), from(bm), - bm.cid === account.collective && - (bm.userId.isNull || bm.userId.in(users)) && + bm.cid === collId && + (bm.userId.isNull || bm.userId === userId) && (bm.name === nameOrId || bm.id ==== nameOrId) ).build.query[RQueryBookmark].option } diff --git a/modules/store/src/main/scala/docspell/store/records/RShare.scala b/modules/store/src/main/scala/docspell/store/records/RShare.scala index 3aca8f81..196c3a00 100644 --- a/modules/store/src/main/scala/docspell/store/records/RShare.scala +++ b/modules/store/src/main/scala/docspell/store/records/RShare.scala @@ -32,7 +32,7 @@ final case class RShare( object RShare { final case class Table(alias: Option[String]) extends TableDef { - val tableName = "item_share"; + val tableName = "item_share" val id = Column[Ident]("id", this) val userId = Column[Ident]("user_id", this) @@ -94,7 +94,7 @@ object RShare { 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 u = RUser.as("u") @@ -139,7 +139,7 @@ object RShare { }) def findOneByCollective( - cid: Ident, + cid: CollectiveId, enabled: Option[Boolean], nameOrId: String ): ConnectionIO[Option[RShare]] = { @@ -156,7 +156,7 @@ object RShare { } def findAllByCollective( - cid: Ident, + cid: CollectiveId, ownerLogin: Option[Ident], q: Option[String] ): ConnectionIO[List[(RShare, RUser)]] = { @@ -177,7 +177,7 @@ object RShare { .to[List] } - def deleteByIdAndCid(id: Ident, cid: Ident): ConnectionIO[Int] = { + def deleteByIdAndCid(id: Ident, cid: CollectiveId): ConnectionIO[Int] = { val u = RUser.T DML.delete(T, T.id === id && T.userId.in(Select(u.uid.s, from(u), u.cid === cid))) } diff --git a/modules/store/src/main/scala/docspell/store/records/RSource.scala b/modules/store/src/main/scala/docspell/store/records/RSource.scala index 629ae832..214660e9 100644 --- a/modules/store/src/main/scala/docspell/store/records/RSource.scala +++ b/modules/store/src/main/scala/docspell/store/records/RSource.scala @@ -17,7 +17,7 @@ import doobie.implicits._ case class RSource( sid: Ident, - cid: Ident, + cid: CollectiveId, abbrev: String, description: Option[String], counter: Int, @@ -40,7 +40,7 @@ object RSource { val tableName = "source" 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 description = Column[String]("description", 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( table, where(table.abbrev === source, table.cid === coll), @@ -110,7 +110,7 @@ object RSource { 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( select(count(table.sid)), from(table), @@ -129,20 +129,20 @@ object RSource { run(select(table.cid), from(table), table.sid === sourceId).query[Ident].option def findAll( - coll: Ident, + coll: CollectiveId, order: Table => Column[_] ): ConnectionIO[Vector[RSource]] = findAllSql(coll, order).query[RSource].to[Vector] private[records] def findAllSql( - coll: Ident, + coll: CollectiveId, order: Table => Column[_] ): Fragment = { val t = RSource.as("s") 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)) def removeFolder(folderId: Ident): ConnectionIO[Int] = { diff --git a/modules/store/src/main/scala/docspell/store/records/RTag.scala b/modules/store/src/main/scala/docspell/store/records/RTag.scala index b9e9ab27..fc66f6e8 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTag.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTag.scala @@ -18,7 +18,7 @@ import doobie.implicits._ case class RTag( tagId: Ident, - collective: Ident, + collective: CollectiveId, name: String, category: Option[String], created: Timestamp @@ -29,7 +29,7 @@ object RTag { val tableName = "tag" 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 category = Column[String]("category", this) val created = Column[Timestamp]("created", this) @@ -62,7 +62,7 @@ object RTag { 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) sql.query[RTag].option } @@ -74,7 +74,7 @@ object RTag { } def findAll( - coll: Ident, + coll: CollectiveId, query: Option[String], order: Table => NonEmptyList[OrderBy] ): ConnectionIO[Vector[RTag]] = { @@ -121,7 +121,7 @@ object RTag { def findAllByNameOrId( nameOrIds: List[String], - coll: Ident + coll: CollectiveId ): ConnectionIO[Vector[RTag]] = { val idList = 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 = NonEmptyList .fromList(excludeTags) @@ -155,14 +158,14 @@ object RTag { ).orderBy(T.name.asc).build.query[RTag].to[List] } - def listCategories(coll: Ident): ConnectionIO[List[String]] = + def listCategories(coll: CollectiveId): ConnectionIO[List[String]] = Select( T.category.s, from(T), T.cid === coll && T.category.isNotNull ).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) def sort(tags: List[RTag]): List[RTag] = diff --git a/modules/store/src/main/scala/docspell/store/records/RTagItem.scala b/modules/store/src/main/scala/docspell/store/records/RTagItem.scala index cb9467bd..0e6e627e 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTagItem.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTagItem.scala @@ -53,7 +53,7 @@ object RTagItem { def deleteItemTags(item: Ident): ConnectionIO[Int] = 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))) def deleteTag(tid: Ident): ConnectionIO[Int] = diff --git a/modules/store/src/main/scala/docspell/store/records/RTotp.scala b/modules/store/src/main/scala/docspell/store/records/RTotp.scala index 6b942852..00148b18 100644 --- a/modules/store/src/main/scala/docspell/store/records/RTotp.scala +++ b/modules/store/src/main/scala/docspell/store/records/RTotp.scala @@ -59,24 +59,15 @@ object RTotp { ) ) - def setEnabled(account: AccountId, enabled: Boolean): ConnectionIO[Int] = - for { - 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 setEnabled(userId: Ident, enabled: Boolean): ConnectionIO[Int] = + DML.update(T, T.userId === userId, DML.set(T.enabled.setTo(enabled))) - def isEnabled(accountId: AccountId): ConnectionIO[Boolean] = { + def isEnabled(userId: Ident): ConnectionIO[Boolean] = { val t = RTotp.as("t") - val u = RUser.as("u") Select( select(count(t.userId)), - from(t).innerJoin(u, t.userId === u.uid), - u.login === accountId.user && u.cid === accountId.collective && t.enabled === true + from(t), + t.userId === userId && t.enabled === true ).build.query[Int].unique.map(_ > 0) } @@ -86,24 +77,45 @@ object RTotp { ): ConnectionIO[Option[RTotp]] = { val t = RTotp.as("t") val u = RUser.as("u") + val c = RCollective.as("c") Select( select(t.all), - from(t).innerJoin(u, t.userId === u.uid), - u.login === accountId.user && u.cid === accountId.collective && t.enabled === enabled + from(t).innerJoin(u, t.userId === u.uid).innerJoin(c, c.id === u.cid), + 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 } def existsByLogin(accountId: AccountId): ConnectionIO[Boolean] = { val t = RTotp.as("t") val u = RUser.as("u") + val c = RCollective.as("c") Select( select(count(t.userId)), - from(t).innerJoin(u, t.userId === u.uid), - u.login === accountId.user && u.cid === accountId.collective + from(t).innerJoin(u, t.userId === u.uid).innerJoin(c, c.id === u.cid), + u.login === accountId.user && c.name === accountId.collective ).build .query[Int] .unique .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) + } } diff --git a/modules/store/src/main/scala/docspell/store/records/RUser.scala b/modules/store/src/main/scala/docspell/store/records/RUser.scala index 651c99fe..1e28c6f6 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUser.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUser.scala @@ -7,8 +7,6 @@ package docspell.store.records import cats.data.NonEmptyList -import cats.data.OptionT -import cats.effect.Sync import docspell.common._ import docspell.store.qb.DSL._ @@ -20,7 +18,7 @@ import doobie.implicits._ case class RUser( uid: Ident, login: Ident, - cid: Ident, + cid: CollectiveId, password: Password, state: UserState, source: AccountSource, @@ -29,8 +27,6 @@ case class RUser( lastLogin: Option[Timestamp], created: Timestamp ) { - def accountId: AccountId = - AccountId(cid, login) def idRef: IdRef = IdRef(uid, login.id) @@ -41,7 +37,7 @@ object RUser { def makeDefault( id: Ident, login: Ident, - collName: Ident, + collId: CollectiveId, password: Password, source: AccountSource, created: Timestamp @@ -49,7 +45,7 @@ object RUser { RUser( id, login, - collName, + collId, password, UserState.Active, source, @@ -64,7 +60,7 @@ object RUser { val uid = Column[Ident]("uid", 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 state = Column[UserState]("state", this) val source = Column[AccountSource]("account_source", this) @@ -73,9 +69,6 @@ object RUser { val lastLogin = Column[Timestamp]("lastlogin", this) val created = Column[Timestamp]("created", this) - def isAccount(aid: AccountId) = - cid === aid.collective && login === aid.user - val all = NonEmptyList.of[Column[_]]( uid, @@ -125,9 +118,14 @@ object RUser { } def findByAccount(aid: AccountId): ConnectionIO[Option[RUser]] = { - val t = Table(None) + val t = RUser.as("u") + val c = RCollective.as("c") 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 } @@ -137,20 +135,26 @@ object RUser { 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 sql = Select(select(t.all), from(t), t.cid === coll).orderBy(order(t)).build 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( - select(T.uid), - from(T), - T.login === accountId.user && T.cid === accountId.collective + select(u.uid), + from(u).innerJoin(c, c.id === u.cid), + u.login === accountId.user && c.name === accountId.collective ) .query[Ident] .option + } case class IdAndLogin(uid: Ident, login: Ident) def getIdByIdOrLogin(idOrLogin: Ident): ConnectionIO[Option[IdAndLogin]] = @@ -160,19 +164,19 @@ object RUser { T.uid === idOrLogin || T.login === idOrLogin ).build.query[IdAndLogin].option - def getIdByAccount(account: AccountId): ConnectionIO[Ident] = - OptionT(findIdByAccount(account)).getOrElseF( - Sync[ConnectionIO].raiseError( - new Exception(s"No user found for: ${account.asString}") - ) - ) +// def getIdByAccount(account: AccountId): ConnectionIO[Ident] = +// OptionT(findIdByAccount(account)).getOrElseF( +// Sync[ConnectionIO].raiseError( +// 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) def stmt(now: Timestamp) = DML.update( t, - t.cid === accountId.collective && t.login === accountId.user, + t.cid === accountId.collectiveId && t.login === accountId.login, DML.set( t.loginCount.increment(1), t.lastLogin.setTo(now) @@ -181,16 +185,20 @@ object RUser { 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) DML.update( 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)) ) } - def delete(user: Ident, coll: Ident): ConnectionIO[Int] = { + def delete(user: Ident, coll: CollectiveId): ConnectionIO[Int] = { val t = Table(None) DML.delete(t, t.cid === coll && t.login === user) } diff --git a/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala b/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala index a11e330a..c8b68e28 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUserEmail.scala @@ -184,11 +184,10 @@ object RUserEmail { } private def findByAccount0( - accId: AccountId, + userId: Ident, nameQ: Option[String], exact: Boolean ): Query0[RUserEmail] = { - val user = RUser.as("u") val email = as("m") val nameFilter = nameQ.map(s => @@ -197,43 +196,32 @@ object RUserEmail { val sql = Select( select(email.all), - from(email).innerJoin(user, email.uid === user.uid), - user.cid === accId.collective && user.login === accId.user &&? nameFilter + from(email), + email.uid === userId &&? nameFilter ).orderBy(email.name) sql.build.query[RUserEmail] } def findByAccount( - accId: AccountId, + userId: Ident, nameQ: Option[String] ): ConnectionIO[Vector[RUserEmail]] = - findByAccount0(accId, nameQ, false).to[Vector] + findByAccount0(userId, nameQ, false).to[Vector] - def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserEmail]] = - findByAccount0(accId, Some(name.id), true).option + def getByName(userId: Ident, name: Ident): ConnectionIO[Option[RUserEmail]] = + findByAccount0(userId, Some(name.id), true).option def getById(id: Ident): ConnectionIO[Option[RUserEmail]] = { val t = Table(None) run(select(t.all), from(t), t.id === id).query[RUserEmail].option } - def delete(accId: AccountId, 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 - ) - + def delete(userId: Ident, connName: Ident): ConnectionIO[Int] = { 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] = { val t = Table(None) run(select(count(t.id)), from(t), t.uid === userId && t.name === connName) diff --git a/modules/store/src/main/scala/docspell/store/records/RUserImap.scala b/modules/store/src/main/scala/docspell/store/records/RUserImap.scala index a6f1885c..ae4d3265 100644 --- a/modules/store/src/main/scala/docspell/store/records/RUserImap.scala +++ b/modules/store/src/main/scala/docspell/store/records/RUserImap.scala @@ -171,12 +171,11 @@ object RUserImap { } private def findByAccount0( - accId: AccountId, + userId: Ident, nameQ: Option[String], exact: Boolean ): Query0[RUserImap] = { val m = RUserImap.as("m") - val u = RUser.as("u") val nameFilter = nameQ.map { str => @@ -186,37 +185,37 @@ object RUserImap { val sql = Select( select(m.all), - from(m).innerJoin(u, m.uid === u.uid), - u.cid === accId.collective && u.login === accId.user &&? nameFilter + from(m), + m.uid === userId &&? nameFilter ).orderBy(m.name).build sql.query[RUserImap] } def findByAccount( - accId: AccountId, + userId: Ident, nameQ: Option[String] ): ConnectionIO[Vector[RUserImap]] = - findByAccount0(accId, nameQ, false).to[Vector] + findByAccount0(userId, nameQ, false).to[Vector] - def getByName(accId: AccountId, name: Ident): ConnectionIO[Option[RUserImap]] = - findByAccount0(accId, Some(name.id), true).option + def getByName( + 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 u = RUser.as("u") - val subsel = - Select(select(u.uid), from(u), u.cid === accId.collective && u.login === accId.user) DML.delete( 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] = { val t = Table(None) run(select(count(t.id)), from(t), t.uid === userId && t.name === connName) diff --git a/modules/store/src/main/scala/docspell/store/records/SourceData.scala b/modules/store/src/main/scala/docspell/store/records/SourceData.scala index aee01672..27fa86e5 100644 --- a/modules/store/src/main/scala/docspell/store/records/SourceData.scala +++ b/modules/store/src/main/scala/docspell/store/records/SourceData.scala @@ -26,7 +26,7 @@ object SourceData { SourceData(s, Vector.empty) def findAll( - coll: Ident, + coll: CollectiveId, order: RSource.Table => Column[_] ): Stream[ConnectionIO, SourceData] = findAllWithTags(RSource.findAllSql(coll, order).query[RSource].stream) @@ -83,7 +83,7 @@ object SourceData { ) } yield n0 + n1.sum - def delete(source: Ident, coll: Ident): ConnectionIO[Int] = + def delete(source: Ident, coll: CollectiveId): ConnectionIO[Int] = for { n0 <- RTagSource.deleteSourceTags(source) n1 <- RSource.delete(source, coll) diff --git a/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala b/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala index 0b3c9aa5..ad081354 100644 --- a/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala +++ b/modules/store/src/test/scala/docspell/store/fts/TempFtsOpsTest.scala @@ -6,22 +6,19 @@ package docspell.store.fts -import java.time.LocalDate - +import java.time.{Instant, LocalDate} import cats.effect.IO import cats.syntax.option._ import cats.syntax.traverse._ import fs2.Stream - import docspell.common._ import docspell.ftsclient.FtsResult import docspell.ftsclient.FtsResult.{AttachmentData, ItemMatch} import docspell.store._ import docspell.store.qb.DSL._ import docspell.store.qb._ -import docspell.store.queries.{QItem, Query} -import docspell.store.records.{RCollective, RItem} - +import docspell.store.queries.{QItem, QLogin, Query} +import docspell.store.records.{RCollective, RItem, RUser} import doobie._ class TempFtsOpsTest extends DatabaseTest { @@ -60,9 +57,10 @@ class TempFtsOpsTest extends DatabaseTest { def prepareItems(store: Store[IO]) = 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) - .map(makeItem(_, DocspellSystem.user)) + .map(makeItem(_, CollectiveId(2))) .toList _ <- items.traverse(i => store.transact(RItem.insert(i))) } yield () @@ -100,7 +98,9 @@ class TempFtsOpsTest extends DatabaseTest { def assertQueryItem(store: Store[IO], ftsResults: Stream[ConnectionIO, FtsResult]) = for { today <- IO(LocalDate.now()) - account = DocspellSystem.account + account <- store + .transact(QLogin.findUser(DocspellSystem.account)) + .map(_.get.account) tempTable = ftsResults .through(TempFtsOps.prepareTable(store.dbms, "fts_result")) .compile @@ -170,10 +170,31 @@ class TempFtsOpsTest extends DatabaseTest { } } - def makeCollective(cid: Ident): RCollective = - RCollective(cid, CollectiveState.Active, Language.English, true, ts) + def makeUser(cid: CollectiveId): RUser = + 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( id(s"item-$n"), cid, diff --git a/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala b/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala index 9f9a9995..ea209c94 100644 --- a/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala +++ b/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala @@ -40,7 +40,7 @@ class ItemQueryGeneratorTest extends FunSuite { test("basic test") { val q = ItemQueryParser .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 = tables.item.name.like("hello") && coalesce(tables.item.itemDate.s, tables.item.created.s) >= @@ -52,14 +52,14 @@ class ItemQueryGeneratorTest extends FunSuite { test("!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("%")) assertEquals(cond, expect) } test("attach.id with wildcard") { 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( Select( select(RAttachment.T.itemId), @@ -73,7 +73,7 @@ class ItemQueryGeneratorTest extends FunSuite { test("attach.id with equals") { 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( Select( select(RAttachment.T.itemId), diff --git a/modules/store/src/test/scala/docspell/store/migrate/MigrateTest.scala b/modules/store/src/test/scala/docspell/store/migrate/MigrateTest.scala index cb34fb18..0594578a 100644 --- a/modules/store/src/test/scala/docspell/store/migrate/MigrateTest.scala +++ b/modules/store/src/test/scala/docspell/store/migrate/MigrateTest.scala @@ -7,9 +7,7 @@ package docspell.store.migrate import cats.effect._ - import docspell.store.{DatabaseTest, SchemaMigrateConfig, StoreFixture} - import org.flywaydb.core.api.output.MigrateResult class MigrateTest extends DatabaseTest {