Working on this data setup logic
This commit is contained in:
parent
384cc3c6fd
commit
447b27f69b
16 changed files with 409 additions and 211 deletions
|
|
@ -11,11 +11,20 @@ public class ConfigurationDatabaseSetup
|
|||
{
|
||||
_connection = connection;
|
||||
}
|
||||
public async Task CreateDatabaseStructure(IDbConnection connection)
|
||||
{
|
||||
await CreateConfigurationTable();
|
||||
await CreateHistoryTable();
|
||||
await CreateConfigurationIndexes();
|
||||
await CreateModifiedAtTrigger();
|
||||
await CreateNotifyTrigger();
|
||||
await CreateHistoryTrigger();
|
||||
}
|
||||
|
||||
public async Task CreateConfigurationTable()
|
||||
{
|
||||
const string sql = @"
|
||||
CREATE TABLE prod.app_configuration (
|
||||
CREATE TABLE app_configuration (
|
||||
id bigserial NOT NULL,
|
||||
""key"" varchar(255) NOT NULL,
|
||||
value text NULL,
|
||||
|
|
@ -34,7 +43,7 @@ public class ConfigurationDatabaseSetup
|
|||
public async Task CreateHistoryTable()
|
||||
{
|
||||
const string sql = @"
|
||||
CREATE TABLE prod.app_configuration_history (
|
||||
CREATE TABLE app_configuration_history (
|
||||
history_id bigserial NOT NULL,
|
||||
action_type char(1) NOT NULL,
|
||||
action_timestamp timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
|
@ -57,15 +66,15 @@ public class ConfigurationDatabaseSetup
|
|||
public async Task CreateConfigurationIndexes()
|
||||
{
|
||||
const string sql = @"
|
||||
CREATE INDEX idx_app_configuration_key ON prod.app_configuration(""key"");
|
||||
CREATE INDEX idx_app_configuration_validity ON prod.app_configuration(valid_from, expires_at);";
|
||||
CREATE INDEX idx_app_configuration_key ON app_configuration(""key"");
|
||||
CREATE INDEX idx_app_configuration_validity ON app_configuration(valid_from, expires_at);";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public async Task CreateModifiedAtTrigger()
|
||||
{
|
||||
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 $$
|
||||
BEGIN
|
||||
NEW.modified_at = CURRENT_TIMESTAMP;
|
||||
|
|
@ -74,16 +83,16 @@ public class ConfigurationDatabaseSetup
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_app_configuration_modified_at
|
||||
BEFORE UPDATE ON prod.app_configuration
|
||||
BEFORE UPDATE ON app_configuration
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION prod.update_app_configuration_modified_at();";
|
||||
EXECUTE FUNCTION update_app_configuration_modified_at();";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public async Task CreateNotifyTrigger()
|
||||
{
|
||||
const string sql = @"
|
||||
CREATE OR REPLACE FUNCTION prod.notify_app_configuration_change()
|
||||
CREATE OR REPLACE FUNCTION notify_app_configuration_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('config_changes', NEW.key);
|
||||
|
|
@ -92,20 +101,20 @@ public class ConfigurationDatabaseSetup
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_app_configuration_notify
|
||||
AFTER INSERT OR UPDATE ON prod.app_configuration
|
||||
AFTER INSERT OR UPDATE ON app_configuration
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION prod.notify_app_configuration_change();";
|
||||
EXECUTE FUNCTION notify_app_configuration_change();";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public async Task CreateHistoryTrigger()
|
||||
{
|
||||
const string sql = @"
|
||||
CREATE OR REPLACE FUNCTION prod.log_app_configuration_changes()
|
||||
CREATE OR REPLACE FUNCTION log_app_configuration_changes()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
INSERT INTO prod.app_configuration_history (
|
||||
INSERT INTO app_configuration_history (
|
||||
action_type, id, ""key"", value, label, content_type,
|
||||
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
|
||||
);
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
INSERT INTO prod.app_configuration_history (
|
||||
INSERT INTO app_configuration_history (
|
||||
action_type, id, ""key"", value, label, content_type,
|
||||
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
|
||||
);
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
INSERT INTO prod.app_configuration_history (
|
||||
INSERT INTO app_configuration_history (
|
||||
action_type, id, ""key"", value, label, content_type,
|
||||
valid_from, expires_at, created_at, modified_at, etag
|
||||
)
|
||||
|
|
@ -137,18 +146,10 @@ public class ConfigurationDatabaseSetup
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_app_configuration_history
|
||||
AFTER INSERT OR UPDATE OR DELETE ON prod.app_configuration
|
||||
FOR EACH ROW EXECUTE FUNCTION prod.log_app_configuration_changes();";
|
||||
AFTER INSERT OR UPDATE OR DELETE ON app_configuration
|
||||
FOR EACH ROW EXECUTE FUNCTION log_app_configuration_changes();";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
|
||||
public async Task CreateDatabaseStructure(IDbConnection connection)
|
||||
{
|
||||
await CreateConfigurationTable();
|
||||
await CreateHistoryTable();
|
||||
await CreateConfigurationIndexes();
|
||||
await CreateModifiedAtTrigger();
|
||||
await CreateNotifyTrigger();
|
||||
await CreateHistoryTrigger();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,4 +9,9 @@
|
|||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="AuditSystem\" />
|
||||
<Folder Include="NavigationSystem\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
115
Database/IdentitySystem/Setup.cs
Normal file
115
Database/IdentitySystem/Setup.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Database/RolesPermissionSystem/Setup.cs
Normal file
101
Database/RolesPermissionSystem/Setup.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ using System.Data;
|
|||
|
||||
namespace Database.Tenants
|
||||
{
|
||||
internal class TenantData
|
||||
internal class InitializeTenantData
|
||||
{
|
||||
private readonly IDbConnection _db;
|
||||
|
||||
public TenantData(IDbConnection db)
|
||||
public InitializeTenantData(IDbConnection db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue