mirror of
				https://github.com/TheAnachronism/docspell.git
				synced 2025-11-04 12:30:12 +00:00 
			
		
		
		
	Add routes for storing/retrieving client settings
This commit is contained in:
		@@ -38,6 +38,7 @@ trait BackendApp[F[_]] {
 | 
				
			|||||||
  def folder: OFolder[F]
 | 
					  def folder: OFolder[F]
 | 
				
			||||||
  def customFields: OCustomFields[F]
 | 
					  def customFields: OCustomFields[F]
 | 
				
			||||||
  def simpleSearch: OSimpleSearch[F]
 | 
					  def simpleSearch: OSimpleSearch[F]
 | 
				
			||||||
 | 
					  def clientSettings: OClientSettings[F]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object BackendApp {
 | 
					object BackendApp {
 | 
				
			||||||
@@ -73,26 +74,28 @@ object BackendApp {
 | 
				
			|||||||
      folderImpl       <- OFolder(store)
 | 
					      folderImpl       <- OFolder(store)
 | 
				
			||||||
      customFieldsImpl <- OCustomFields(store)
 | 
					      customFieldsImpl <- OCustomFields(store)
 | 
				
			||||||
      simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
 | 
					      simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
 | 
				
			||||||
 | 
					      clientSettingsImpl <- OClientSettings(store)
 | 
				
			||||||
    } yield new BackendApp[F] {
 | 
					    } yield new BackendApp[F] {
 | 
				
			||||||
      val login        = loginImpl
 | 
					      val login          = loginImpl
 | 
				
			||||||
      val signup       = signupImpl
 | 
					      val signup         = signupImpl
 | 
				
			||||||
      val collective   = collImpl
 | 
					      val collective     = collImpl
 | 
				
			||||||
      val source       = sourceImpl
 | 
					      val source         = sourceImpl
 | 
				
			||||||
      val tag          = tagImpl
 | 
					      val tag            = tagImpl
 | 
				
			||||||
      val equipment    = equipImpl
 | 
					      val equipment      = equipImpl
 | 
				
			||||||
      val organization = orgImpl
 | 
					      val organization   = orgImpl
 | 
				
			||||||
      val upload       = uploadImpl
 | 
					      val upload         = uploadImpl
 | 
				
			||||||
      val node         = nodeImpl
 | 
					      val node           = nodeImpl
 | 
				
			||||||
      val job          = jobImpl
 | 
					      val job            = jobImpl
 | 
				
			||||||
      val item         = itemImpl
 | 
					      val item           = itemImpl
 | 
				
			||||||
      val itemSearch   = itemSearchImpl
 | 
					      val itemSearch     = itemSearchImpl
 | 
				
			||||||
      val fulltext     = fulltextImpl
 | 
					      val fulltext       = fulltextImpl
 | 
				
			||||||
      val mail         = mailImpl
 | 
					      val mail           = mailImpl
 | 
				
			||||||
      val joex         = joexImpl
 | 
					      val joex           = joexImpl
 | 
				
			||||||
      val userTask     = userTaskImpl
 | 
					      val userTask       = userTaskImpl
 | 
				
			||||||
      val folder       = folderImpl
 | 
					      val folder         = folderImpl
 | 
				
			||||||
      val customFields = customFieldsImpl
 | 
					      val customFields   = customFieldsImpl
 | 
				
			||||||
      val simpleSearch = simpleSearchImpl
 | 
					      val simpleSearch   = simpleSearchImpl
 | 
				
			||||||
 | 
					      val clientSettings = clientSettingsImpl
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def apply[F[_]: ConcurrentEffect: ContextShift](
 | 
					  def apply[F[_]: ConcurrentEffect: ContextShift](
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					package docspell.backend.ops
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.data.OptionT
 | 
				
			||||||
 | 
					import cats.effect.{Effect, Resource}
 | 
				
			||||||
 | 
					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.RUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.circe.Json
 | 
				
			||||||
 | 
					import org.log4s._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object OClientSettings {
 | 
				
			||||||
 | 
					  private[this] val logger = getLogger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def apply[F[_]: Effect](store: Store[F]): Resource[F, OClientSettings[F]] =
 | 
				
			||||||
 | 
					    Resource.pure[F, OClientSettings[F]](new OClientSettings[F] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private def getUserId(account: AccountId): OptionT[F, Ident] =
 | 
				
			||||||
 | 
					        OptionT(store.transact(RUser.findByAccount(account))).map(_.uid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def delete(clientId: Ident, account: AccountId): F[Boolean] =
 | 
				
			||||||
 | 
					        (for {
 | 
				
			||||||
 | 
					          _ <- OptionT.liftF(
 | 
				
			||||||
 | 
					            logger.fdebug(
 | 
				
			||||||
 | 
					              s"Deleting client settings for client ${clientId.id} and account $account"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          userId <- getUserId(account)
 | 
				
			||||||
 | 
					          n <- OptionT.liftF(
 | 
				
			||||||
 | 
					            store.transact(
 | 
				
			||||||
 | 
					              RClientSettings.delete(clientId, userId)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        } yield n > 0).getOrElse(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def save(clientId: Ident, account: AccountId, data: Json): F[Unit] =
 | 
				
			||||||
 | 
					        (for {
 | 
				
			||||||
 | 
					          _ <- OptionT.liftF(
 | 
				
			||||||
 | 
					            logger.fdebug(
 | 
				
			||||||
 | 
					              s"Storing client settings for client ${clientId.id} and account $account"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          userId <- getUserId(account)
 | 
				
			||||||
 | 
					          n <- OptionT.liftF(
 | 
				
			||||||
 | 
					            store.transact(RClientSettings.upsert(clientId, userId, data))
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          _ <- OptionT.liftF(
 | 
				
			||||||
 | 
					            if (n <= 0) Effect[F].raiseError(new Exception("No rows updated!"))
 | 
				
			||||||
 | 
					            else ().pure[F]
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        } yield ()).getOrElse(())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def load(clientId: Ident, account: AccountId): F[Option[RClientSettings]] =
 | 
				
			||||||
 | 
					        (for {
 | 
				
			||||||
 | 
					          _ <- OptionT.liftF(
 | 
				
			||||||
 | 
					            logger.fdebug(
 | 
				
			||||||
 | 
					              s"Loading client settings for client ${clientId.id} and account $account"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          userId <- getUserId(account)
 | 
				
			||||||
 | 
					          data   <- OptionT(store.transact(RClientSettings.find(clientId, userId)))
 | 
				
			||||||
 | 
					        } yield data).value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1185,6 +1185,68 @@ paths:
 | 
				
			|||||||
              schema:
 | 
					              schema:
 | 
				
			||||||
                $ref: "#/components/schemas/BasicResult"
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /sec/clientSettings/{clientId}:
 | 
				
			||||||
 | 
					    parameters:
 | 
				
			||||||
 | 
					      - $ref: "#/components/parameters/clientId"
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      tags: [ Client Settings ]
 | 
				
			||||||
 | 
					      summary: Return the current user settings
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Returns the settings for the current user. The `clientId` is
 | 
				
			||||||
 | 
					        an identifier to a client application. It returns a JSON
 | 
				
			||||||
 | 
					        structure. The server doesn't care about the actual data,
 | 
				
			||||||
 | 
					        since it is meant to be interpreted by clients.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema: {}
 | 
				
			||||||
 | 
					    put:
 | 
				
			||||||
 | 
					      tags: [ Client Settings ]
 | 
				
			||||||
 | 
					      summary: Update current user settings
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Updates (replaces or creates) the current user's settings with
 | 
				
			||||||
 | 
					        the given data. The `clientId` is an identifier to a client
 | 
				
			||||||
 | 
					        application. The request body is expected to be JSON, the
 | 
				
			||||||
 | 
					        structure is not important to the server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The data is stored for the current user and given `clientId`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The data is only saved without being checked in any way
 | 
				
			||||||
 | 
					        (besides being valid JSON). It is returned "as is" to the
 | 
				
			||||||
 | 
					        client in the corresponding GET endpoint.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema: {}
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					    delete:
 | 
				
			||||||
 | 
					      tags: [ Client Settings ]
 | 
				
			||||||
 | 
					      summary: Clears the current user settings
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Removes all stored user settings for the client identified by
 | 
				
			||||||
 | 
					        `clientId`.
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authTokenHeader: []
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        200:
 | 
				
			||||||
 | 
					          description: Ok
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: "#/components/schemas/BasicResult"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /admin/user/resetPassword:
 | 
					  /admin/user/resetPassword:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      tags: [ Collective, Admin ]
 | 
					      tags: [ Collective, Admin ]
 | 
				
			||||||
@@ -5571,3 +5633,11 @@ components:
 | 
				
			|||||||
      required: false
 | 
					      required: false
 | 
				
			||||||
      schema:
 | 
					      schema:
 | 
				
			||||||
        type: boolean
 | 
					        type: boolean
 | 
				
			||||||
 | 
					    clientId:
 | 
				
			||||||
 | 
					      name: clientId
 | 
				
			||||||
 | 
					      in: path
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        some identifier for a client application
 | 
				
			||||||
 | 
					      schema:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,7 +91,8 @@ object RestServer {
 | 
				
			|||||||
      "calevent/check"          -> CalEventCheckRoutes(),
 | 
					      "calevent/check"          -> CalEventCheckRoutes(),
 | 
				
			||||||
      "fts"                     -> FullTextIndexRoutes.secured(cfg, restApp.backend, token),
 | 
					      "fts"                     -> FullTextIndexRoutes.secured(cfg, restApp.backend, token),
 | 
				
			||||||
      "folder"                  -> FolderRoutes(restApp.backend, token),
 | 
					      "folder"                  -> FolderRoutes(restApp.backend, token),
 | 
				
			||||||
      "customfield"             -> CustomFieldRoutes(restApp.backend, token)
 | 
					      "customfield"             -> CustomFieldRoutes(restApp.backend, token),
 | 
				
			||||||
 | 
					      "clientSettings"          -> ClientSettingsRoutes(restApp.backend, token)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
 | 
					  def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					package docspell.restserver.routes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cats.effect._
 | 
				
			||||||
 | 
					import cats.implicits._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import docspell.backend.BackendApp
 | 
				
			||||||
 | 
					import docspell.backend.auth.AuthToken
 | 
				
			||||||
 | 
					import docspell.common._
 | 
				
			||||||
 | 
					import docspell.restapi.model._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.circe.Json
 | 
				
			||||||
 | 
					import org.http4s.HttpRoutes
 | 
				
			||||||
 | 
					import org.http4s.circe.CirceEntityDecoder._
 | 
				
			||||||
 | 
					import org.http4s.circe.CirceEntityEncoder._
 | 
				
			||||||
 | 
					import org.http4s.dsl.Http4sDsl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object ClientSettingsRoutes {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
 | 
				
			||||||
 | 
					    val dsl = new Http4sDsl[F] {}
 | 
				
			||||||
 | 
					    import dsl._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    HttpRoutes.of {
 | 
				
			||||||
 | 
					      case req @ PUT -> Root / Ident(clientId) =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          data <- req.as[Json]
 | 
				
			||||||
 | 
					          _    <- backend.clientSettings.save(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)
 | 
				
			||||||
 | 
					          res <- data match {
 | 
				
			||||||
 | 
					            case Some(d) => Ok(d.settingsData)
 | 
				
			||||||
 | 
					            case None    => NotFound()
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } yield res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case DELETE -> Root / Ident(clientId) =>
 | 
				
			||||||
 | 
					        for {
 | 
				
			||||||
 | 
					          flag <- backend.clientSettings.delete(clientId, user.account)
 | 
				
			||||||
 | 
					          res <- Ok(
 | 
				
			||||||
 | 
					            BasicResult(
 | 
				
			||||||
 | 
					              flag,
 | 
				
			||||||
 | 
					              if (flag) "Settings deleted" else "Deleting settings failed"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        } yield res
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					CREATE TABLE "client_settings" (
 | 
				
			||||||
 | 
					  "id" varchar(254) not null primary key,
 | 
				
			||||||
 | 
					  "client_id" varchar(254) not null,
 | 
				
			||||||
 | 
					  "user_id" varchar(254) not null,
 | 
				
			||||||
 | 
					  "settings_data" text not null,
 | 
				
			||||||
 | 
					  "created" timestamp not null,
 | 
				
			||||||
 | 
					  "updated" timestamp not null,
 | 
				
			||||||
 | 
					  foreign key ("user_id") references "user_"("uid") on delete cascade,
 | 
				
			||||||
 | 
					  unique ("client_id", "user_id")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					CREATE TABLE `client_settings` (
 | 
				
			||||||
 | 
					  `id` varchar(254) not null primary key,
 | 
				
			||||||
 | 
					  `client_id` 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 (`user_id`) references `user_`(`uid`) on delete cascade,
 | 
				
			||||||
 | 
					  unique (`client_id`, `user_id`)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					CREATE TABLE "client_settings" (
 | 
				
			||||||
 | 
					  "id" varchar(254) not null primary key,
 | 
				
			||||||
 | 
					  "client_id" varchar(254) not null,
 | 
				
			||||||
 | 
					  "user_id" varchar(254) not null,
 | 
				
			||||||
 | 
					  "settings_data" text not null,
 | 
				
			||||||
 | 
					  "created" timestamp not null,
 | 
				
			||||||
 | 
					  "updated" timestamp not null,
 | 
				
			||||||
 | 
					  foreign key ("user_id") references "user_"("uid") on delete cascade,
 | 
				
			||||||
 | 
					  unique ("client_id", "user_id")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@@ -11,6 +11,7 @@ import doobie._
 | 
				
			|||||||
import doobie.implicits.legacy.instant._
 | 
					import doobie.implicits.legacy.instant._
 | 
				
			||||||
import doobie.util.log.Success
 | 
					import doobie.util.log.Success
 | 
				
			||||||
import emil.doobie.EmilDoobieMeta
 | 
					import emil.doobie.EmilDoobieMeta
 | 
				
			||||||
 | 
					import io.circe.Json
 | 
				
			||||||
import io.circe.{Decoder, Encoder}
 | 
					import io.circe.{Decoder, Encoder}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait DoobieMeta extends EmilDoobieMeta {
 | 
					trait DoobieMeta extends EmilDoobieMeta {
 | 
				
			||||||
@@ -112,10 +113,18 @@ trait DoobieMeta extends EmilDoobieMeta {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  implicit val metaOrgUse: Meta[OrgUse] =
 | 
					  implicit val metaOrgUse: Meta[OrgUse] =
 | 
				
			||||||
    Meta[String].timap(OrgUse.unsafeFromString)(_.name)
 | 
					    Meta[String].timap(OrgUse.unsafeFromString)(_.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  implicit val metaJsonString: Meta[Json] =
 | 
				
			||||||
 | 
					    Meta[String].timap(DoobieMeta.parseJsonUnsafe)(_.noSpaces)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object DoobieMeta extends DoobieMeta {
 | 
					object DoobieMeta extends DoobieMeta {
 | 
				
			||||||
  import org.log4s._
 | 
					  import org.log4s._
 | 
				
			||||||
  private val logger = getLogger
 | 
					  private val logger = getLogger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private def parseJsonUnsafe(str: String): Json =
 | 
				
			||||||
 | 
					    io.circe.parser
 | 
				
			||||||
 | 
					      .parse(str)
 | 
				
			||||||
 | 
					      .fold(throw _, identity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					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 RClientSettings(
 | 
				
			||||||
 | 
					    id: Ident,
 | 
				
			||||||
 | 
					    clientId: Ident,
 | 
				
			||||||
 | 
					    userId: Ident,
 | 
				
			||||||
 | 
					    settingsData: Json,
 | 
				
			||||||
 | 
					    updated: Timestamp,
 | 
				
			||||||
 | 
					    created: Timestamp
 | 
				
			||||||
 | 
					) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object RClientSettings {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final case class Table(alias: Option[String]) extends TableDef {
 | 
				
			||||||
 | 
					    val tableName = "client_settings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val id           = Column[Ident]("id", this)
 | 
				
			||||||
 | 
					    val clientId     = Column[Ident]("client_id", this)
 | 
				
			||||||
 | 
					    val userId       = Column[Ident]("user_id", 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, userId, settingsData, updated, created)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def as(alias: String): Table = Table(Some(alias))
 | 
				
			||||||
 | 
					  val T                        = Table(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def insert(v: RClientSettings): ConnectionIO[Int] = {
 | 
				
			||||||
 | 
					    val t = Table(None)
 | 
				
			||||||
 | 
					    DML.insert(
 | 
				
			||||||
 | 
					      t,
 | 
				
			||||||
 | 
					      t.all,
 | 
				
			||||||
 | 
					      fr"${v.id},${v.clientId},${v.userId},${v.settingsData},${v.updated},${v.created}"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def updateSettings(
 | 
				
			||||||
 | 
					      clientId: Ident,
 | 
				
			||||||
 | 
					      userId: Ident,
 | 
				
			||||||
 | 
					      data: Json,
 | 
				
			||||||
 | 
					      updateTs: Timestamp
 | 
				
			||||||
 | 
					  ): ConnectionIO[Int] =
 | 
				
			||||||
 | 
					    DML.update(
 | 
				
			||||||
 | 
					      T,
 | 
				
			||||||
 | 
					      T.clientId === clientId && T.userId === userId,
 | 
				
			||||||
 | 
					      DML.set(T.settingsData.setTo(data), T.updated.setTo(updateTs))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def upsert(clientId: Ident, userId: Ident, data: Json): ConnectionIO[Int] =
 | 
				
			||||||
 | 
					    for {
 | 
				
			||||||
 | 
					      id  <- Ident.randomId[ConnectionIO]
 | 
				
			||||||
 | 
					      now <- Timestamp.current[ConnectionIO]
 | 
				
			||||||
 | 
					      nup <- updateSettings(clientId, userId, data, now)
 | 
				
			||||||
 | 
					      nin <-
 | 
				
			||||||
 | 
					        if (nup <= 0) insert(RClientSettings(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]] =
 | 
				
			||||||
 | 
					    run(select(T.all), from(T), T.clientId === clientId && T.userId === userId)
 | 
				
			||||||
 | 
					      .query[RClientSettings]
 | 
				
			||||||
 | 
					      .option
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user