let the good times roll
This commit is contained in:
parent
53a0114cbb
commit
5c7c33d1b5
8 changed files with 510 additions and 63 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package gs.smolban.auth
|
package gs.smolban.auth
|
||||||
|
|
||||||
import gs.smolban.model.account.AccountId
|
import gs.smolban.model.account.AccountId
|
||||||
|
import gs.smolban.model.account.AccountType
|
||||||
import gs.smolban.model.metadata.CreatedAt
|
import gs.smolban.model.metadata.CreatedAt
|
||||||
|
|
||||||
/** Describes some credential but does not contain the actual credential value.
|
/** Describes some credential but does not contain the actual credential value.
|
||||||
|
|
@ -10,6 +11,8 @@ import gs.smolban.model.metadata.CreatedAt
|
||||||
* @param accountId
|
* @param accountId
|
||||||
* The unique identifier of the account to which this credential is
|
* The unique identifier of the account to which this credential is
|
||||||
* associated.
|
* associated.
|
||||||
|
* @param accountType
|
||||||
|
* The type of account this credential is associated with.
|
||||||
* @param credentialType
|
* @param credentialType
|
||||||
* The type of credential.
|
* The type of credential.
|
||||||
* @param status
|
* @param status
|
||||||
|
|
@ -22,6 +25,7 @@ import gs.smolban.model.metadata.CreatedAt
|
||||||
case class Credential(
|
case class Credential(
|
||||||
credentialId: CredentialId,
|
credentialId: CredentialId,
|
||||||
accountId: AccountId,
|
accountId: AccountId,
|
||||||
|
accountType: AccountType,
|
||||||
credentialType: CredentialType,
|
credentialType: CredentialType,
|
||||||
status: CredentialStatus,
|
status: CredentialStatus,
|
||||||
effectivity: Option[CredentialEffectivity],
|
effectivity: Option[CredentialEffectivity],
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
-- sqlite3
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS groups(
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
group_id BLOB NOT NULL,
|
|
||||||
slug TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_groups_group_id ON groups(group_id);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tags(
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
tag_value TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_value ON tags(tag_value);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tickets(
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
ticket_number INTEGER NOT NULL,
|
|
||||||
group_id BLOB NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
description TEXT NOT NULL,
|
|
||||||
status TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_tickets_number_group
|
|
||||||
ON tickets(group_id, ticket_number);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS ticket_tags(
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
ticket_id BIGINT NOT NULL,
|
|
||||||
tag_id BIGINT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_ticket_tags_ticket_tag
|
|
||||||
ON ticket_tags(ticket_id, tag_id);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS ticket_history(
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
ticket_id BIGINT NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
set_at DATETIME NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_ticket_history_order_ticket_set_at
|
|
||||||
ON ticket_history(ticket_id, set_at);
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package gs.smolban.db.doobie
|
||||||
|
|
||||||
|
import cats.data.EitherT
|
||||||
|
import doobie.*
|
||||||
|
import gs.smolban.auth.Base64
|
||||||
|
import gs.smolban.auth.Credential
|
||||||
|
import gs.smolban.auth.CredentialId
|
||||||
|
import gs.smolban.auth.NewAiAgentAccount
|
||||||
|
import gs.smolban.auth.NewServiceAccount
|
||||||
|
import gs.smolban.auth.Password
|
||||||
|
import gs.smolban.db.AuthDb
|
||||||
|
import gs.smolban.db.DbError
|
||||||
|
import gs.smolban.model.account.Account
|
||||||
|
import gs.smolban.model.account.AccountId
|
||||||
|
import gs.smolban.model.account.AccountName
|
||||||
|
import gs.smolban.model.account.AiAgent
|
||||||
|
import gs.smolban.model.account.PermissionSet
|
||||||
|
import gs.smolban.model.account.ServiceAccount
|
||||||
|
import gs.smolban.model.account.User
|
||||||
|
|
||||||
|
final class DoobieAuthDb extends AuthDb[ConnectionIO] {
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def getUser(name: AccountName): ConnectionIO[Option[User]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def createUser(
|
||||||
|
name: AccountName,
|
||||||
|
initialPassword: Password,
|
||||||
|
initialPermissions: PermissionSet
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, User] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def setUserPassword(
|
||||||
|
id: AccountId,
|
||||||
|
newPassword: Password
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, User] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def listActiveUsers(): ConnectionIO[List[User]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def getServiceAccount(name: AccountName)
|
||||||
|
: ConnectionIO[Option[ServiceAccount]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def createServiceAccount(
|
||||||
|
name: AccountName,
|
||||||
|
owner: AccountId,
|
||||||
|
initialPermissions: PermissionSet
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, NewServiceAccount] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def listActiveServiceAccounts(): ConnectionIO[List[ServiceAccount]] =
|
||||||
|
???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def getAgentAccount(name: AccountName)
|
||||||
|
: ConnectionIO[Option[AiAgent]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def createAgentAccount(
|
||||||
|
name: AccountName,
|
||||||
|
owner: AccountId,
|
||||||
|
initialPermissions: PermissionSet
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, NewAiAgentAccount] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def listActiveAgentAccounts(): ConnectionIO[List[AiAgent]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def rotateClientSecret(
|
||||||
|
accountId: AccountId,
|
||||||
|
credentialId: CredentialId,
|
||||||
|
overlapHours: Int
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, Base64] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def setAccountPermissions(
|
||||||
|
id: AccountId,
|
||||||
|
newPermissions: PermissionSet
|
||||||
|
): EitherT[[A] =>> ConnectionIO[A], DbError, Account] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def listAccountCredentials(id: AccountId)
|
||||||
|
: ConnectionIO[List[Credential]] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def revokeCredential(id: CredentialId)
|
||||||
|
: EitherT[[A] =>> ConnectionIO[A], DbError, Unit] = ???
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def expireCredential(id: CredentialId)
|
||||||
|
: EitherT[[A] =>> ConnectionIO[A], DbError, Unit] = ???
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
package gs.smolban.db.doobie
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.effect.syntax.all.*
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import doobie.*
|
||||||
|
import doobie.implicits.*
|
||||||
|
|
||||||
|
/** Provisions the database for SQLite. This class reflects the current state of
|
||||||
|
* Smolban.
|
||||||
|
*/
|
||||||
|
final class DoobieSqliteDb[F[_]: Sync]:
|
||||||
|
|
||||||
|
def initializeDatabase(xa: Transactor[F]): F[Unit] =
|
||||||
|
(for
|
||||||
|
_ <- setupUser()
|
||||||
|
_ <- setupServiceAccount()
|
||||||
|
_ <- setupAgentAccount()
|
||||||
|
_ <- setupCredential()
|
||||||
|
_ <- setupTag()
|
||||||
|
yield ()).transact(xa)
|
||||||
|
|
||||||
|
def tearDownDatabase(xa: Transactor[F]): F[Unit] =
|
||||||
|
(for _ <- tearDownUser()
|
||||||
|
yield ()).transact(xa) *> withoutTransaction(vacuum()).transact(xa).as(())
|
||||||
|
|
||||||
|
private def withoutTransaction[A](p: ConnectionIO[A]): ConnectionIO[A] =
|
||||||
|
FC.setAutoCommit(true).bracket(_ => p)(_ => FC.setAutoCommit(false))
|
||||||
|
|
||||||
|
private def vacuum(): ConnectionIO[Int] =
|
||||||
|
sql"VACUUM;".update.run
|
||||||
|
|
||||||
|
def setupUser(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- userTable()
|
||||||
|
_ <- userIndexStatusCreatedAt()
|
||||||
|
_ <- userPermissionGlobal()
|
||||||
|
_ <- userPermissionGroup()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def tearDownUser(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- dropUserPermissionGlobal()
|
||||||
|
_ <- dropUserPermissionGroup()
|
||||||
|
_ <- dropUserIndexStatusCreatedAt()
|
||||||
|
_ <- dropUserTable()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def userTable(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS user(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def dropUserTable(): ConnectionIO[Int] =
|
||||||
|
sql"DROP TABLE IF EXISTS user;".update.run
|
||||||
|
|
||||||
|
def userIndexStatusCreatedAt(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_status_created_at ON user(status, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def dropUserIndexStatusCreatedAt(): ConnectionIO[Int] =
|
||||||
|
sql"""DROP INDEX IF EXISTS idx_user_status_created_at;""".update.run
|
||||||
|
|
||||||
|
def userPermissionGlobal(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS user_permission_global(
|
||||||
|
user_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def dropUserPermissionGlobal(): ConnectionIO[Int] =
|
||||||
|
sql"""DROP TABLE IF EXISTS user_permission_global;""".update.run
|
||||||
|
|
||||||
|
def userPermissionGroup(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS user_permission_group(
|
||||||
|
user_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, group_name, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def dropUserPermissionGroup(): ConnectionIO[Int] =
|
||||||
|
sql"""DROP TABLE IF EXISTS user_permission_group;""".update.run
|
||||||
|
|
||||||
|
def setupServiceAccount(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- serviceAccountTable()
|
||||||
|
_ <- serviceAccountIndexStatusCreatedAt()
|
||||||
|
_ <- serviceAccountIndexOwner()
|
||||||
|
_ <- serviceAccountPermissionGlobal()
|
||||||
|
_ <- serviceAccountPermissionGroup()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def serviceAccountTable(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
owner BLOB NOT NULL
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def serviceAccountIndexStatusCreatedAt(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_service_account_status_created_at ON service_account(status, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def serviceAccountIndexOwner(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_service_account_owner ON service_account(owner);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def serviceAccountPermissionGlobal(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account_permission_global(
|
||||||
|
service_account_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (service_account_id, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def serviceAccountPermissionGroup(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account_permission_group(
|
||||||
|
service_account_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (service_account_id, group_name, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def setupAgentAccount(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- agentAccountTable()
|
||||||
|
_ <- agentAccountIndexStatusCreatedAt()
|
||||||
|
_ <- agentAccountIndexOwner()
|
||||||
|
_ <- agentAccountPermissionGlobal()
|
||||||
|
_ <- agentAccountPermissionGroup()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def agentAccountTable(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
owner BLOB NOT NULL
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def agentAccountIndexStatusCreatedAt(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_account_status_created_at ON agent_account(status, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def agentAccountIndexOwner(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_account_owner ON agent_account(owner);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def agentAccountPermissionGlobal(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account_permission_global(
|
||||||
|
agent_account_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (agent_account_id, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def agentAccountPermissionGroup(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account_permission_group(
|
||||||
|
agent_account_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (agent_account_id, group_name, permission)
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def setupCredential(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- credentialTable()
|
||||||
|
_ <- credentialIndexAccountId()
|
||||||
|
_ <- credentialIndexAccountType()
|
||||||
|
_ <- credentialIndexCredentialType()
|
||||||
|
_ <- credentialIndexCredentialStatus()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def credentialTable(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS credential(
|
||||||
|
credential_id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
credential_hash TEXT NOT NULL,
|
||||||
|
account_id BLOB NOT NULL,
|
||||||
|
account_type TEXT NOT NULL,
|
||||||
|
credential_type TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
effective_at DATE NULL,
|
||||||
|
effective_through DATE NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def credentialIndexAccountId(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_credential_account_id ON credential(account_id, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def credentialIndexAccountType(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_credential_account_type ON credential(account_type, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def credentialIndexCredentialType(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_credential_credential_type ON credential(credential_type, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def credentialIndexCredentialStatus(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_credential_credential_status ON credential(status, created_at);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def setupTag(): ConnectionIO[Unit] =
|
||||||
|
for
|
||||||
|
_ <- tagTable()
|
||||||
|
_ <- tagIndexCreatedAt()
|
||||||
|
yield ()
|
||||||
|
|
||||||
|
def tagTable(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE TABLE IF NOT EXISTS tag(
|
||||||
|
tag_value TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
""".update.run
|
||||||
|
|
||||||
|
def tagIndexCreatedAt(): ConnectionIO[Int] =
|
||||||
|
sql"""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tag_created_at ON tag(created_at);
|
||||||
|
""".update.run
|
||||||
|
|
@ -28,27 +28,38 @@ class DoobieTagDbTests extends munit.FunSuite:
|
||||||
): Unit =
|
): Unit =
|
||||||
test(name)(f.unsafeRunSync())
|
test(name)(f.unsafeRunSync())
|
||||||
|
|
||||||
private val dbConfig: Config = Config(
|
|
||||||
jdbcUrl = "jdbc:sqlite:test/doobie_tag_db_tests",
|
|
||||||
driverClassName = Some("org.sqlite.JDBC")
|
|
||||||
)
|
|
||||||
|
|
||||||
private val transactor: Resource[IO, Transactor[IO]] =
|
|
||||||
HikariTransactor.fromConfig[IO](dbConfig)
|
|
||||||
|
|
||||||
private val tagDb: TagDb[ConnectionIO] = new DoobieTagDb(
|
private val tagDb: TagDb[ConnectionIO] = new DoobieTagDb(
|
||||||
CuratedSqlStates.sqlite
|
CuratedSqlStates.sqlite
|
||||||
)
|
)
|
||||||
|
|
||||||
private val clock = Clock.systemDefaultZone()
|
private val clock = Clock.systemDefaultZone()
|
||||||
|
|
||||||
iotest("should create, read, and delete a tag") {
|
private def dbConfig(dbName: String): Config =
|
||||||
|
Config(
|
||||||
|
jdbcUrl = s"jdbc:sqlite:test/$dbName",
|
||||||
|
driverClassName = Some("org.sqlite.JDBC")
|
||||||
|
)
|
||||||
|
|
||||||
|
private def dbTransactor(dbName: String): Resource[IO, Transactor[IO]] =
|
||||||
|
HikariTransactor.fromConfig[IO](dbConfig(dbName))
|
||||||
|
|
||||||
|
private def inDb(dbName: String): Resource[IO, TestDb] =
|
||||||
|
for
|
||||||
|
xa <- dbTransactor(dbName)
|
||||||
|
db <- provision(xa)
|
||||||
|
yield new TestDb(xa, db)
|
||||||
|
|
||||||
|
private def provision(xa: Transactor[IO]): Resource[IO, DoobieSqliteDb[IO]] =
|
||||||
|
val out = new DoobieSqliteDb[IO]
|
||||||
|
Resource.make(
|
||||||
|
out.tearDownDatabase(xa) *> out.initializeDatabase(xa).as(out)
|
||||||
|
)(db => db.tearDownDatabase(xa))
|
||||||
|
|
||||||
|
iotest("(db_0001) should create, read, and delete a tag") {
|
||||||
val tagValue = TagValue.unsafe("x")
|
val tagValue = TagValue.unsafe("x")
|
||||||
val createdAt = CreatedAt.now(clock)
|
val createdAt = CreatedAt.now(clock)
|
||||||
transactor.use { xa =>
|
inDb("db_0001").use { testDb =>
|
||||||
(for
|
(for
|
||||||
_ <- dropTable().run
|
|
||||||
_ <- createTable().run
|
|
||||||
tag <- tagDb.createTag(tagValue, createdAt).value
|
tag <- tagDb.createTag(tagValue, createdAt).value
|
||||||
t2 <- tagDb.readTag(tagValue)
|
t2 <- tagDb.readTag(tagValue)
|
||||||
result <- tagDb.deleteTag(tagValue)
|
result <- tagDb.deleteTag(tagValue)
|
||||||
|
|
@ -56,7 +67,7 @@ class DoobieTagDbTests extends munit.FunSuite:
|
||||||
assertEquals(tag, Right(Tag(tagValue, createdAt)))
|
assertEquals(tag, Right(Tag(tagValue, createdAt)))
|
||||||
assertEquals(t2, tag.toOption)
|
assertEquals(t2, tag.toOption)
|
||||||
assert(result)
|
assert(result)
|
||||||
).transact(xa)
|
).transact(testDb.xa)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
modules/db/src/test/scala/gs/smolban/db/doobie/TestDb.scala
Normal file
10
modules/db/src/test/scala/gs/smolban/db/doobie/TestDb.scala
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package gs.smolban.db.doobie
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import doobie.util.transactor.Transactor
|
||||||
|
import gs.smolban.db.doobie.DoobieSqliteDb
|
||||||
|
|
||||||
|
final class TestDb(
|
||||||
|
val xa: Transactor[IO],
|
||||||
|
val sqlite: DoobieSqliteDb[IO]
|
||||||
|
)
|
||||||
8
sql/README.md
Normal file
8
sql/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Smolban SQL
|
||||||
|
|
||||||
|
This directory contains the _reference SQL_ for Smolban. Smolban supports both
|
||||||
|
SQLite and PostgreSQL.
|
||||||
|
|
||||||
|
For implementations that provision and migrate the database:
|
||||||
|
|
||||||
|
- [DoobieSqliteDb](../modules/db/src/main/scala/gs/smolban/db/doobie/DoobieSqliteDb.scala)
|
||||||
97
sql/sqlite/1.sql
Normal file
97
sql/sqlite/1.sql
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS user(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_status_created_at ON user(status, created_at);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_permission_global(
|
||||||
|
user_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_permission_group(
|
||||||
|
user_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, group_name, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
owner BLOB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_service_account_status_created_at ON service_account(status, created_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_service_account_owner ON service_account(owner);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account_permission_global(
|
||||||
|
service_account_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (service_account_id, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS service_account_permission_group(
|
||||||
|
service_account_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (service_account_id, group_name, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account(
|
||||||
|
id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
owner BLOB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_agent_account_status_created_at ON agent_account(status, created_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_agent_account_owner ON agent_account(owner);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account_permission_global(
|
||||||
|
agent_account_id BLOB NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (agent_account_id, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_account_permission_group(
|
||||||
|
agent_account_id BLOB NOT NULL,
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
permission TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (agent_account_id, group_name, permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS credential(
|
||||||
|
credential_id BLOB NOT NULL PRIMARY KEY,
|
||||||
|
credential_hash TEXT NOT NULL,
|
||||||
|
account_id BLOB NOT NULL,
|
||||||
|
account_type TEXT NOT NULL,
|
||||||
|
credential_type TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
effective_at DATE NULL,
|
||||||
|
effective_through DATE NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_credential_account_id ON credential(account_id, created_at);
|
||||||
|
CREATE INDEX idx_credential_account_type ON credential(account_type, created_at);
|
||||||
|
CREATE INDEX idx_credential_credential_type ON credential(credential_type, created_at);
|
||||||
|
CREATE INDEX idx_credential_credential_status ON credential(credential_status, created_at);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tag(
|
||||||
|
tag_value TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_tag_created_at ON tag(created_at);
|
||||||
Loading…
Add table
Reference in a new issue