Setup db automation

This commit is contained in:
Janus Knudsen 2025-02-06 16:58:13 +01:00
parent 1540f9f655
commit 72544d62e2
12 changed files with 473 additions and 337 deletions

View file

@ -8,6 +8,7 @@ namespace Core.ModuleRegistry
public required string ConnectionString { get; set; }
protected override void Load(ContainerBuilder builder)
{
Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider();
builder.Register(c =>
{

View file

@ -0,0 +1,148 @@
using Insight.Database;
using System.Data;
namespace Database.IdentitySystem
{
public interface IDbSetup
{
void CreateSystem(string schema = null);
}
/// <summary>
/// This is by purpose not async await
/// </summary>
public class DbSetup : IDbSetup
{
readonly IDbConnection _db;
IDbTransaction _transaction = null;
string _schema;
public DbSetup(IDbConnection db)
{
_db = 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 void CreateSystem(string schema = null)
{
using (_transaction = _db.BeginTransaction())
{
try
{
CreateUsersTable();
CreateTenantsTable();
CreateUserTenantsTable();
SetupRLS();
_transaction.Commit();
}
catch (Exception ex)
{
_transaction.Rollback();
throw new InvalidOperationException("Failed to create system tables.", ex);
}
}
}
private void ExecuteSql(string sql)
{
if (string.IsNullOrEmpty(sql))
throw new ArgumentNullException(nameof(sql));
_db.ExecuteSql(sql);
}
/// <summary>
/// Creates the users table
/// </summary>
public void CreateUsersTable()
{
var sql = @"
CREATE TABLE IF NOT EXISTS 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
);";
ExecuteSql(sql);
}
/// <summary>
/// Creates the tenants table
/// </summary>
public void CreateTenantsTable()
{
var sql = @"
CREATE TABLE IF NOT EXISTS tenants (
id SERIAL PRIMARY KEY,
connection_string VARCHAR(500) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_by INTEGER NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);";
ExecuteSql(sql);
}
/// <summary>
/// Creates the user_tenants table
/// </summary>
public void CreateUserTenantsTable()
{
var sql = @"
CREATE TABLE IF NOT EXISTS user_tenants (
user_id INTEGER NOT NULL REFERENCES users(id),
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
pin_code VARCHAR(10) NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, tenant_id)
);";
ExecuteSql(sql);
}
/// <summary>
/// Sets up Row Level Security (RLS) for the tenants and user_tenants tables.
/// </summary>
public void SetupRLS()
{
var sql = new[]
{
"ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;",
"ALTER TABLE user_tenants ENABLE ROW LEVEL SECURITY;",
"DROP POLICY IF EXISTS tenant_access ON tenants;",
@"CREATE POLICY tenant_access ON tenants
USING (id IN (
SELECT tenant_id
FROM user_tenants
WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER
));",
"DROP POLICY IF EXISTS user_tenant_access ON user_tenants;",
@"CREATE POLICY user_tenant_access ON user_tenants
USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);"
};
foreach (var statement in sql)
{
ExecuteSql(statement);
}
}
}
}

View file

@ -0,0 +1,62 @@
using System;
using System.Data;
using System.Text.RegularExpressions;
using Insight.Database;
namespace Database.Core
{
public class SetupUser
{
private readonly IDbConnection _db;
public SetupUser(IDbConnection db)
{
_db = db;
}
public async Task CreateTenantInDatabase(string schema, string user, string password)
{
if (!Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"))
throw new ArgumentException("Invalid schema name");
await CreateUser(user, password);
await CreateSchema(schema);
await GrantSchemaRights(schema, user);
await CreateNavigationLinkTemplatesTable(schema);
await CreateNavigationLinkTemplateTranslationsTable(schema);
}
private async Task CreateSchema(string schema)
{
var sql = $"CREATE SCHEMA IF NOT EXISTS {schema}";
await _db.ExecuteAsync(sql);
}
private async Task CreateUser(string user, string password)
{
var sql = $"CREATE USER {user} WITH PASSWORD '{password}';";
await _db.ExecuteAsync(sql);
}
private async Task GrantSchemaRights(string schema, string user)
{
var sql = $"GRANT USAGE ON SCHEMA {schema} TO {user};";
await _db.ExecuteAsync(sql);
var sql1 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} " +
$"GRANT ALL PRIVILEGES ON TABLES TO {user};";
await _db.ExecuteAsync(sql1);
var sql2 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {schema} TO {user};";
await _db.ExecuteAsync(sql2);
}
}
}

View file

@ -1,147 +0,0 @@
using Database.Common;
using Insight.Database;
using System;
using System.Data;
using System.Threading.Tasks;
namespace Database.IdentitySystem
{
public class DbSetup
{
readonly IDbConnection _db;
IDbTransaction _transaction = null;
string _schema;
public DbSetup(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)
{
_schema = schema;
if (!Validations.IsValidSchemaName(schema))
throw new ArgumentException("Invalid schema name", nameof(schema));
using (_transaction = _db.BeginTransaction())
{
try
{
await CreateUsersTable().ConfigureAwait(false);
await CreateTenantsTable().ConfigureAwait(false);
await CreateUserTenantsTable().ConfigureAwait(false);
await SetupRLS().ConfigureAwait(false);
_transaction.Commit();
}
catch (Exception ex)
{
_transaction.Rollback();
throw new InvalidOperationException("Failed to create system tables.", ex);
}
}
}
private async Task ExecuteSqlAsync(string sql)
{
if (string.IsNullOrEmpty(sql))
throw new ArgumentNullException(nameof(sql));
await _db.ExecuteAsync(sql).ConfigureAwait(false);
}
/// <summary>
/// Creates the users table in the ptmain schema.
/// </summary>
public async Task CreateUsersTable()
{
var sql = @"
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
);";
await ExecuteSqlAsync(sql).ConfigureAwait(false);
}
/// <summary>
/// Creates the tenants table in the ptmain schema.
/// </summary>
public async Task CreateTenantsTable()
{
var sql = @"
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
);";
await ExecuteSqlAsync(sql).ConfigureAwait(false);
}
/// <summary>
/// Creates the user_tenants table in the ptmain schema.
/// </summary>
public async Task CreateUserTenantsTable()
{
var sql = @"
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)
);";
await ExecuteSqlAsync(sql).ConfigureAwait(false);
}
/// <summary>
/// Sets up Row Level Security (RLS) for the tenants and user_tenants tables.
/// </summary>
public async Task SetupRLS()
{
var sql = new[]
{
"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);"
};
foreach (var statement in sql)
{
await ExecuteSqlAsync(statement).ConfigureAwait(false);
}
}
}
}

View file

@ -0,0 +1,49 @@
using Insight.Database;
using System.Data;
namespace Database.RolesPermissionSystem
{
internal class NavigationSystem
{
private readonly IDbConnection _db;
public NavigationSystem(IDbConnection db)
{
_db = db;
}
private async Task CreateNavigationLinkTemplatesTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.navigation_link_templates (
id SERIAL PRIMARY KEY,
parent_id INTEGER NULL,
url VARCHAR(500) NOT NULL,
permission_id INTEGER NULL,
icon VARCHAR(100) NULL,
default_order INTEGER NOT NULL,
FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id),
FOREIGN KEY (parent_id) REFERENCES {schema}.navigation_link_templates(id)
)";
await _db.ExecuteAsync(sql);
}
private async Task CreateNavigationLinkTemplateTranslationsTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.navigation_link_template_translations (
id SERIAL PRIMARY KEY,
template_id INTEGER NOT NULL,
language VARCHAR(10) NOT NULL,
display_name VARCHAR(100) NOT NULL,
FOREIGN KEY (template_id) REFERENCES {schema}.navigation_link_templates(id)
)";
await _db.ExecuteAsync(sql);
}
}
}

View file

@ -18,7 +18,7 @@ namespace Database.RolesPermissionSystem
/// 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)
public void CreateSystem(string schema)
{
if (!Validations.IsValidSchemaName(schema))
throw new ArgumentException("Invalid schema name", nameof(schema));
@ -27,10 +27,10 @@ namespace Database.RolesPermissionSystem
{
try
{
await CreateRolesTable(schema, transaction).ConfigureAwait(false);
await CreatePermissionsTable(schema, transaction).ConfigureAwait(false);
await CreatePermissionTypesTable(schema, transaction).ConfigureAwait(false);
await CreateRolePermissionsTable(schema, transaction).ConfigureAwait(false);
CreateRolesTable(schema, transaction);
CreatePermissionsTable(schema, transaction);
CreatePermissionTypesTable(schema, transaction);
CreateRolePermissionsTable(schema, transaction);
transaction.Commit();
}
@ -43,25 +43,25 @@ namespace Database.RolesPermissionSystem
}
private async Task ExecuteSqlAsync(string sql, IDbTransaction transaction)
private void ExecuteSql(string sql, IDbTransaction transaction)
{
if (string.IsNullOrEmpty(sql))
throw new ArgumentNullException(nameof(sql));
await _db.ExecuteAsync(sql, transaction: transaction).ConfigureAwait(false);
_db.Execute(sql, transaction: transaction);
}
private async Task CreatePermissionTypesTable(string schema, IDbTransaction transaction)
private void 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);
ExecuteSql(sql, transaction);
}
private async Task CreatePermissionsTable(string schema, IDbTransaction transaction)
private void CreatePermissionsTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.permissions (
@ -70,20 +70,20 @@ namespace Database.RolesPermissionSystem
type_id INTEGER NOT NULL,
FOREIGN KEY (type_id) REFERENCES {schema}.permission_types(id)
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
ExecuteSql(sql, transaction);
}
private async Task CreateRolesTable(string schema, IDbTransaction transaction)
private void 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);
ExecuteSql(sql, transaction);
}
private async Task CreateRolePermissionsTable(string schema, IDbTransaction transaction)
private void CreateRolePermissionsTable(string schema, IDbTransaction transaction)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.role_permissions (
@ -93,7 +93,7 @@ namespace Database.RolesPermissionSystem
FOREIGN KEY (role_id) REFERENCES {schema}.roles(id),
FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
)";
await ExecuteSqlAsync(sql, transaction).ConfigureAwait(false);
ExecuteSql(sql, transaction);
}
}
}

View file

@ -5,11 +5,11 @@ using Insight.Database;
namespace Database.Tenants
{
public class TenantSetupService
public class Setup
{
private readonly IDbConnection _db;
public TenantSetupService(IDbConnection db)
public Setup(IDbConnection db)
{
_db = db;
}
@ -22,9 +22,8 @@ namespace Database.Tenants
await CreateUser(user, password);
await CreateSchema(schema);
await CreateRolesTable(schema);
await CreatePermissionsTable(schema);
await CreateRolePermissionsTable(schema);
await GrantSchemaRights(schema, user);
await CreateNavigationLinkTemplatesTable(schema);
await CreateNavigationLinkTemplateTranslationsTable(schema);
}
@ -49,57 +48,56 @@ namespace Database.Tenants
$"GRANT ALL PRIVILEGES ON TABLES TO {user};";
await _db.ExecuteAsync(sql1);
var sql2 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {schema} TO {user};";
await _db.ExecuteAsync(sql2);
}
private async Task CreatePermissionTypesTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.permission_types (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await _db.ExecuteAsync(sql);
}
//private async Task CreatePermissionTypesTable(string schema)
//{
// var sql = $@"
// CREATE TABLE IF NOT EXISTS {schema}.permission_types (
// id SERIAL PRIMARY KEY,
// name VARCHAR(100) NOT NULL UNIQUE
// )";
// await _db.ExecuteAsync(sql);
//}
private async Task CreatePermissionsTable(string schema)
{
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 _db.ExecuteAsync(sql);
}
//private async Task CreatePermissionsTable(string schema)
//{
// 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 _db.ExecuteAsync(sql);
//}
private async Task CreateRolesTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.roles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await _db.ExecuteAsync(sql);
}
//private async Task CreateRolesTable(string schema)
//{
// var sql = $@"
// CREATE TABLE IF NOT EXISTS {schema}.roles (
// id SERIAL PRIMARY KEY,
// name VARCHAR(100) NOT NULL UNIQUE
// )";
// await _db.ExecuteAsync(sql);
//}
private async Task CreateRolePermissionsTable(string schema)
{
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 _db.ExecuteAsync(sql);
}
//private async Task CreateRolePermissionsTable(string schema)
//{
// 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 _db.ExecuteAsync(sql);
//}
private async Task CreateNavigationLinkTemplatesTable(string schema)
{

View file

@ -1,7 +1,4 @@
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel;
using Autofac;
namespace SetupInfrastructure
{
@ -21,27 +18,16 @@ namespace SetupInfrastructure
{
static async Task Main(string[] args)
{
var container = new Startup().ConfigureContainer();
var telemetryChannel = new ServerTelemetryChannel();
var configuration = Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.CreateDefault();
configuration.ConnectionString = "InstrumentationKey=2d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/";
configuration.TelemetryChannel = telemetryChannel;
container.Resolve
telemetryChannel.Initialize(configuration);
var log = new TelemetryClient(configuration);
log.TrackTrace("Console log med kanal 2");
log.Flush();
Console.WriteLine("Hello, World!");
await Task.Delay(5000);
Console.Read();
}
}
}

View file

@ -0,0 +1,39 @@
using Autofac;
using Core.Configurations;
using Core.Configurations.JsonConfigProvider;
namespace SetupInfrastructure
{
public class Startup
{
public virtual IConfigurationRoot Configuration()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appconfiguration.dev.json")
.Build();
return configuration;
}
public IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
var configuration = Configuration();
builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule
{
ConnectionString = configuration.GetConnectionString("DefaultConnection")
});
builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule
{
TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject<Core.ModuleRegistry.TelemetryConfig>()
});
return builder.Build();
}
}
}

View file

@ -1,7 +1,7 @@
{
"AllowedHosts": "*",
"ConnectionStrings": {
"ptdb": "Host=192.168.1.57;Port=5432;Database=ptdb01;User Id=sathumper;Password=3911;"
"DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptdb01;User Id=sathumper;Password=3911;"
},
"ApplicationInsights": {
"ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/"

View file

@ -37,7 +37,7 @@ namespace Tests
{
CreateContainerBuilder();
Container = ContainerBuilder.Build();
Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider();
}