From 706cfaeb05cb99f9182b089f7c6b1c43972b4374 Mon Sep 17 00:00:00 2001 From: eikek Date: Thu, 23 Dec 2021 22:46:13 +0100 Subject: [PATCH] Introduce table to store client settings per collective --- .../backend/ops/OClientSettings.scala | 57 +++++++++---- .../routes/ClientSettingsRoutes.scala | 6 +- .../h2/V1.30.0__clientsettings_collective.sql | 12 +++ .../V1.30.0__clientsettings_collective.sql | 12 +++ .../V1.30.0__clientsettings_collective.sql | 12 +++ .../records/RClientSettingsCollective.scala | 85 +++++++++++++++++++ ...ttings.scala => RClientSettingsUser.scala} | 14 +-- 7 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 modules/store/src/main/resources/db/migration/h2/V1.30.0__clientsettings_collective.sql create mode 100644 modules/store/src/main/resources/db/migration/mariadb/V1.30.0__clientsettings_collective.sql create mode 100644 modules/store/src/main/resources/db/migration/postgresql/V1.30.0__clientsettings_collective.sql create mode 100644 modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala rename modules/store/src/main/scala/docspell/store/records/{RClientSettings.scala => RClientSettingsUser.scala} (86%) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala b/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala index a7e71cbe..3e6ff0c9 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala @@ -12,56 +12,75 @@ import cats.implicits._ import docspell.common.AccountId import docspell.common._ -import docspell.common.syntax.all._ import docspell.store.Store -import docspell.store.records.RClientSettings +import docspell.store.records.RClientSettingsUser import docspell.store.records.RUser import io.circe.Json -import org.log4s._ +import docspell.store.records.RClientSettingsCollective trait OClientSettings[F[_]] { - def delete(clientId: Ident, account: AccountId): F[Boolean] - def save(clientId: Ident, account: AccountId, data: Json): F[Unit] - def load(clientId: Ident, account: AccountId): F[Option[RClientSettings]] + def deleteUser(clientId: Ident, account: AccountId): F[Boolean] + def saveUser(clientId: Ident, account: AccountId, data: Json): F[Unit] + def loadUser(clientId: Ident, account: AccountId): F[Option[RClientSettingsUser]] + + def deleteCollective(clientId: Ident, account: AccountId): F[Boolean] + def saveCollective(clientId: Ident, account: AccountId, data: Json): F[Unit] + def loadCollective(clientId: Ident, account: AccountId): F[Option[RClientSettingsCollective]] } object OClientSettings { - private[this] val logger = getLogger + private[this] val logger = org.log4s.getLogger def apply[F[_]: Async](store: Store[F]): Resource[F, OClientSettings[F]] = Resource.pure[F, OClientSettings[F]](new OClientSettings[F] { + val log = Logger.log4s[F](logger) private def getUserId(account: AccountId): OptionT[F, Ident] = OptionT(store.transact(RUser.findByAccount(account))).map(_.uid) - def delete(clientId: Ident, account: AccountId): F[Boolean] = + def deleteCollective(clientId: Ident, account: AccountId): F[Boolean] = + store + .transact(RClientSettingsCollective.delete(clientId, account.collective)) + .map(_ > 0) + + def deleteUser(clientId: Ident, account: AccountId): F[Boolean] = (for { _ <- OptionT.liftF( - logger.fdebug( + log.debug( s"Deleting client settings for client ${clientId.id} and account $account" ) ) userId <- getUserId(account) n <- OptionT.liftF( store.transact( - RClientSettings.delete(clientId, userId) + RClientSettingsUser.delete(clientId, userId) ) ) } yield n > 0).getOrElse(false) - def save(clientId: Ident, account: AccountId, data: Json): F[Unit] = + def saveCollective(clientId: Ident, account: AccountId, data: Json): F[Unit] = + for { + n <- store.transact( + RClientSettingsCollective.upsert(clientId, account.collective, data) + ) + _ <- + if (n <= 0) Async[F].raiseError(new IllegalStateException("No rows updated!")) + else ().pure[F] + } yield () + + def saveUser(clientId: Ident, account: AccountId, data: Json): F[Unit] = (for { _ <- OptionT.liftF( - logger.fdebug( + log.debug( s"Storing client settings for client ${clientId.id} and account $account" ) ) userId <- getUserId(account) n <- OptionT.liftF( - store.transact(RClientSettings.upsert(clientId, userId, data)) + store.transact(RClientSettingsUser.upsert(clientId, userId, data)) ) _ <- OptionT.liftF( if (n <= 0) Async[F].raiseError(new Exception("No rows updated!")) @@ -69,15 +88,21 @@ object OClientSettings { ) } yield ()).getOrElse(()) - def load(clientId: Ident, account: AccountId): F[Option[RClientSettings]] = + def loadCollective( + clientId: Ident, + account: AccountId + ): F[Option[RClientSettingsCollective]] = + store.transact(RClientSettingsCollective.find(clientId, account.collective)) + + def loadUser(clientId: Ident, account: AccountId): F[Option[RClientSettingsUser]] = (for { _ <- OptionT.liftF( - logger.fdebug( + log.debug( s"Loading client settings for client ${clientId.id} and account $account" ) ) userId <- getUserId(account) - data <- OptionT(store.transact(RClientSettings.find(clientId, userId))) + data <- OptionT(store.transact(RClientSettingsUser.find(clientId, userId))) } yield data).value }) diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala index 962f6f13..28e1426f 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala @@ -30,13 +30,13 @@ object ClientSettingsRoutes { case req @ PUT -> Root / Ident(clientId) => for { data <- req.as[Json] - _ <- backend.clientSettings.save(clientId, user.account, data) + _ <- backend.clientSettings.saveUser(clientId, user.account, data) res <- Ok(BasicResult(true, "Settings stored")) } yield res case GET -> Root / Ident(clientId) => for { - data <- backend.clientSettings.load(clientId, user.account) + data <- backend.clientSettings.loadUser(clientId, user.account) res <- data match { case Some(d) => Ok(d.settingsData) case None => NotFound() @@ -45,7 +45,7 @@ object ClientSettingsRoutes { case DELETE -> Root / Ident(clientId) => for { - flag <- backend.clientSettings.delete(clientId, user.account) + flag <- backend.clientSettings.deleteUser(clientId, user.account) res <- Ok( BasicResult( flag, diff --git a/modules/store/src/main/resources/db/migration/h2/V1.30.0__clientsettings_collective.sql b/modules/store/src/main/resources/db/migration/h2/V1.30.0__clientsettings_collective.sql new file mode 100644 index 00000000..c2806a43 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/h2/V1.30.0__clientsettings_collective.sql @@ -0,0 +1,12 @@ +ALTER TABLE "client_settings" RENAME TO "client_settings_user"; + +CREATE TABLE "client_settings_collective" ( + "id" varchar(254) not null primary key, + "client_id" varchar(254) not null, + "cid" varchar(254) not null, + "settings_data" text not null, + "created" timestamp not null, + "updated" timestamp not null, + foreign key ("cid") references "collective"("cid") on delete cascade, + unique ("client_id", "cid") +); diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.30.0__clientsettings_collective.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.30.0__clientsettings_collective.sql new file mode 100644 index 00000000..6a7faa35 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/mariadb/V1.30.0__clientsettings_collective.sql @@ -0,0 +1,12 @@ +RENAME TABLE `client_settings` TO `client_settings_user`; + +CREATE TABLE `client_settings` ( + `id` varchar(254) not null primary key, + `cid` varchar(254) not null, + `user_id` varchar(254) not null, + `settings_data` longtext not null, + `created` timestamp not null, + `updated` timestamp not null, + foreign key (`cid`) references `collective`(`cid`) on delete cascade, + unique (`client_id`, `cid`) +); diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.30.0__clientsettings_collective.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.30.0__clientsettings_collective.sql new file mode 100644 index 00000000..c2806a43 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/postgresql/V1.30.0__clientsettings_collective.sql @@ -0,0 +1,12 @@ +ALTER TABLE "client_settings" RENAME TO "client_settings_user"; + +CREATE TABLE "client_settings_collective" ( + "id" varchar(254) not null primary key, + "client_id" varchar(254) not null, + "cid" varchar(254) not null, + "settings_data" text not null, + "created" timestamp not null, + "updated" timestamp not null, + foreign key ("cid") references "collective"("cid") on delete cascade, + unique ("client_id", "cid") +); diff --git a/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala b/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala new file mode 100644 index 00000000..659fde30 --- /dev/null +++ b/modules/store/src/main/scala/docspell/store/records/RClientSettingsCollective.scala @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Eike K. & Contributors + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package docspell.store.records + +import cats.data.NonEmptyList +import cats.implicits._ + +import docspell.common._ +import docspell.store.qb.DSL._ +import docspell.store.qb._ + +import doobie._ +import doobie.implicits._ +import io.circe.Json + +case class RClientSettingsCollective( + id: Ident, + clientId: Ident, + cid: Ident, + settingsData: Json, + updated: Timestamp, + created: Timestamp +) {} + +object RClientSettingsCollective { + + final case class Table(alias: Option[String]) extends TableDef { + val tableName = "client_settings_collective" + + val id = Column[Ident]("id", this) + val clientId = Column[Ident]("client_id", this) + val cid = Column[Ident]("cid", this) + val settingsData = Column[Json]("settings_data", this) + val updated = Column[Timestamp]("updated", this) + val created = Column[Timestamp]("created", this) + val all = + NonEmptyList.of[Column[_]](id, clientId, cid, settingsData, updated, created) + } + + def as(alias: String): Table = Table(Some(alias)) + val T = Table(None) + + def insert(v: RClientSettingsCollective): ConnectionIO[Int] = { + val t = Table(None) + DML.insert( + t, + t.all, + fr"${v.id},${v.clientId},${v.cid},${v.settingsData},${v.updated},${v.created}" + ) + } + + def updateSettings( + clientId: Ident, + cid: Ident, + data: Json, + updateTs: Timestamp + ): ConnectionIO[Int] = + DML.update( + T, + T.clientId === clientId && T.cid === cid, + DML.set(T.settingsData.setTo(data), T.updated.setTo(updateTs)) + ) + + def upsert(clientId: Ident, cid: Ident, data: Json): ConnectionIO[Int] = + for { + id <- Ident.randomId[ConnectionIO] + now <- Timestamp.current[ConnectionIO] + nup <- updateSettings(clientId, cid, data, now) + nin <- + if (nup <= 0) insert(RClientSettingsCollective(id, clientId, cid, data, now, now)) + else 0.pure[ConnectionIO] + } yield nup + nin + + def delete(clientId: Ident, cid: Ident): ConnectionIO[Int] = + DML.delete(T, T.clientId === clientId && T.cid === cid) + + def find(clientId: Ident, cid: Ident): 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/RClientSettings.scala b/modules/store/src/main/scala/docspell/store/records/RClientSettingsUser.scala similarity index 86% rename from modules/store/src/main/scala/docspell/store/records/RClientSettings.scala rename to modules/store/src/main/scala/docspell/store/records/RClientSettingsUser.scala index 94ef0026..5dfbc323 100644 --- a/modules/store/src/main/scala/docspell/store/records/RClientSettings.scala +++ b/modules/store/src/main/scala/docspell/store/records/RClientSettingsUser.scala @@ -17,7 +17,7 @@ import doobie._ import doobie.implicits._ import io.circe.Json -case class RClientSettings( +case class RClientSettingsUser( id: Ident, clientId: Ident, userId: Ident, @@ -26,10 +26,10 @@ case class RClientSettings( created: Timestamp ) {} -object RClientSettings { +object RClientSettingsUser { final case class Table(alias: Option[String]) extends TableDef { - val tableName = "client_settings" + val tableName = "client_settings_user" val id = Column[Ident]("id", this) val clientId = Column[Ident]("client_id", this) @@ -44,7 +44,7 @@ object RClientSettings { def as(alias: String): Table = Table(Some(alias)) val T = Table(None) - def insert(v: RClientSettings): ConnectionIO[Int] = { + def insert(v: RClientSettingsUser): ConnectionIO[Int] = { val t = Table(None) DML.insert( t, @@ -71,15 +71,15 @@ object RClientSettings { now <- Timestamp.current[ConnectionIO] nup <- updateSettings(clientId, userId, data, now) nin <- - if (nup <= 0) insert(RClientSettings(id, clientId, userId, data, now, now)) + if (nup <= 0) insert(RClientSettingsUser(id, clientId, userId, data, now, now)) else 0.pure[ConnectionIO] } yield nup + nin def delete(clientId: Ident, userId: Ident): ConnectionIO[Int] = DML.delete(T, T.clientId === clientId && T.userId === userId) - def find(clientId: Ident, userId: Ident): ConnectionIO[Option[RClientSettings]] = + def find(clientId: Ident, userId: Ident): ConnectionIO[Option[RClientSettingsUser]] = run(select(T.all), from(T), T.clientId === clientId && T.userId === userId) - .query[RClientSettings] + .query[RClientSettingsUser] .option }