From 78d49a9829407991f6b97db80797ae8ae98c4510 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 21 Feb 2025 23:34:06 +0100 Subject: [PATCH] Refactoring SetupConsole with DBFactory --- Core/Logging/SeqConfiguration.cs | 3 - Core/ModuleRegistry/SeqLoggingModule.cs | 6 +- Core/ModuleRegistry/TelemetryModule.cs | 23 +-- Core/Telemetry/SeqLoggingTelemetryChannel.cs | 45 ++--- .../SetupConfiguration.cs | 6 +- .../ConnectionFactory/IDbConnectionFactory.cs | 12 +- .../PostgresConnectionFactory.cs | 10 +- Database/Core/DCL/SetupApplicationUser.cs | 149 ++++++++-------- Database/Core/DCL/SetupDbAdmin.cs | 53 +++--- Database/Core/DCL/SetupOrganizationUser.cs | 67 ++++--- Database/Core/DDL/SetupIdentitySystem.cs | 168 +++++++++--------- Database/Core/IDbConfigure.cs | 2 +- Database/Core/Sql/SqlOperations.cs | 4 - Database/ModuleRegistry/DbPostgreSqlModule.cs | 38 +--- SetupInfrastructure/Program.cs | 81 +++++---- SetupInfrastructure/Startup.cs | 70 ++++---- SetupInfrastructure/appconfiguration.json | 2 +- .../.dbeaver/.project-metadata.json.bak | 2 +- SqlManagement/Scripts/Script.sql | 1 + .../JsonConfigurationProviderTests.cs | 2 +- 20 files changed, 337 insertions(+), 407 deletions(-) diff --git a/Core/Logging/SeqConfiguration.cs b/Core/Logging/SeqConfiguration.cs index 884eb56..00e1e9b 100644 --- a/Core/Logging/SeqConfiguration.cs +++ b/Core/Logging/SeqConfiguration.cs @@ -1,6 +1,3 @@ -using Microsoft.ApplicationInsights.DataContracts; -using System.Text; - namespace PlanTempus.Core.Logging { public record SeqConfiguration(string IngestionEndpoint, string ApiKey, string Environment); diff --git a/Core/ModuleRegistry/SeqLoggingModule.cs b/Core/ModuleRegistry/SeqLoggingModule.cs index 63bbdc7..5e52188 100644 --- a/Core/ModuleRegistry/SeqLoggingModule.cs +++ b/Core/ModuleRegistry/SeqLoggingModule.cs @@ -11,9 +11,9 @@ namespace PlanTempus.Core.ModuleRegistry protected override void Load(ContainerBuilder builder) { - builder.RegisterType() - .As>() - .SingleInstance(); + //builder.RegisterType() + // .As>() + // .SingleInstance(); builder.RegisterType() .As() diff --git a/Core/ModuleRegistry/TelemetryModule.cs b/Core/ModuleRegistry/TelemetryModule.cs index a4d80f6..059182a 100644 --- a/Core/ModuleRegistry/TelemetryModule.cs +++ b/Core/ModuleRegistry/TelemetryModule.cs @@ -6,29 +6,30 @@ namespace PlanTempus.Core.ModuleRegistry { public class TelemetryModule : Module { - public TelemetryConfig TelemetryConfig { get; set; } + public required TelemetryConfig TelemetryConfig { get; set; } protected override void Load(ContainerBuilder builder) { - if (TelemetryConfig == null) - throw new Exceptions.ConfigurationException("TelemetryConfig is missing"); - var configuration = TelemetryConfiguration.CreateDefault(); configuration.ConnectionString = TelemetryConfig.ConnectionString; configuration.TelemetryChannel.DeveloperMode = true; if (TelemetryConfig.UseSeqLoggingTelemetryChannel) - configuration.TelemetryChannel = new Telemetry.SeqLoggingTelemetryChannel(); ; + { + var messageChannel = new Telemetry.MessageChannel(); - var r = new Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryProcessorChainBuilder(configuration); - r.Use(next => new Telemetry.Enrichers.EnrichWithMetaTelemetry(next)); - r.Build(); + builder.RegisterInstance(messageChannel) + .As>() + .SingleInstance(); - //builder.RegisterInstance(configuration); + configuration.TelemetryChannel = new Telemetry.SeqLoggingTelemetryChannel(messageChannel); + } + + var telemetryProcessorChain = new Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryProcessorChainBuilder(configuration); + telemetryProcessorChain.Use(next => new Telemetry.Enrichers.EnrichWithMetaTelemetry(next)); + telemetryProcessorChain.Build(); - //builder.RegisterType() - // .InstancePerLifetimeScope(); var client = new Microsoft.ApplicationInsights.TelemetryClient(configuration); client.Context.GlobalProperties["Application"] = GetType().Namespace.Split('.')[0]; diff --git a/Core/Telemetry/SeqLoggingTelemetryChannel.cs b/Core/Telemetry/SeqLoggingTelemetryChannel.cs index 9f7517a..5a262a6 100644 --- a/Core/Telemetry/SeqLoggingTelemetryChannel.cs +++ b/Core/Telemetry/SeqLoggingTelemetryChannel.cs @@ -1,50 +1,27 @@ using Microsoft.ApplicationInsights.Channel; -using System.Net.Http.Headers; namespace PlanTempus.Core.Telemetry { public class SeqLoggingTelemetryChannel : InMemoryChannel, ITelemetryChannel { - public ITelemetryChannel _defaultChannel; - static HttpClient _client = new HttpClient(); + private readonly IMessageChannel _messageChannel; - static SeqLoggingTelemetryChannel() - { - _client = new HttpClient() - { - BaseAddress = new Uri("http://localhost:5341"), - Timeout = TimeSpan.FromSeconds(30) - }; - - _client.DefaultRequestHeaders.Accept.Clear(); - _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } - - - public SeqLoggingTelemetryChannel() + public SeqLoggingTelemetryChannel(IMessageChannel messageChannel) { + _messageChannel = messageChannel; } public new void Send(ITelemetry telemetry) { - //var l = new SeqLogger(_client, "", ""); - - //l.LogToSeq( - // "Bruger {UserId} loggede ind", - // "Debug", - // new Dictionary { { "UserId", "12345" }, { "Counter", "i++" } } - // ); - - - //if (telemetry is Microsoft.ApplicationInsights.DataContracts.TraceTelemetry trace) - //{ - // var severity = trace.SeverityLevel; - // Console.WriteLine($"Trace severity: {severity}, Message: {trace.Message}"); - //} - + var writeTask = _messageChannel.Writer.WriteAsync(telemetry).AsTask(); + writeTask.ContinueWith(t => + { + if (t.Exception != null) + { + throw t.Exception; + } + }, TaskContinuationOptions.OnlyOnFaulted); base.Send(telemetry); - var logEntry = $"{DateTime.UtcNow:u}|{telemetry.Context.Operation.Name}|{telemetry.Context.Operation.Id}"; - //File.AppendAllText(_filePath, logEntry + Environment.NewLine); } } } diff --git a/Database/ConfigurationManagementSystem/SetupConfiguration.cs b/Database/ConfigurationManagementSystem/SetupConfiguration.cs index 45ca0bf..c1b6d4b 100644 --- a/Database/ConfigurationManagementSystem/SetupConfiguration.cs +++ b/Database/ConfigurationManagementSystem/SetupConfiguration.cs @@ -15,10 +15,10 @@ public class SetupConfiguration : IDbConfigure { _connectionFactory = connectionFactory; } - public void With(Command notInUse) + public void With(Command notInUse, ConnectionStringParameters parameters = null) { - using var conn = _connectionFactory.Create(); - using var transaction = conn.OpenWithTransaction(); + using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters); + using var transaction = conn.OpenWithTransaction(); try { CreateConfigurationTable(conn); diff --git a/Database/Core/ConnectionFactory/IDbConnectionFactory.cs b/Database/Core/ConnectionFactory/IDbConnectionFactory.cs index f2ace54..e7ec864 100644 --- a/Database/Core/ConnectionFactory/IDbConnectionFactory.cs +++ b/Database/Core/ConnectionFactory/IDbConnectionFactory.cs @@ -1,8 +1,10 @@ namespace PlanTempus.Database.Core.ConnectionFactory { - public interface IDbConnectionFactory - { - System.Data.IDbConnection Create(); - System.Data.IDbConnection Create(string username, string password); - } + public record ConnectionStringParameters(string user, string pwd); + + public interface IDbConnectionFactory + { + System.Data.IDbConnection Create(); + System.Data.IDbConnection Create(ConnectionStringParameters connectionStringTemplateParameters); + } } diff --git a/Database/Core/ConnectionFactory/PostgresConnectionFactory.cs b/Database/Core/ConnectionFactory/PostgresConnectionFactory.cs index 3cf8d58..b42c899 100644 --- a/Database/Core/ConnectionFactory/PostgresConnectionFactory.cs +++ b/Database/Core/ConnectionFactory/PostgresConnectionFactory.cs @@ -2,9 +2,9 @@ using System.Data; namespace PlanTempus.Database.Core.ConnectionFactory { - - public class PostgresConnectionFactory : IDbConnectionFactory, IAsyncDisposable + + public class PostgresConnectionFactory : IDbConnectionFactory, IAsyncDisposable { private readonly NpgsqlDataSource _baseDataSource; private readonly Action _configureDataSource; @@ -29,13 +29,13 @@ namespace PlanTempus.Database.Core.ConnectionFactory return _baseDataSource.CreateConnection(); } - public IDbConnection Create(string username, string password) + public IDbConnection Create(ConnectionStringParameters param) { var connectionStringBuilder = new NpgsqlConnectionStringBuilder( _baseDataSource.ConnectionString) { - Username = username, - Password = password + Username = param.user, + Password = param.pwd }; var tempDataSourceBuilder = new NpgsqlDataSourceBuilder( diff --git a/Database/Core/DCL/SetupApplicationUser.cs b/Database/Core/DCL/SetupApplicationUser.cs index b750733..7a84c37 100644 --- a/Database/Core/DCL/SetupApplicationUser.cs +++ b/Database/Core/DCL/SetupApplicationUser.cs @@ -5,105 +5,102 @@ using PlanTempus.Database.Core.ConnectionFactory; namespace PlanTempus.Database.Core.DCL { + /// + /// Only a superadmin or similar can create Application Users + /// + public class SetupApplicationUser : IDbConfigure + { + public class Command + { + public required string Schema { get; init; } + public required string User { get; init; } + public required string Password { get; init; } + } - /// - /// Only a superadmin or similar can create Application Users - /// - public class SetupApplicationUser : IDbConfigure - { - public class Command - { - public required string Schema { get; init; } - public required string User { get; init; } - public required string Password { get; init; } - } + Command _command; + private readonly IDbConnectionFactory _connectionFactory; + public SetupApplicationUser(IDbConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } - Command _command; - private readonly IDbConnectionFactory _connectionFactory; + public void With(Command command, ConnectionStringParameters parameters = null) + { + _command = command; - public SetupApplicationUser(IDbConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory; - } + if (!Validations.IsValidSchemaName(_command.Schema)) + throw new ArgumentException("Invalid schema name", _command.Schema); - public void With(Command command) - { - _command = command; + using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters); + using var transaction = conn.OpenWithTransaction(); + try + { + CreateSchema(conn); + CreateRole(conn); + GrantSchemaRights(conn); - if (!Validations.IsValidSchemaName(_command.Schema)) - throw new ArgumentException("Invalid schema name", _command.Schema); + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new InvalidOperationException("Failed to SetupApplicationUser in Database", ex); + } + } - using var conn = _connectionFactory.Create(); - using var transaction = conn.OpenWithTransaction(); - try - { - CreateSchema(conn); - CreateRole(conn); - GrantSchemaRights(conn); + private void CreateSchema(IDbConnection db) + { + var sql = $"CREATE SCHEMA IF NOT EXISTS {_command.Schema}"; + db.ExecuteSql(sql); + } - transaction.Commit(); - } - catch (Exception ex) - { - transaction.Rollback(); - throw new InvalidOperationException("Failed to SetupApplicationUser in Database", ex); - } - - } - - private void CreateSchema(IDbConnection db) - { - var sql = $"CREATE SCHEMA IF NOT EXISTS {_command.Schema}"; - db.ExecuteSql(sql); - } - - private void CreateRole(IDbConnection db) - { - var sql = $@" + private void CreateRole(IDbConnection db) + { + var sql = $@" DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '{_command.User}') THEN CREATE ROLE {_command.User} WITH CREATEDB CREATEROLE LOGIN PASSWORD '{_command.Password}'; END IF; END $$;"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); - var sql1 = $"ALTER ROLE {_command.User} SET search_path='{_command.Schema}';"; - db.ExecuteSql(sql1); - } + var sql1 = $"ALTER ROLE {_command.User} SET search_path='{_command.Schema}';"; + db.ExecuteSql(sql1); + } - private void GrantSchemaRights(IDbConnection db) - { - // Grant USAGE og alle CREATE rettigheder på schema niveau - var sql = $@" + private void GrantSchemaRights(IDbConnection db) + { + // Grant USAGE og alle CREATE rettigheder på schema niveau + var sql = $@" GRANT USAGE ON SCHEMA {_command.Schema} TO {_command.User}; GRANT ALL ON SCHEMA {_command.Schema} TO {_command.User};"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); - // Grant rettigheder på eksisterende og fremtidige tabeller - var sql1 = $"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA {_command.Schema} TO {_command.User};"; - db.ExecuteSql(sql1); + // Grant rettigheder på eksisterende og fremtidige tabeller + var sql1 = $"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA {_command.Schema} TO {_command.User};"; + db.ExecuteSql(sql1); - var sql2 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT ALL PRIVILEGES ON TABLES TO {_command.User};"; - db.ExecuteSql(sql2); + var sql2 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT ALL PRIVILEGES ON TABLES TO {_command.User};"; + db.ExecuteSql(sql2); - // Grant sequence rettigheder - var sql3 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {_command.Schema} TO {_command.User};"; - db.ExecuteSql(sql3); + // Grant sequence rettigheder + var sql3 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {_command.Schema} TO {_command.User};"; + db.ExecuteSql(sql3); - // Grant execute på functions - var sql4 = $"GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA {_command.Schema} TO {_command.User};"; - db.ExecuteSql(sql4); + // Grant execute på functions + var sql4 = $"GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA {_command.Schema} TO {_command.User};"; + db.ExecuteSql(sql4); - // Grant for fremtidige functions - var sql5 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT EXECUTE ON FUNCTIONS TO {_command.User};"; - db.ExecuteSql(sql5); + // Grant for fremtidige functions + var sql5 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT EXECUTE ON FUNCTIONS TO {_command.User};"; + db.ExecuteSql(sql5); - // Grant for fremtidige sequences - var sql6 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT USAGE ON SEQUENCES TO {_command.User};"; - db.ExecuteSql(sql6); - } + // Grant for fremtidige sequences + var sql6 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} GRANT USAGE ON SEQUENCES TO {_command.User};"; + db.ExecuteSql(sql6); + } - } + } } diff --git a/Database/Core/DCL/SetupDbAdmin.cs b/Database/Core/DCL/SetupDbAdmin.cs index 37af87e..989595b 100644 --- a/Database/Core/DCL/SetupDbAdmin.cs +++ b/Database/Core/DCL/SetupDbAdmin.cs @@ -21,39 +21,39 @@ namespace PlanTempus.Database.Core.DCL Command _command; - private readonly IDbConnectionFactory _connectionFactory; + private readonly IDbConnectionFactory _connectionFactory; - public SetupDbAdmin(IDbConnectionFactory connectionFactory) + public SetupDbAdmin(IDbConnectionFactory connectionFactory) { - _connectionFactory = connectionFactory; - } + _connectionFactory = connectionFactory; + } - public void With(Command command) + public void With(Command command, ConnectionStringParameters parameters = null) { _command = command; if (!Validations.IsValidSchemaName(_command.Schema)) throw new ArgumentException("Invalid schema name", _command.Schema); - using var conn = _connectionFactory.Create(); - using var transaction = conn.OpenWithTransaction(); - try - { - CreateSchema(conn); - CreateRole(conn); - GrantSchemaRights(conn); + using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters); + using var transaction = conn.OpenWithTransaction(); + try + { + CreateSchema(conn); + CreateRole(conn); + GrantSchemaRights(conn); - transaction.Commit(); - } - catch (Exception ex) - { - transaction.Rollback(); - throw new InvalidOperationException("Failed to SetupApplicationUser in Database", ex); - } + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new InvalidOperationException("Failed to SetupApplicationUser in Database", ex); + } + + } - } - private void CreateSchema(IDbConnection db) { var sql = $"CREATE SCHEMA IF NOT EXISTS {_command.Schema}"; @@ -69,13 +69,13 @@ namespace PlanTempus.Database.Core.DCL CREATE ROLE {_command.User} WITH CREATEDB CREATEROLE LOGIN PASSWORD '{_command.Password}'; END IF; END $$;"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); var sql1 = $"ALTER ROLE {_command.User} SET search_path='{_command.Schema}';"; - db.ExecuteSql(sql1); + db.ExecuteSql(sql1); var sql2 = $"ALTER SCHEMA {_command.Schema} OWNER TO {_command.User};"; - db.ExecuteSql(sql2); + db.ExecuteSql(sql2); } @@ -83,10 +83,9 @@ namespace PlanTempus.Database.Core.DCL { // Grant USAGE og alle CREATE rettigheder på schema niveau //GRANT USAGE ON SCHEMA {_command.Schema} TO {_command.User}; - var sql = $@" - GRANT CREATE ON SCHEMA {_command.Schema} TO {_command.User};"; + var sql = $@"GRANT CREATE ON SCHEMA {_command.Schema} TO {_command.User};"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); // Grant rettigheder på eksisterende og fremtidige tabeller //var sql1 = $"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA {_command.Schema} TO {_command.User};"; diff --git a/Database/Core/DCL/SetupOrganizationUser.cs b/Database/Core/DCL/SetupOrganizationUser.cs index 5b878bb..6941989 100644 --- a/Database/Core/DCL/SetupOrganizationUser.cs +++ b/Database/Core/DCL/SetupOrganizationUser.cs @@ -2,6 +2,7 @@ using Insight.Database; using PlanTempus.Database.Common; using PlanTempus.Database.Core; +using PlanTempus.Database.Core.ConnectionFactory; namespace PlanTempus.Database.Core.DCL { @@ -14,81 +15,75 @@ namespace PlanTempus.Database.Core.DCL public required string Password { get; init; } } - IDbConnection _db; Command _command; + private readonly IDbConnectionFactory _connectionFactory; - public SetupOrganization(IDbConnection db) + + public SetupOrganization(IDbConnectionFactory connectionFactory) { - _db = db; + _connectionFactory = connectionFactory; } - public void With(Command command) + public void With(Command command, ConnectionStringParameters parameters = null) { - _command = command; - if (!Validations.IsValidSchemaName(_command.Schema)) throw new ArgumentException("Invalid schema name", _command.Schema); - using (var transaction = _db.BeginTransaction()) + using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters); + using var transaction = conn.OpenWithTransaction(); + try { - try - { - CreateSchema(); - CreateRole(); - GrantSchemaRights(); + CreateSchema(conn); + CreateRole(conn); + GrantSchemaRights(conn); - transaction.Commit(); - } - catch (Exception ex) - { - transaction.Rollback(); - throw new InvalidOperationException("Failed to SetupOrganization in Database", ex); - } + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new InvalidOperationException("Failed to SetupOrganization in Database", ex); } } - private void ExecuteSql(string sql) - { - _db.ExecuteSql(sql); - } - - private void CreateSchema() + + private void CreateSchema(IDbConnection db) { var sql = $"CREATE SCHEMA IF NOT EXISTS {_command.Schema}"; - ExecuteSql(sql); + db.ExecuteSql(sql); } - private void CreateRole() + private void CreateRole(IDbConnection db) { var sql = $"CREATE ROLE {_command.User} LOGIN PASSWORD '{_command.Password}';"; - ExecuteSql(sql); + db.ExecuteSql(sql); var sql1 = $"ALTER ROLE {_command.User} SET search_path='{_command.Schema}';"; - ExecuteSql(sql1); + db.ExecuteSql(sql1); } - private void GrantSchemaRights() + private void GrantSchemaRights(IDbConnection db) { var sql = $"GRANT USAGE ON SCHEMA {_command.Schema} TO {_command.User};"; - ExecuteSql(sql); + db.ExecuteSql(sql); var sql1 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {_command.Schema} " + $"GRANT INSERT, SELECT, UPDATE PRIVILEGES ON TABLES TO {_command.User};"; - ExecuteSql(sql1); + db.ExecuteSql(sql1); var sql2 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {_command.Schema} TO {_command.User};"; - ExecuteSql(sql2); + db.ExecuteSql(sql2); var sql3 = $"GRANT CREATE TABLE ON SCHEMA {_command.Schema} TO {_command.User};"; - ExecuteSql(sql3); + db.ExecuteSql(sql3); } - public void RevokeCreateTable() + public void RevokeCreateTable(IDbConnection db) { var sql = $"REVOKE CREATE TABLE ON SCHEMA {_command.Schema} FROM {_command.User};"; - ExecuteSql(sql); + db.ExecuteSql(sql); } } } diff --git a/Database/Core/DDL/SetupIdentitySystem.cs b/Database/Core/DDL/SetupIdentitySystem.cs index 3641e26..942257e 100644 --- a/Database/Core/DDL/SetupIdentitySystem.cs +++ b/Database/Core/DDL/SetupIdentitySystem.cs @@ -4,58 +4,58 @@ using System.Data; namespace PlanTempus.Database.Core.DDL { - /// - /// This is by purpose not async await - /// It is intended that this is created with the correct Application User, which is why the schema name is omitted. - /// - public class SetupIdentitySystem : IDbConfigure - { - public class Command - { - public required string Schema { get; init; } - } + /// + /// This is by purpose not async await + /// It is intended that this is created with the correct Application User, which is why the schema name is omitted. + /// + public class SetupIdentitySystem : IDbConfigure + { + public class Command + { + public required string Schema { get; init; } + } - Command _command; - private readonly IDbConnectionFactory _connectionFactory; + Command _command; + private readonly IDbConnectionFactory _connectionFactory; - public SetupIdentitySystem(IDbConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory; - } + public SetupIdentitySystem(IDbConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } - /// - /// Creates the system tables in the specified schema within a transaction. - /// - /// The schema name where the tables will be created. - public void With(Command command) - { - _command = command; + /// + /// Creates the system tables in the specified schema within a transaction. + /// + /// The schema name where the tables will be created. + public void With(Command command, ConnectionStringParameters parameters = null) + { + _command = command; - using var conn = _connectionFactory.Create(); - using var transaction = conn.OpenWithTransaction(); - try - { - CreateUsersTable(conn); - CreateOrganizationsTable(conn); - CreateUserOrganizationsTable(conn); - SetupRLS(conn); + using var conn = parameters is null ? _connectionFactory.Create() : _connectionFactory.Create(parameters); + using var transaction = conn.OpenWithTransaction(); + try + { + CreateUsersTable(conn); + CreateOrganizationsTable(conn); + CreateUserOrganizationsTable(conn); + SetupRLS(conn); - transaction.Commit(); - } - catch (Exception ex) - { - transaction.Rollback(); - throw new InvalidOperationException("Failed to SetupIdentitySystem. Transaction is rolled back", ex); - } - } - + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new InvalidOperationException("Failed to SetupIdentitySystem. Transaction is rolled back", ex); + } + } - /// - /// Creates the users table - /// - void CreateUsersTable(IDbConnection db) - { - var sql = @$" + + /// + /// Creates the users table + /// + void CreateUsersTable(IDbConnection db) + { + var sql = @$" CREATE TABLE IF NOT EXISTS {_command.Schema}.users ( id SERIAL PRIMARY KEY, email VARCHAR(256) NOT NULL UNIQUE, @@ -70,16 +70,16 @@ namespace PlanTempus.Database.Core.DDL last_login_at TIMESTAMPTZ NULL );"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); - } + } - /// - /// Creates the organizations table - /// - void CreateOrganizationsTable(IDbConnection db) - { - var sql = @$" + /// + /// Creates the organizations table + /// + void CreateOrganizationsTable(IDbConnection db) + { + var sql = @$" CREATE TABLE IF NOT EXISTS {_command.Schema}.organizations ( id SERIAL PRIMARY KEY, connection_string VARCHAR(500) NOT NULL, @@ -88,16 +88,16 @@ namespace PlanTempus.Database.Core.DDL created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP );"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); - } + } - /// - /// Creates the user_organizations table - /// - void CreateUserOrganizationsTable(IDbConnection db) - { - var sql = @$" + /// + /// Creates the user_organizations table + /// + void CreateUserOrganizationsTable(IDbConnection db) + { + var sql = @$" CREATE TABLE IF NOT EXISTS {_command.Schema}.user_organizations ( user_id INTEGER NOT NULL REFERENCES {_command.Schema}.users(id), organization_id INTEGER NOT NULL REFERENCES {_command.Schema}.organizations(id), @@ -106,37 +106,37 @@ namespace PlanTempus.Database.Core.DDL PRIMARY KEY (user_id, organization_id) );"; - db.ExecuteSql(sql); + db.ExecuteSql(sql); - } + } - /// - /// Sets up Row Level Security (RLS) for the organizations and user_organizations tables. - /// - void SetupRLS(IDbConnection db) - { - var sql = new[] - { - $"ALTER TABLE {_command.Schema}.organizations ENABLE ROW LEVEL SECURITY;", - $"ALTER TABLE {_command.Schema}.user_organizations ENABLE ROW LEVEL SECURITY;", - $"DROP POLICY IF EXISTS organization_access ON {_command.Schema}.organizations;", - @$"CREATE POLICY organization_access ON {_command.Schema}.organizations + /// + /// Sets up Row Level Security (RLS) for the organizations and user_organizations tables. + /// + void SetupRLS(IDbConnection db) + { + var sql = new[] + { + $"ALTER TABLE {_command.Schema}.organizations ENABLE ROW LEVEL SECURITY;", + $"ALTER TABLE {_command.Schema}.user_organizations ENABLE ROW LEVEL SECURITY;", + $"DROP POLICY IF EXISTS organization_access ON {_command.Schema}.organizations;", + @$"CREATE POLICY organization_access ON {_command.Schema}.organizations USING (id IN ( SELECT organization_id FROM {_command.Schema}.user_organizations WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER )) WITH CHECK (true);", - $"DROP POLICY IF EXISTS user_organization_access ON {_command.Schema}.user_organizations;", - @$"CREATE POLICY user_organization_access ON {_command.Schema}.user_organizations + $"DROP POLICY IF EXISTS user_organization_access ON {_command.Schema}.user_organizations;", + @$"CREATE POLICY user_organization_access ON {_command.Schema}.user_organizations USING (user_id = current_setting('app.user_id', TRUE)::INTEGER) WITH CHECK (true);" - }; + }; - foreach (var statement in sql) - { - db.ExecuteSql(statement); - } - } + foreach (var statement in sql) + { + db.ExecuteSql(statement); + } + } - } + } } \ No newline at end of file diff --git a/Database/Core/IDbConfigure.cs b/Database/Core/IDbConfigure.cs index 998d6cc..7eb5da4 100644 --- a/Database/Core/IDbConfigure.cs +++ b/Database/Core/IDbConfigure.cs @@ -2,6 +2,6 @@ { public interface IDbConfigure { - void With(T command); + void With(T command, ConnectionFactory.ConnectionStringParameters parameters = null); } } diff --git a/Database/Core/Sql/SqlOperations.cs b/Database/Core/Sql/SqlOperations.cs index e044fc4..ae96dc0 100644 --- a/Database/Core/Sql/SqlOperations.cs +++ b/Database/Core/Sql/SqlOperations.cs @@ -6,8 +6,6 @@ using System.Data; namespace PlanTempus.Database.Core.Sql { - - public class DatabaseScope : IDisposable { private readonly IDbConnection _connection; @@ -99,6 +97,4 @@ namespace PlanTempus.Database.Core.Sql } } - - } diff --git a/Database/ModuleRegistry/DbPostgreSqlModule.cs b/Database/ModuleRegistry/DbPostgreSqlModule.cs index fba43cb..07c756c 100644 --- a/Database/ModuleRegistry/DbPostgreSqlModule.cs +++ b/Database/ModuleRegistry/DbPostgreSqlModule.cs @@ -3,7 +3,7 @@ using Npgsql; using System.Data; namespace PlanTempus.Database.ModuleRegistry { - + public class DbPostgreSqlModule : Module { public required string ConnectionString { get; set; } @@ -12,42 +12,14 @@ namespace PlanTempus.Database.ModuleRegistry { Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider(); - builder.Register(c => - new Core.ConnectionFactory.PostgresConnectionFactory(ConnectionString)) - .SingleInstance(); + builder.RegisterType() + .As() + .WithParameter(new TypedParameter(typeof(string), ConnectionString)) + .SingleInstance(); builder.RegisterType() .As(); } } - public class PostgresConnectionFactory1 //: IDbConnectionFactory - { - private readonly string _baseConnectionString; - - public PostgresConnectionFactory1(string connectionString) - { - _baseConnectionString = connectionString; - } - - public IDbConnection Create() - { - return new NpgsqlConnection(_baseConnectionString); - } - - public IDbConnection Create(string username, string password) - { - var builder = new NpgsqlConnectionStringBuilder(_baseConnectionString) - { - Username = username, - Password = password - }; - - return new NpgsqlConnection(builder.ToString()); - } - } - - - - } diff --git a/SetupInfrastructure/Program.cs b/SetupInfrastructure/Program.cs index 2d18d7e..5e32b3b 100644 --- a/SetupInfrastructure/Program.cs +++ b/SetupInfrastructure/Program.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Npgsql; using PlanTempus.Database.ConfigurationManagementSystem; +using PlanTempus.Database.Core.ConnectionFactory; using PlanTempus.Database.Core.DCL; using PlanTempus.Database.Core.DDL; using System.Data; @@ -25,7 +26,7 @@ namespace PlanTempus.SetupInfrastructure /// internal class Program { - + static async Task Main(string[] args) { try @@ -44,9 +45,9 @@ namespace PlanTempus.SetupInfrastructure await host.StartAsync(); Console.WriteLine("Host has started."); - - var console = host.Services.GetRequiredService(); - console.Run(); + + var console = host.Services.GetRequiredService(); + console.Run(); await host.WaitForShutdownAsync(); } @@ -59,31 +60,30 @@ namespace PlanTempus.SetupInfrastructure public class ConsoleService { - static ConsoleColor _backgroundColor = Console.BackgroundColor; - static ConsoleColor _foregroundColor = Console.ForegroundColor; + static ConsoleColor _backgroundColor = Console.BackgroundColor; + static ConsoleColor _foregroundColor = Console.ForegroundColor; + private readonly IDbConnectionFactory _connectionFactory; + private readonly SetupDbAdmin _setupDbAdmin; + private readonly SetupIdentitySystem _setupIdentitySystem; + private readonly SetupConfiguration _setupConfiguration; + private readonly SetupApplicationUser _setupApplicationUser; - private readonly SetupDbAdmin _setupDbAdmin; - private readonly SetupIdentitySystem _setupIdentitySystem; - private readonly SetupConfiguration _setupConfiguration; - private readonly SetupApplicationUser _setupApplicationUser; - - public ConsoleService(SetupDbAdmin setupDbAdmin, SetupIdentitySystem setupIdentitySystem, SetupConfiguration setupConfiguration, SetupApplicationUser setupApplicationUser) + public ConsoleService(IDbConnectionFactory connectionFactory, SetupDbAdmin setupDbAdmin, SetupIdentitySystem setupIdentitySystem, SetupConfiguration setupConfiguration, SetupApplicationUser setupApplicationUser) { - _setupDbAdmin = setupDbAdmin; - _setupIdentitySystem = setupIdentitySystem; - _setupConfiguration = setupConfiguration; - _setupApplicationUser = setupApplicationUser; - } - static bool IsSuperAdmin() + _connectionFactory = connectionFactory; + _setupDbAdmin = setupDbAdmin; + _setupIdentitySystem = setupIdentitySystem; + _setupConfiguration = setupConfiguration; + _setupApplicationUser = setupApplicationUser; + } + bool IsSuperAdmin(ConnectionStringParameters parameters) { - //test db access Console.WriteLine("Testing db access..."); string query = @"SELECT usename, usesuper FROM pg_user WHERE usename = CURRENT_USER;"; - var conn = new NpgsqlConnection(); - - var result = (dynamic)conn.QuerySql(query).Single(); + using var conn = _connectionFactory.Create(parameters); + var result = (dynamic)conn.QuerySql(query).Single(); string username = result.usename; bool isSuperuser = (bool)result.usesuper; @@ -91,12 +91,12 @@ namespace PlanTempus.SetupInfrastructure if ((bool)result.usesuper) { Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(); Console.BackgroundColor = ConsoleColor.Yellow; - Console.WriteLine(); Console.WriteLine("TEST SUCCESSFULLY"); - Console.WriteLine(); Console.ForegroundColor = ConsoleColor.White; - Console.BackgroundColor = _backgroundColor; + Console.BackgroundColor = ConsoleColor.Black; + Console.WriteLine(); Console.WriteLine("-------------------------------"); Console.WriteLine(); Console.WriteLine($"Username: {username}"); @@ -118,7 +118,7 @@ namespace PlanTempus.SetupInfrastructure Console.WriteLine("TEST WAS NOT SUCCESSFULLY"); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.White; - Console.BackgroundColor = _backgroundColor; + Console.BackgroundColor = ConsoleColor.Black; Console.WriteLine("-------------------------------"); Console.WriteLine(); Console.WriteLine($"Username: {username}"); @@ -169,40 +169,38 @@ namespace PlanTempus.SetupInfrastructure string.IsNullOrEmpty(userPass.Split(":")[0]) || string.IsNullOrEmpty(userPass.Split(":")[1])); - //var ctp = new Startup.ConnectionStringTemplateParameters( - // user: userPass.Split(":")[0], - // pwd: userPass.Split(":")[1] - //); - //_container = new Startup().ConfigureContainer(ctp); + var superUser = new ConnectionStringParameters( + user: userPass.Split(":")[0], + pwd: userPass.Split(":")[1] + ); - if (IsSuperAdmin()) + if (IsSuperAdmin(superUser)) { Console.ForegroundColor = ConsoleColor.Green; var sw = new Stopwatch(); Console.Write("Database.Core.DCL.SetupDbAdmin..."); sw.Start(); - - _setupDbAdmin.With(new SetupDbAdmin.Command { Password = "3911", Schema = "system", User = "heimdall" }); - Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); + _setupDbAdmin.With(new SetupDbAdmin.Command { Password = "3911", Schema = "system", User = "heimdall" }, superUser); + Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); Console.WriteLine("::"); Console.WriteLine("::"); Console.Write("Database.Core.DDL.SetupIdentitySystem..."); sw.Restart(); - //create new container with application user, we use that role from now. - //_container = new Startup().ConfigureContainer(new Startup.ConnectionStringTemplateParameters("heimdall", "3911")); + //use application user, we use that role from now. + var connParams = new ConnectionStringParameters(user: "heimdall", pwd: "3911"); - _setupIdentitySystem.With(new SetupIdentitySystem.Command { Schema = "system" }); + _setupIdentitySystem.With(new SetupIdentitySystem.Command { Schema = "system" }, connParams); Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); Console.WriteLine("::"); Console.WriteLine("::"); Console.Write("Database.ConfigurationManagementSystem.SetupConfiguration..."); sw.Restart(); - _setupConfiguration.With(new SetupConfiguration.Command()); + _setupConfiguration.With(new SetupConfiguration.Command(), connParams); Console.Write($"DONE, took: {sw.ElapsedMilliseconds} ms"); @@ -210,8 +208,8 @@ namespace PlanTempus.SetupInfrastructure Console.WriteLine("::"); Console.Write("Database.Core.DCL.SetupApplicationUser..."); sw.Start(); - - _setupApplicationUser.With(new SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" }); + + _setupApplicationUser.With(new SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" }, superUser); Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); @@ -227,6 +225,7 @@ namespace PlanTempus.SetupInfrastructure catch (Exception e) { Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(); Console.WriteLine(e); } diff --git a/SetupInfrastructure/Startup.cs b/SetupInfrastructure/Startup.cs index 2d3eb82..0ffec4d 100644 --- a/SetupInfrastructure/Startup.cs +++ b/SetupInfrastructure/Startup.cs @@ -6,50 +6,44 @@ using PlanTempus.Database.Core; namespace PlanTempus.SetupInfrastructure { - public class Startup - { + public class Startup + { + public virtual IConfigurationRoot Configuration() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.json") + .Build(); - public virtual IConfigurationRoot Configuration() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appconfiguration.json") - .Build(); + return configuration; + } - return configuration; - } + public void ConfigureContainer(ContainerBuilder builder) + { - public void ConfigureContainer(ContainerBuilder builder) - { + var configuration = Configuration(); - //var builder = new ContainerBuilder(); - var configuration = Configuration(); + builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule + { + ConnectionString = configuration.GetConnectionString("DefaultConnection") + }); + + builder.RegisterModule(new TelemetryModule + { + TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() + }); + + builder.RegisterModule(new SeqLoggingModule + { + SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject() + }); - builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule - { - ConnectionString = configuration.GetConnectionString("DefaultConnection") - }); + builder.RegisterAssemblyTypes(typeof(IDbConfigure<>).Assembly) + .AsClosedTypesOf(typeof(IDbConfigure<>)) + .AsSelf(); - builder.RegisterModule(new TelemetryModule - { - TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() - }); + builder.RegisterType(); - builder.RegisterModule(new SeqLoggingModule - { - SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject() - }); - - - builder.RegisterAssemblyTypes(typeof(IDbConfigure<>).Assembly) - .AsClosedTypesOf(typeof(IDbConfigure<>)) - .AsSelf(); - - builder.RegisterType(); - - - //return builder.Build(); - } - - } + } + } } diff --git a/SetupInfrastructure/appconfiguration.json b/SetupInfrastructure/appconfiguration.json index ae27c49..9bb0266 100644 --- a/SetupInfrastructure/appconfiguration.json +++ b/SetupInfrastructure/appconfiguration.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;User Id={usr};Password={pwd};" + "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;" }, "ApplicationInsights": { "ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548", diff --git a/SqlManagement/.dbeaver/.project-metadata.json.bak b/SqlManagement/.dbeaver/.project-metadata.json.bak index 5119abe..3200961 100644 --- a/SqlManagement/.dbeaver/.project-metadata.json.bak +++ b/SqlManagement/.dbeaver/.project-metadata.json.bak @@ -1 +1 @@ -{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"}}} \ No newline at end of file +{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/Script.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"},"Scripts/SmartConfigSystem.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptmain"}}} \ No newline at end of file diff --git a/SqlManagement/Scripts/Script.sql b/SqlManagement/Scripts/Script.sql index 68ca72d..adc2d91 100644 --- a/SqlManagement/Scripts/Script.sql +++ b/SqlManagement/Scripts/Script.sql @@ -36,5 +36,6 @@ SHOW search_path; create table system.foos(id int) select * from system.foo +select * from "system".organizations diff --git a/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs index be895fe..53e5b2e 100644 --- a/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs +++ b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs @@ -132,7 +132,7 @@ namespace PlanTempus.Tests.ConfigurationTests } } - public class Feature + internal class Feature { public bool Enabled { get; set; } public int RolloutPercentage { get; set; }