Working on this data setup logic

This commit is contained in:
Janus C. H. Knudsen 2025-01-28 14:51:09 +01:00
parent 384cc3c6fd
commit 447b27f69b
16 changed files with 409 additions and 211 deletions

View file

@ -2,7 +2,7 @@ using System.Data;
using Insight.Database; using Insight.Database;
namespace Configuration.Core; namespace Configuration.Core;
public class ConfigurationRepository : IConfigurationRepository public class ConfigurationRepository : IConfigurationRepository
{ {
private readonly IDbConnection _connection; private readonly IDbConnection _connection;
@ -16,8 +16,8 @@ namespace Configuration.Core;
const string sql = @" const string sql = @"
SELECT id, key, value, label, content_type, valid_from, expires_at, created_at, modified_at, etag SELECT id, key, value, label, content_type, valid_from, expires_at, created_at, modified_at, etag
FROM prod.app_configuration FROM prod.app_configuration
WHERE (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP) WHERE (expires_at IS NULL OR expires_at <= CURRENT_TIMESTAMP)
AND (valid_from IS NULL OR valid_from <= CURRENT_TIMESTAMP)"; AND (valid_from IS NULL OR valid_from >= CURRENT_TIMESTAMP)";
return await _connection.QueryAsync<AppConfiguration>(sql); return await _connection.QueryAsync<AppConfiguration>(sql);
} }

View file

@ -1,56 +1,80 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using System.Data;
namespace Configuration.Core; namespace Configuration.Core
public class JsonConfigurationSection : IConfigurationSection
{ {
public class JsonConfigurationSection : IConfigurationSection
{
private readonly JObject _data; private readonly JObject _data;
private readonly string _path; private readonly string _path;
private readonly string _normalizedPath;
public JsonConfigurationSection(JObject data, string path) public JsonConfigurationSection(JObject data, string path)
{ {
_data = data; _data = data ?? throw new ArgumentNullException(nameof(data));
_path = path; _path = path ?? throw new ArgumentNullException(nameof(path));
_normalizedPath = NormalizePath(_path);
} }
public string this[string key] public string this[string key]
{ {
get => _data.SelectToken($"{_path.Replace(":", ".")}.{key.Replace(":", ".")}")?.ToString(); get
set => throw new NotImplementedException(); {
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
var token = _data.SelectToken($"{_normalizedPath}.{NormalizePath(key)}");
return token?.ToString();
}
set => throw new NotImplementedException("Setting values is not supported.");
} }
public string Key => _path.Split(':').Last(); public string Key => _path.Split(':').Last();
public string Path => _path; public string Path => _path;
public string Value public string Value
{ {
get => _data.SelectToken(_path.Replace(":", "."))?.ToString(); get
set => throw new NotImplementedException(); {
var token = _data.SelectToken(_normalizedPath);
return token?.ToString();
}
set => throw new NotImplementedException("Setting values is not supported.");
} }
public IConfigurationSection GetSection(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
public IConfigurationSection GetSection(string key) => return new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? key : $"{_path}:{key}");
new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? key : $"{_path}:{key}"); }
public JToken GetToken() => _data.SelectToken(_path.Replace(":", ".")); public JToken GetToken() => _data.SelectToken(_normalizedPath);
public IEnumerable<IConfigurationSection> GetChildren() public IEnumerable<IConfigurationSection> GetChildren()
{ {
var token = _data.SelectToken(_path.Replace(":", ".")); var token = _data.SelectToken(_normalizedPath);
if (token is JObject obj) if (token is JObject obj)
{ {
return obj.Properties() return obj.Properties()
.Select(p => new JsonConfigurationSection(_data, .Select(p => new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? p.Name : $"{_path}:{p.Name}"));
string.IsNullOrEmpty(_path) ? p.Name : $"{_path}:{p.Name}"));
} }
return Enumerable.Empty<IConfigurationSection>(); return Enumerable.Empty<IConfigurationSection>();
} }
public T Get<T>() where T : class public T Get<T>() where T : class
{ {
var token = _data.SelectToken(_path.Replace(":", ".")); var token = _data.SelectToken(_normalizedPath);
return token?.ToObject<T>(); return token?.ToObject<T>();
} }
public IChangeToken GetReloadToken() => new ConfigurationReloadToken(); public IChangeToken GetReloadToken() => new ConfigurationReloadToken();
private static string NormalizePath(string path)
{
return path?.Replace(":", ".", StringComparison.Ordinal) ?? string.Empty;
}
}
} }

View file

@ -2,20 +2,27 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace Configuration.Core; namespace Configuration.Core
public class KeyValueConfigurationBuilder
{ {
public class KeyValueConfigurationBuilder
{
private readonly IConfigurationRepository _repository; private readonly IConfigurationRepository _repository;
private readonly JObject _rootObject = new(); private readonly JObject _rootObject = new();
private ConfigurationReloadToken _reloadToken = new(); private ConfigurationReloadToken _reloadToken = new();
private IConfiguration _configuration; private IConfiguration _configuration;
private readonly object _configurationLock = new();
public KeyValueConfigurationBuilder(IConfigurationRepository repository) public KeyValueConfigurationBuilder(IConfigurationRepository repository)
{ {
_repository = repository; _repository = repository ?? throw new ArgumentNullException(nameof(repository));
} }
/// <summary>
/// Loads configurations from the repository and builds the configuration tree.
/// </summary>
public async Task LoadConfiguration() public async Task LoadConfiguration()
{
try
{ {
var configurations = await _repository.GetActiveConfigurations(); var configurations = await _repository.GetActiveConfigurations();
foreach (var config in configurations) foreach (var config in configurations)
@ -24,8 +31,26 @@ namespace Configuration.Core;
} }
OnReload(); OnReload();
} }
catch (Exception ex)
{
// Log the exception or handle it as needed
throw new InvalidOperationException("Failed to load configurations.", ex);
}
}
/// <summary>
/// Adds a key-value pair to the configuration tree.
/// </summary>
/// <param name="key">The key to add.</param>
/// <param name="jsonValue">The JSON value to add.</param>
public void AddKeyValue(string key, string jsonValue) public void AddKeyValue(string key, string jsonValue)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
if (string.IsNullOrEmpty(jsonValue))
throw new ArgumentNullException(nameof(jsonValue));
try
{ {
var valueObject = JsonConvert.DeserializeObject<JObject>(jsonValue); var valueObject = JsonConvert.DeserializeObject<JObject>(jsonValue);
var parts = key.Split(':'); var parts = key.Split(':');
@ -43,14 +68,36 @@ namespace Configuration.Core;
current[parts[^1]] = valueObject; current[parts[^1]] = valueObject;
} }
catch (JsonException ex)
{
throw new ArgumentException("Invalid JSON value.", nameof(jsonValue), ex);
}
}
private void OnReload() private void OnReload()
{ {
var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()); var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
previousToken.OnReload(); previousToken.OnReload();
_configuration = null; _configuration = null; // Reset the configuration to force a rebuild
} }
public IConfiguration Build() => /// <summary>
_configuration ??= new JsonConfiguration(_rootObject, _reloadToken); /// Builds the configuration instance.
/// </summary>
/// <returns>The built <see cref="IConfiguration"/> instance.</returns>
public IConfiguration Build()
{
if (_configuration == null)
{
lock (_configurationLock)
{
if (_configuration == null)
{
_configuration = new JsonConfiguration(_rootObject, _reloadToken);
}
}
}
return _configuration;
}
}
} }

View file

@ -11,11 +11,20 @@ public class ConfigurationDatabaseSetup
{ {
_connection = connection; _connection = connection;
} }
public async Task CreateDatabaseStructure(IDbConnection connection)
{
await CreateConfigurationTable();
await CreateHistoryTable();
await CreateConfigurationIndexes();
await CreateModifiedAtTrigger();
await CreateNotifyTrigger();
await CreateHistoryTrigger();
}
public async Task CreateConfigurationTable() public async Task CreateConfigurationTable()
{ {
const string sql = @" const string sql = @"
CREATE TABLE prod.app_configuration ( CREATE TABLE app_configuration (
id bigserial NOT NULL, id bigserial NOT NULL,
""key"" varchar(255) NOT NULL, ""key"" varchar(255) NOT NULL,
value text NULL, value text NULL,
@ -34,7 +43,7 @@ public class ConfigurationDatabaseSetup
public async Task CreateHistoryTable() public async Task CreateHistoryTable()
{ {
const string sql = @" const string sql = @"
CREATE TABLE prod.app_configuration_history ( CREATE TABLE app_configuration_history (
history_id bigserial NOT NULL, history_id bigserial NOT NULL,
action_type char(1) NOT NULL, action_type char(1) NOT NULL,
action_timestamp timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, action_timestamp timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -57,15 +66,15 @@ public class ConfigurationDatabaseSetup
public async Task CreateConfigurationIndexes() public async Task CreateConfigurationIndexes()
{ {
const string sql = @" const string sql = @"
CREATE INDEX idx_app_configuration_key ON prod.app_configuration(""key""); CREATE INDEX idx_app_configuration_key ON app_configuration(""key"");
CREATE INDEX idx_app_configuration_validity ON prod.app_configuration(valid_from, expires_at);"; CREATE INDEX idx_app_configuration_validity ON app_configuration(valid_from, expires_at);";
await _connection.ExecuteAsync(sql); await _connection.ExecuteAsync(sql);
} }
public async Task CreateModifiedAtTrigger() public async Task CreateModifiedAtTrigger()
{ {
const string sql = @" const string sql = @"
CREATE OR REPLACE FUNCTION prod.update_app_configuration_modified_at() CREATE OR REPLACE FUNCTION update_app_configuration_modified_at()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
BEGIN BEGIN
NEW.modified_at = CURRENT_TIMESTAMP; NEW.modified_at = CURRENT_TIMESTAMP;
@ -74,16 +83,16 @@ public class ConfigurationDatabaseSetup
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_modified_at CREATE TRIGGER trg_app_configuration_modified_at
BEFORE UPDATE ON prod.app_configuration BEFORE UPDATE ON app_configuration
FOR EACH ROW FOR EACH ROW
EXECUTE FUNCTION prod.update_app_configuration_modified_at();"; EXECUTE FUNCTION update_app_configuration_modified_at();";
await _connection.ExecuteAsync(sql); await _connection.ExecuteAsync(sql);
} }
public async Task CreateNotifyTrigger() public async Task CreateNotifyTrigger()
{ {
const string sql = @" const string sql = @"
CREATE OR REPLACE FUNCTION prod.notify_app_configuration_change() CREATE OR REPLACE FUNCTION notify_app_configuration_change()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
BEGIN BEGIN
PERFORM pg_notify('config_changes', NEW.key); PERFORM pg_notify('config_changes', NEW.key);
@ -92,20 +101,20 @@ public class ConfigurationDatabaseSetup
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_notify CREATE TRIGGER trg_app_configuration_notify
AFTER INSERT OR UPDATE ON prod.app_configuration AFTER INSERT OR UPDATE ON app_configuration
FOR EACH ROW FOR EACH ROW
EXECUTE FUNCTION prod.notify_app_configuration_change();"; EXECUTE FUNCTION notify_app_configuration_change();";
await _connection.ExecuteAsync(sql); await _connection.ExecuteAsync(sql);
} }
public async Task CreateHistoryTrigger() public async Task CreateHistoryTrigger()
{ {
const string sql = @" const string sql = @"
CREATE OR REPLACE FUNCTION prod.log_app_configuration_changes() CREATE OR REPLACE FUNCTION log_app_configuration_changes()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
BEGIN BEGIN
IF (TG_OP = 'INSERT') THEN IF (TG_OP = 'INSERT') THEN
INSERT INTO prod.app_configuration_history ( INSERT INTO app_configuration_history (
action_type, id, ""key"", value, label, content_type, action_type, id, ""key"", value, label, content_type,
valid_from, expires_at, created_at, modified_at, etag valid_from, expires_at, created_at, modified_at, etag
) )
@ -114,7 +123,7 @@ public class ConfigurationDatabaseSetup
NEW.valid_from, NEW.expires_at, NEW.created_at, NEW.modified_at, NEW.etag NEW.valid_from, NEW.expires_at, NEW.created_at, NEW.modified_at, NEW.etag
); );
ELSIF (TG_OP = 'UPDATE') THEN ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO prod.app_configuration_history ( INSERT INTO app_configuration_history (
action_type, id, ""key"", value, label, content_type, action_type, id, ""key"", value, label, content_type,
valid_from, expires_at, created_at, modified_at, etag valid_from, expires_at, created_at, modified_at, etag
) )
@ -123,7 +132,7 @@ public class ConfigurationDatabaseSetup
OLD.valid_from, OLD.expires_at, OLD.created_at, OLD.modified_at, OLD.etag OLD.valid_from, OLD.expires_at, OLD.created_at, OLD.modified_at, OLD.etag
); );
ELSIF (TG_OP = 'DELETE') THEN ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO prod.app_configuration_history ( INSERT INTO app_configuration_history (
action_type, id, ""key"", value, label, content_type, action_type, id, ""key"", value, label, content_type,
valid_from, expires_at, created_at, modified_at, etag valid_from, expires_at, created_at, modified_at, etag
) )
@ -137,18 +146,10 @@ public class ConfigurationDatabaseSetup
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_app_configuration_history CREATE TRIGGER trg_app_configuration_history
AFTER INSERT OR UPDATE OR DELETE ON prod.app_configuration AFTER INSERT OR UPDATE OR DELETE ON app_configuration
FOR EACH ROW EXECUTE FUNCTION prod.log_app_configuration_changes();"; FOR EACH ROW EXECUTE FUNCTION log_app_configuration_changes();";
await _connection.ExecuteAsync(sql); await _connection.ExecuteAsync(sql);
} }
public async Task CreateDatabaseStructure(IDbConnection connection)
{
await CreateConfigurationTable();
await CreateHistoryTable();
await CreateConfigurationIndexes();
await CreateModifiedAtTrigger();
await CreateNotifyTrigger();
await CreateHistoryTrigger();
}
} }

View file

@ -9,4 +9,9 @@
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="AuditSystem\" />
<Folder Include="NavigationSystem\" />
</ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,115 @@
using System;
using System.Data;
using System.Threading.Tasks;
namespace Database.Tenants
{
public class DbSetup
{
private readonly IDbConnection _db;
public DbSetup(IDbConnection db)
{
_db = db ?? throw new ArgumentNullException(nameof(db));
}
/// <summary>
/// Creates the users table in the ptmain schema.
/// </summary>
public void CreateUsersTable()
{
ExecuteInTransaction(@"
CREATE TABLE IF NOT EXISTS ptmain.users (
id SERIAL PRIMARY KEY,
email VARCHAR(256) NOT NULL UNIQUE,
password_hash VARCHAR(256) NOT NULL,
security_stamp VARCHAR(36) NOT NULL,
email_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
access_failed_count INTEGER NOT NULL DEFAULT 0,
lockout_enabled BOOLEAN NOT NULL DEFAULT TRUE,
lockout_end TIMESTAMPTZ NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMPTZ NULL
);");
}
/// <summary>
/// Creates the tenants table in the ptmain schema.
/// </summary>
public void CreateTenantsTable()
{
ExecuteInTransaction(@"
CREATE TABLE IF NOT EXISTS ptmain.tenants (
id SERIAL PRIMARY KEY,
connection_string VARCHAR(500) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by INTEGER NOT NULL REFERENCES ptmain.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);");
}
/// <summary>
/// Creates the user_tenants table in the ptmain schema.
/// </summary>
public void CreateUserTenantsTable()
{
ExecuteInTransaction(@"
CREATE TABLE IF NOT EXISTS ptmain.user_tenants (
user_id INTEGER NOT NULL REFERENCES ptmain.users(id),
tenant_id INTEGER NOT NULL REFERENCES ptmain.tenants(id),
pin_code VARCHAR(10) NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, tenant_id)
);");
}
/// <summary>
/// Sets up Row Level Security (RLS) for the tenants and user_tenants tables.
/// </summary>
public void SetupRLS()
{
ExecuteInTransaction(
"ALTER TABLE ptmain.tenants ENABLE ROW LEVEL SECURITY;",
"ALTER TABLE ptmain.user_tenants ENABLE ROW LEVEL SECURITY;",
"DROP POLICY IF EXISTS tenant_access ON ptmain.tenants;",
@"
CREATE POLICY tenant_access ON ptmain.tenants
USING (id IN (
SELECT tenant_id
FROM ptmain.user_tenants
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER
));",
"DROP POLICY IF EXISTS user_tenant_access ON ptmain.user_tenants;",
@"
CREATE POLICY user_tenant_access ON ptmain.user_tenants
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);"
);
}
/// <summary>
/// Executes one or more SQL commands within a transaction.
/// </summary>
/// <param name="sqlCommands">The SQL commands to execute.</param>
private void ExecuteInTransaction(params string[] sqlCommands)
{
if (_db.State != ConnectionState.Open)
_db.Open();
using var transaction = _db.BeginTransaction();
try
{
foreach (var sql in sqlCommands)
{
_db.ExecuteSql(sql, transaction: transaction);
}
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw new InvalidOperationException("Failed to execute SQL commands in transaction.", ex);
}
}
}
}

View file

@ -0,0 +1,101 @@
using System.Data;
using System.Text.RegularExpressions;
using Insight.Database;
namespace Database.Tenants
{
public class Setup
{
private readonly IDbConnection _db;
public Setup(IDbConnection db)
{
_db = db ?? throw new ArgumentNullException(nameof(db));
}
/// <summary>
/// Creates the system tables in the specified schema within a transaction.
/// </summary>
/// <param name="schema">The schema name where the tables will be created.</param>
public async Task CreateSystem(string schema)
{
if (!IsValidSchemaName(schema))
throw new ArgumentException("Invalid schema name", nameof(schema));
using (var transaction = _db.BeginTransaction())
{
try
{
await CreateRolesTable(schema, transaction).ConfigureAwait(false);
await CreatePermissionsTable(schema, transaction).ConfigureAwait(false);
await CreateRolePermissionsTable(schema, transaction).ConfigureAwait(false);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw new InvalidOperationException("Failed to create system tables.", ex);
}
}
}
private bool IsValidSchemaName(string schema)
{
return !string.IsNullOrEmpty(schema) && Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$");
}
private async Task ExecuteSqlAsync(string sql, IDbTransaction transaction)
{
if (string.IsNullOrEmpty(sql))
throw new ArgumentNullException(nameof(sql));
await _db.ExecuteAsync(sql, transaction: transaction).ConfigureAwait(false);
}
private async Task CreatePermissionTypesTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.permission_types (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
}
private async Task CreatePermissionsTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.permissions (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
type_id INTEGER NOT NULL,
FOREIGN KEY (type_id) REFERENCES {schema}.permission_types(id)
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
}
private async Task CreateRolesTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.roles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
}
private async Task CreateRolePermissionsTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.role_permissions (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES {schema}.roles(id),
FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
}
}
}

View file

@ -3,11 +3,11 @@ using System.Data;
namespace Database.Tenants namespace Database.Tenants
{ {
internal class TenantData internal class InitializeTenantData
{ {
private readonly IDbConnection _db; private readonly IDbConnection _db;
public TenantData(IDbConnection db) public InitializeTenantData(IDbConnection db)
{ {
_db = db; _db = db;
} }

View file

@ -1 +1 @@
{"resources":{"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}} {"resources":{"Scripts/Script-2.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}}

View file

@ -1 +1 @@
{"resources":{"Scripts/Script-2.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}} {"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-19484872d85-cd2a4a40116e706","default-catalog":"ptdb01","default-schema":"ptmain"},"Scripts/Script-2.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}}

View file

View file

@ -8,6 +8,7 @@ CREATE SCHEMA ptmain;
GRANT USAGE, CREATE ON SCHEMA ptmain TO sathumper; GRANT USAGE, CREATE ON SCHEMA ptmain TO sathumper;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA ptmain TO sathumper; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA ptmain TO sathumper;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ptmain TO sathumper; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ptmain TO sathumper;
ALTER DEFAULT PRIVILEGES IN SCHEMA ptmain ALTER DEFAULT PRIVILEGES IN SCHEMA ptmain
GRANT ALL PRIVILEGES ON TABLES TO sathumper; GRANT ALL PRIVILEGES ON TABLES TO sathumper;

View file

@ -1,96 +0,0 @@

GRANT USAGE, CREATE ON SCHEMA swp TO sathumper;
ALTER DEFAULT PRIVILEGES IN SCHEMA swp
GRANT ALL PRIVILEGES ON TABLES TO sathumper;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA swp TO sathumper;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA swp TO sathumper;
select * from dev.app_configuration
CREATE OR REPLACE FUNCTION dev.notify_config_change()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM pg_notify(
'config_changes',
json_build_object('operation', TG_OP, 'data', row_to_json(OLD))::text
);
RETURN OLD; -- Return OLD for DELETE operations
ELSE
PERFORM pg_notify(
'config_changes',
json_build_object('operation', TG_OP, 'data', row_to_json(NEW))::text
);
RETURN NEW; -- Return NEW for INSERT/UPDATE operations
END IF;
END;
$$ LANGUAGE plpgsql;
-- Trigger på configuration tabellen
CREATE TRIGGER config_change_trigger
AFTER INSERT OR UPDATE OR DELETE ON dev.app_configuration
FOR EACH ROW EXECUTE FUNCTION dev.notify_config_change();
update dev.app_configuration
set "label" = "label" where id = 3
SELECT row_to_json(t)
FROM (SELECT 1 as id, 'test' as key) t;
SET myapp.tenant_id = '1';
SHOW myapp.tenant_id;
create TABLE dev.app_configuration1 (
tenant_id varchar(25),
config_key VARCHAR(255),
config_value TEXT,
PRIMARY KEY (tenant_id, config_key)
);
ALTER TABLE dev.app_configuration1 ENABLE ROW LEVEL SECURITY;
CREATE drop POLICY tenant_policy
ON dev.app_configuration1
FOR SELECT
USING (
current_setting('myapp.tenant_id', true) IS NOT NULL AND
tenant_id = current_setting('myapp.tenant_id')::INT
);
CREATE POLICY tenant_policy
ON dev.app_configuration1
FOR SELECT
USING (
tenant_id::text = current_user
);
insert into dev.app_configuration1
(tenant_id, config_key, config_value) values('dev', 't2', 'best dat')
SET myapp.tenant_id = 0
ALTER USER din_bruger NOBYPASS RLS;
select * from dev.app_configuration1
SHOW row_security;
GRANT USAGE, CREATE ON SCHEMA dev TO sathumper;
ALTER DEFAULT PRIVILEGES IN SCHEMA dev
GRANT ALL PRIVILEGES ON TABLES TO sathumper;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA dev TO sathumper;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA dev TO sathumper;