diff --git a/api/dc_bot.API/dc_bot.API/Program.cs b/api/dc_bot.API/dc_bot.API/Program.cs index 2d6e2ac..473a92c 100644 --- a/api/dc_bot.API/dc_bot.API/Program.cs +++ b/api/dc_bot.API/dc_bot.API/Program.cs @@ -17,8 +17,6 @@ var app = builder.Build(); using (var scope = app.Services.CreateScope()) { - var context = scope.ServiceProvider.GetRequiredService(); - context.Database.EnsureCreated(); var migrationService = scope.ServiceProvider.GetRequiredService(); await migrationService.Migrate(); } diff --git a/api/dc_bot.API/dc_bot.db/AppDbContext.cs b/api/dc_bot.API/dc_bot.db/AppDbContext.cs index 51246de..07ec0f3 100644 --- a/api/dc_bot.API/dc_bot.db/AppDbContext.cs +++ b/api/dc_bot.API/dc_bot.db/AppDbContext.cs @@ -1,5 +1,5 @@ -using dc_bot.db.Model; -using dc_bot.db.Model.administration; +using dc_bot.db.Model.administration; +using dc_bot.db.Model.permission; using dc_bot.db.Model.system; using Microsoft.EntityFrameworkCore; @@ -8,19 +8,17 @@ namespace dc_bot.db; public class AppDbContext(DbContextOptions options) : DbContext(options) { public required DbSet ExecutedMigrations { get; set; } + public required DbSet Users { get; set; } + public required DbSet ApiKeys { get; set; } + + public required DbSet Roles { get; set; } + public required DbSet Permissions { get; set; } + public required DbSet RolePermissions { get; set; } + public required DbSet ApiKeyPermissions { get; set; } + public required DbSet RoleUsers { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(e => - { - e.ToTable("_executed_migrations", "system"); - e.HasKey(e => e.MigrationId); - }); - - modelBuilder.Entity(e => - { - e.ToTable("users", "administration"); - }); } } \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/MigrationService.cs b/api/dc_bot.API/dc_bot.db/MigrationService.cs index fbaf948..9b0fcd8 100644 --- a/api/dc_bot.API/dc_bot.db/MigrationService.cs +++ b/api/dc_bot.API/dc_bot.db/MigrationService.cs @@ -1,6 +1,6 @@ -using System.Runtime.CompilerServices; using dc_bot.db.Model.system; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; namespace dc_bot.db; @@ -34,20 +34,21 @@ public class MigrationService using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); + await EnsureExecutedMigrationsTableExistsAsync(context); + var appliedMigrations = await context.ExecutedMigrations .Select(m => m.MigrationId) .ToListAsync(); - var appliedMigrationNames = appliedMigrations - .Select(m => m.Replace(".sql", string.Empty)) - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - var migrations = GetMigrations() - .Where(m => !appliedMigrationNames.Contains(m)) + var migrations = GetMigrations(); + var migrationsToApply = migrations + .Where(m => !appliedMigrations.Contains(m.Replace(".sql", string.Empty))) .ToList(); - foreach (var migration in migrations) + foreach (var migration in migrationsToApply) { + await using var transaction = await context.Database.BeginTransactionAsync(); + try { var migrationPath = Path.Combine(MigrationDirectory, migration); @@ -57,18 +58,59 @@ public class MigrationService var executedMigration = new ExecutedMigration { - MigrationId = migration, + MigrationId = migration.Replace(".sql", string.Empty), Created = DateTime.UtcNow, Updated = DateTime.UtcNow }; context.ExecutedMigrations.Add(executedMigration); + await context.SaveChangesAsync(); + await transaction.CommitAsync(); } catch (Exception ex) { - Console.WriteLine($"Error applying migration {migration}: {ex.Message}"); + try + { + await transaction.RollbackAsync(); + } + catch + { + // ignored + } + + Console.WriteLine($"Error applying migration: {migration} => {ex.Message}"); throw; } } } + + private async Task EnsureExecutedMigrationsTableExistsAsync(AppDbContext context) + { + var entityType = context.Model.FindEntityType(typeof(ExecutedMigration)); + if (entityType == null) + return; + + var schema = entityType.GetSchema() ?? "public"; + var tableName = entityType.GetTableName() ?? entityType.GetDefaultTableName(); + + // Spaltennamen ermitteln (relational) + var storeIdentifier = StoreObjectIdentifier.Table(tableName, schema); + string migrationIdCol = entityType.FindProperty(nameof(ExecutedMigration.MigrationId)) + .GetColumnName(storeIdentifier); + string createdCol = entityType.FindProperty(nameof(ExecutedMigration.Created)) + .GetColumnName(storeIdentifier); + string updatedCol = entityType.FindProperty(nameof(ExecutedMigration.Updated)) + .GetColumnName(storeIdentifier); + + var createSchemaSql = $"CREATE SCHEMA IF NOT EXISTS \"{schema}\";"; + var createTableSql = $@" + CREATE TABLE IF NOT EXISTS ""{schema}"".""{tableName}"" ( + ""{migrationIdCol}"" TEXT PRIMARY KEY, + ""{createdCol}"" TIMESTAMPTZ NOT NULL, + ""{updatedCol}"" TIMESTAMPTZ NOT NULL + ); + "; + + await context.Database.ExecuteSqlRawAsync(createSchemaSql + createTableSql); + } } \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/IDbModel.cs b/api/dc_bot.API/dc_bot.db/Model/IDbModel.cs index c2a7d49..6729541 100644 --- a/api/dc_bot.API/dc_bot.db/Model/IDbModel.cs +++ b/api/dc_bot.API/dc_bot.db/Model/IDbModel.cs @@ -2,8 +2,9 @@ namespace dc_bot.db.Model; public interface IDbModel { + public double Id { get; set; } public bool Deleted { get; set; } - public Double EditorId { get; set; } + public double EditorId { get; set; } public DateTimeOffset Created { get; set; } public DateTimeOffset Updated { get; set; } } \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/administration/ApiKey.cs b/api/dc_bot.API/dc_bot.db/Model/administration/ApiKey.cs new file mode 100644 index 0000000..43fa395 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/administration/ApiKey.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.administration; + +[Table("api_keys", Schema = "administration")] +public class ApiKey : IDbModel +{ + public double Id { get; set; } + public required string Identifier { get; init; } + public required string Key { get; init; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/administration/User.cs b/api/dc_bot.API/dc_bot.db/Model/administration/User.cs index 6d14960..7a1db2d 100644 --- a/api/dc_bot.API/dc_bot.db/Model/administration/User.cs +++ b/api/dc_bot.API/dc_bot.db/Model/administration/User.cs @@ -1,8 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; + namespace dc_bot.db.Model.administration; +[Table("users", Schema = "administration")] public class User : IDbModel { - public Double Id { get; set; } + public double Id { get; set; } public required string KeycloakId { get; init; } public bool Deleted { get; set; } diff --git a/api/dc_bot.API/dc_bot.db/Model/permission/ApiKeyPermission.cs b/api/dc_bot.API/dc_bot.db/Model/permission/ApiKeyPermission.cs new file mode 100644 index 0000000..2e03f81 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/permission/ApiKeyPermission.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.permission; + +[Table("api_key_permissions", Schema = "permission")] +public class ApiKeyPermission : IDbModel +{ + public double Id { get; set; } + public double ApiKeyId { get; set; } + public double PermissionId { get; set; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/permission/Permission.cs b/api/dc_bot.API/dc_bot.db/Model/permission/Permission.cs new file mode 100644 index 0000000..7545318 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/permission/Permission.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.permission; + +[Table("permissions", Schema = "permission")] +public class Permission : IDbModel +{ + public double Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/permission/Role.cs b/api/dc_bot.API/dc_bot.db/Model/permission/Role.cs new file mode 100644 index 0000000..f56acf5 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/permission/Role.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.permission; + +[Table("roles", Schema = "permission")] +public class Role : IDbModel +{ + public double Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/permission/RolePermission.cs b/api/dc_bot.API/dc_bot.db/Model/permission/RolePermission.cs new file mode 100644 index 0000000..bd0b7c4 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/permission/RolePermission.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.permission; + +[Table("role_permissions", Schema = "permission")] +public class RolePermission : IDbModel +{ + public double Id { get; set; } + public double RoleId { get; set; } + public double PermissionId { get; set; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/permission/RoleUser.cs b/api/dc_bot.API/dc_bot.db/Model/permission/RoleUser.cs new file mode 100644 index 0000000..341c738 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Model/permission/RoleUser.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace dc_bot.db.Model.permission; + +[Table("role_users", Schema = "permission")] +public class RoleUser : IDbModel +{ + public double Id { get; set; } + public double UserId { get; set; } + public double PermissionId { get; set; } + + public bool Deleted { get; set; } + public double EditorId { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset Updated { get; set; } +} \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Model/system/ExecutedMigration.cs b/api/dc_bot.API/dc_bot.db/Model/system/ExecutedMigration.cs index e93ae0b..f1421d3 100644 --- a/api/dc_bot.API/dc_bot.db/Model/system/ExecutedMigration.cs +++ b/api/dc_bot.API/dc_bot.db/Model/system/ExecutedMigration.cs @@ -1,7 +1,10 @@ +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; namespace dc_bot.db.Model.system; +[Table("_executed_migrations", Schema = "system")] +[PrimaryKey(nameof(MigrationId))] public class ExecutedMigration { public required string MigrationId { get; set; } diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-05-inital-migration.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-05-inital-migration.sql index 4107afe..06489c6 100644 --- a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-05-inital-migration.sql +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-05-inital-migration.sql @@ -4,6 +4,9 @@ CREATE SCHEMA IF NOT EXISTS system; CREATE TABLE IF NOT EXISTS system._executed_migrations ( MigrationId VARCHAR(255) PRIMARY KEY, - Created timestamptz NOT NULL DEFAULT NOW(), - Updated timestamptz NOT NULL DEFAULT NOW() -); \ No newline at end of file + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW() +); + +DROP EXTENSION IF EXISTS fuzzystrmatch; +CREATE EXTENSION fuzzystrmatch SCHEMA public; \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-inital-model.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-users.sql similarity index 67% rename from api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-inital-model.sql rename to api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-users.sql index e918e0e..0b790cb 100644 --- a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-inital-model.sql +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-05-21-15-users.sql @@ -6,13 +6,15 @@ CREATE TABLE IF NOT EXISTS administration.users KeycloakId UUID NOT NULL, -- for history Deleted BOOLEAN NOT NULL DEFAULT FALSE, - EditorId INT NULL REFERENCES administration.users (Id), + EditorId BIGINT NULL REFERENCES administration.users (Id), Created timestamptz NOT NULL DEFAULT NOW(), - Updated timestamptz NOT NULL DEFAULT NOW(), - - CONSTRAINT UC_KeycloakId UNIQUE (KeycloakId) + Updated timestamptz NOT NULL DEFAULT NOW() ); +CREATE UNIQUE INDEX IF NOT EXISTS IX_UC_KeycloakId + ON administration.users (KeycloakId) + WHERE KeycloakId != '00000000-0000-0000-0000-000000000000'; + CREATE TABLE IF NOT EXISTS administration.users_history ( LIKE administration.users diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-15-permissions.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-15-permissions.sql new file mode 100644 index 0000000..e9421c1 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-15-permissions.sql @@ -0,0 +1,105 @@ +CREATE SCHEMA IF NOT EXISTS permission; + +-- Permissions +CREATE TABLE permission.permissions +( + Id SERIAL PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Description TEXT NULL, + + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_PermissionName UNIQUE (Name) +); + +CREATE TABLE permission.permissions_history +( + LIKE permission.permissions +); + +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON permission.permissions + FOR EACH ROW +EXECUTE PROCEDURE public.history_trigger_function(); + +-- Roles +CREATE TABLE permission.roles +( + Id SERIAL PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Description TEXT NULL, + + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_RoleName UNIQUE (Name) +); + +CREATE TABLE permission.roles_history +( + LIKE permission.roles +); + +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON permission.roles + FOR EACH ROW +EXECUTE PROCEDURE public.history_trigger_function(); + +-- Role permissions +CREATE TABLE permission.role_permissions +( + Id SERIAL PRIMARY KEY, + RoleId BIGINT NOT NULL REFERENCES permission.roles (Id) ON DELETE CASCADE, + PermissionId BIGINT NOT NULL REFERENCES permission.permissions (Id) ON DELETE CASCADE, + + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_RolePermission UNIQUE (RoleId, PermissionId) +); + +CREATE TABLE permission.role_permissions_history +( + LIKE permission.role_permissions +); + +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON permission.role_permissions + FOR EACH ROW +EXECUTE PROCEDURE public.history_trigger_function(); + +-- Role user +CREATE TABLE permission.role_users +( + Id SERIAL PRIMARY KEY, + RoleId BIGINT NOT NULL REFERENCES permission.roles (Id) ON DELETE CASCADE, + UserId BIGINT NOT NULL REFERENCES administration.users (Id) ON DELETE CASCADE, + + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_RoleUser UNIQUE (RoleId, UserId) +); + +CREATE TABLE permission.role_users_history +( + LIKE permission.role_users +); + +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON permission.role_users + FOR EACH ROW +EXECUTE PROCEDURE public.history_trigger_function(); \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-16-api-keys.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-16-api-keys.sql new file mode 100644 index 0000000..0cbc01e --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-16-api-keys.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS administration.api_keys +( + Id SERIAL PRIMARY KEY, + Identifier VARCHAR(255) NOT NULL, + KeyString VARCHAR(255) NOT NULL, + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + + CONSTRAINT UC_Identifier_Key UNIQUE (Identifier, KeyString), + CONSTRAINT UC_Key UNIQUE (KeyString) +); + +CREATE TABLE IF NOT EXISTS administration.api_keys_history +( + LIKE administration.api_keys +); + +CREATE TRIGGER api_keys_history_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON administration.api_keys + FOR EACH ROW +EXECUTE FUNCTION public.history_trigger_function(); + +CREATE TABLE permission.api_key_permissions +( + Id SERIAL PRIMARY KEY, + ApiKeyId BIGINT NOT NULL REFERENCES administration.api_keys (Id) ON DELETE CASCADE, + PermissionId BIGINT NOT NULL REFERENCES permission.permissions (Id) ON DELETE CASCADE, + + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_ApiKeyPermission UNIQUE (ApiKeyId, PermissionId) +); + +CREATE TABLE permission.api_key_permissions_history +( + LIKE permission.api_key_permissions +); + +CREATE TRIGGER versioning_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON permission.api_key_permissions + FOR EACH ROW +EXECUTE PROCEDURE public.history_trigger_function(); \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-17-settings.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-17-settings.sql new file mode 100644 index 0000000..1f4f644 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-17-settings.sql @@ -0,0 +1,26 @@ +CREATE SCHEMA IF NOT EXISTS system; + +CREATE TABLE IF NOT EXISTS system.settings +( + Id SERIAL PRIMARY KEY, + Key TEXT NOT NULL, + Value TEXT NOT NULL, + Type TEXT NOT NULL DEFAULT 'string' CHECK (type IN ('string', 'number', 'boolean')), + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UC_Key UNIQUE (Key) +); + +CREATE TABLE system.settings_history +( + LIKE system.settings +); + +CREATE TRIGGER settings_history_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON system.settings + FOR EACH ROW +EXECUTE FUNCTION public.history_trigger_function(); \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-18-feature-flags.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-18-feature-flags.sql new file mode 100644 index 0000000..07e1182 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-18-feature-flags.sql @@ -0,0 +1,25 @@ +CREATE SCHEMA IF NOT EXISTS system; + +CREATE TABLE IF NOT EXISTS system.feature_flags +( + Id SERIAL PRIMARY KEY, + Key TEXT NOT NULL, + Value BOOLEAN NOT NULL, + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_FeatureFlagKey UNIQUE (Key) +); + +CREATE TABLE system.feature_flags_history +( + LIKE system.feature_flags +); + +CREATE TRIGGER feature_flags_history_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON system.feature_flags + FOR EACH ROW +EXECUTE FUNCTION public.history_trigger_function(); \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-19-user-settings.sql b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-19-user-settings.sql new file mode 100644 index 0000000..da5afc1 --- /dev/null +++ b/api/dc_bot.API/dc_bot.db/Scripts/2026-01-06-01-19-user-settings.sql @@ -0,0 +1,26 @@ +CREATE SCHEMA IF NOT EXISTS public; + +CREATE TABLE IF NOT EXISTS public.user_settings +( + Id SERIAL PRIMARY KEY, + Key TEXT NOT NULL, + Value TEXT NOT NULL, + UserId BIGINT NOT NULL REFERENCES administration.users (Id) ON DELETE CASCADE, + -- for history + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + EditorId BIGINT NULL REFERENCES administration.users (Id), + Created timestamptz NOT NULL DEFAULT NOW(), + Updated timestamptz NOT NULL DEFAULT NOW(), + CONSTRAINT UQ_UserSetting UNIQUE (UserId, Key) +); + +CREATE TABLE public.user_settings_history +( + LIKE public.user_settings +); + +CREATE TRIGGER user_settings_history_trigger + BEFORE INSERT OR UPDATE OR DELETE + ON public.user_settings + FOR EACH ROW +EXECUTE FUNCTION public.history_trigger_function(); \ No newline at end of file diff --git a/api/dc_bot.API/dc_bot.db/dc_bot.db.csproj b/api/dc_bot.API/dc_bot.db/dc_bot.db.csproj index 34b5fce..b34eff6 100644 --- a/api/dc_bot.API/dc_bot.db/dc_bot.db.csproj +++ b/api/dc_bot.API/dc_bot.db/dc_bot.db.csproj @@ -10,6 +10,36 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always +