diff --git a/Application/appconfiguration.json b/Application/appconfiguration.json index c43a4d2..ae27c49 100644 --- a/Application/appconfiguration.json +++ b/Application/appconfiguration.json @@ -3,7 +3,7 @@ "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;User Id={usr};Password={pwd};" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/", + "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", "UseSeqLoggingTelemetryChannel": true }, "SeqConfiguration": { diff --git a/Database/Core/ConnectionFactory/Class1.cs b/Database/Core/ConnectionFactory/Class1.cs new file mode 100644 index 0000000..cc80b4e --- /dev/null +++ b/Database/Core/ConnectionFactory/Class1.cs @@ -0,0 +1,70 @@ +using Npgsql; +using PlanTempus.Database.ModuleRegistry; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PlanTempus.Database.Core.ConnectionFactory +{ + public class PostgresConnectionFactory : IDbConnectionFactory, IAsyncDisposable + { + private readonly NpgsqlDataSource _baseDataSource; + private readonly Action _configureDataSource; + private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory; + + public PostgresConnectionFactory( + string connectionString, + Microsoft.Extensions.Logging.ILoggerFactory loggerFactory = null, + Action configureDataSource = null) + { + _loggerFactory = loggerFactory; + _configureDataSource = configureDataSource ?? (builder => { }); + + // Opret base data source med konfiguration + var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); + ConfigureDataSourceBuilder(dataSourceBuilder); + _baseDataSource = dataSourceBuilder.Build(); + } + + public IDbConnection Create() + { + return _baseDataSource.CreateConnection(); + } + + public IDbConnection Create(string username, string password) + { + var connectionStringBuilder = new NpgsqlConnectionStringBuilder( + _baseDataSource.ConnectionString) + { + Username = username, + Password = password + }; + + var tempDataSourceBuilder = new NpgsqlDataSourceBuilder( + connectionStringBuilder.ToString()); + + ConfigureDataSourceBuilder(tempDataSourceBuilder); + + var tempDataSource = tempDataSourceBuilder.Build(); + return tempDataSource.CreateConnection(); + } + + private void ConfigureDataSourceBuilder(NpgsqlDataSourceBuilder builder) + { + if (_loggerFactory != null) + { + builder.UseLoggerFactory(_loggerFactory); + } + + _configureDataSource?.Invoke(builder); + } + + public async ValueTask DisposeAsync() + { + await _baseDataSource.DisposeAsync(); + } + } +} diff --git a/Database/Core/Sql/SqlOperations.cs b/Database/Core/Sql/SqlOperations.cs new file mode 100644 index 0000000..e0c5ef4 --- /dev/null +++ b/Database/Core/Sql/SqlOperations.cs @@ -0,0 +1,102 @@ +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; +using PlanTempus.Database.ModuleRegistry; +using System.Data; + +namespace PlanTempus.Database.Core.Sql +{ + public class DatabaseScope : IDisposable + { + private readonly IDbConnection _connection; + private readonly IOperationHolder _operation; + + public DatabaseScope(IDbConnection connection, IOperationHolder operation) + { + _connection = connection; + _operation = operation; + } + + public IDbConnection Connection => _connection; + + public void Success() + { + _operation.Telemetry.Success = true; + } + + public void Error(Exception ex) + { + _operation.Telemetry.Success = false; + _operation.Telemetry.Properties["Error"] = ex.Message; + } + + public void Dispose() + { + _operation.Dispose(); + _connection.Dispose(); + } + } + + public interface IDatabaseOperations + { + DatabaseScope CreateScope(string operationName); + Task ExecuteAsync(Func> operation, string operationName); + Task ExecuteAsync(Func operation, string operationName); + } + + public class SqlOperations : IDatabaseOperations + { + private readonly IDbConnectionFactory _connectionFactory; + private readonly TelemetryClient _telemetryClient; + + public SqlOperations(IDbConnectionFactory connectionFactory, TelemetryClient telemetryClient) + { + _connectionFactory = connectionFactory; + _telemetryClient = telemetryClient; + } + + public DatabaseScope CreateScope(string operationName) + { + var connection = _connectionFactory.Create(); + var operation = _telemetryClient.StartOperation(operationName); + operation.Telemetry.Type = "SQL"; + operation.Telemetry.Target = "PostgreSQL"; + + return new DatabaseScope(connection, operation); + } + + public async Task ExecuteAsync(Func> operation, string operationName) + { + using var scope = CreateScope(operationName); + try + { + var result = await operation(scope.Connection); + scope.Success(); + return result; + } + catch (Exception ex) + { + scope.Error(ex); + throw; + } + } + + public async Task ExecuteAsync(Func operation, string operationName) + { + using var scope = CreateScope(operationName); + try + { + await operation(scope.Connection); + scope.Success(); + } + catch (Exception ex) + { + scope.Error(ex); + throw; + } + } + + } + + +} diff --git a/Database/ModuleRegistry/DbPostgreSqlModule.cs b/Database/ModuleRegistry/DbPostgreSqlModule.cs index b22afe9..b914756 100644 --- a/Database/ModuleRegistry/DbPostgreSqlModule.cs +++ b/Database/ModuleRegistry/DbPostgreSqlModule.cs @@ -3,20 +3,55 @@ using Npgsql; using System.Data; namespace PlanTempus.Database.ModuleRegistry { + public interface IDbConnectionFactory + { + IDbConnection Create(); + IDbConnection Create(string username, string password); + } public class DbPostgreSqlModule : Module { public required string ConnectionString { get; set; } + protected override void Load(ContainerBuilder builder) { Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider(); - builder.Register(c => - { - IDbConnection connection = new NpgsqlConnection(ConnectionString); - return connection; - }) - .InstancePerLifetimeScope(); + builder.Register(c => + new Core.ConnectionFactory.PostgresConnectionFactory(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 59c2053..58b83a1 100644 --- a/SetupInfrastructure/Program.cs +++ b/SetupInfrastructure/Program.cs @@ -29,82 +29,28 @@ namespace PlanTempus.SetupInfrastructure static async Task Main(string[] args) { - Welcome(); - string userPass; - try { - do - { - Console.WriteLine("Input username:password for a superadmin role"); - userPass = Console.ReadLine() ?? string.Empty; - } while (!userPass.Contains(":") || userPass.Split(":").Length != 2 || - string.IsNullOrEmpty(userPass.Split(":")[0]) || - string.IsNullOrEmpty(userPass.Split(":")[1])); + var host = Host.CreateDefaultBuilder(args) + .UseEnvironment("Development") + .UseServiceProviderFactory(new Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory()) + .ConfigureContainer((hostContext, builder) => + { + var startup = new Startup(); + startup.ConfigureContainer(); + }) - var ctp = new Startup.ConnectionStringTemplateParameters( - user: userPass.Split(":")[0], - pwd: userPass.Split(":")[1] - ); - _container = new Startup().ConfigureContainer(ctp); - - if (IsSuperAdmin()) - { - Console.ForegroundColor = ConsoleColor.Green; - var sw = new Stopwatch(); - - Console.Write("Database.Core.DCL.SetupDbAdmin..."); - sw.Start(); - var setupDbAdmin = _container.Resolve(); - setupDbAdmin.With(new SetupDbAdmin.Command { Password = "3911", Schema = "system", User = "heimdall" }); - 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")); - - var setupIdentitySystem = _container.Resolve(); - setupIdentitySystem.With(new SetupIdentitySystem.Command { Schema = "system" }); - Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); - - Console.WriteLine("::"); - Console.WriteLine("::"); - Console.Write("Database.ConfigurationManagementSystem.SetupConfiguration..."); - sw.Restart(); - var setupConfigurationSystem = _container.Resolve(); - setupConfigurationSystem.With(new SetupConfiguration.Command()); - Console.Write($"DONE, took: {sw.ElapsedMilliseconds} ms"); - - - Console.WriteLine("::"); - Console.WriteLine("::"); - Console.Write("Database.Core.DCL.SetupApplicationUser..."); - sw.Start(); - var setupApplicationUser = _container.Resolve(); - setupApplicationUser.With(new SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" }); - Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); - - - //and a lot of other tables that we haven't defined yet - - // input configurations!!! TODO:Missing - - Console.ForegroundColor = _foregroundColor; - } + .Build(); + await host.StartAsync(); + Console.WriteLine("Host has started."); + Run(); + await host.WaitForShutdownAsync(); } - - catch (Exception e) + catch (Exception ex) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(e); - + Console.WriteLine($"Host failed to start: {ex}"); } @@ -112,8 +58,6 @@ namespace PlanTempus.SetupInfrastructure static bool IsSuperAdmin() { - - //test db access Console.WriteLine("Testing db access..."); @@ -191,34 +135,90 @@ namespace PlanTempus.SetupInfrastructure } - async Task Run(string[] args) - { - try - { - var host = Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory()) - .ConfigureContainer((hostContext, builder) => - { - var startup = new Startup(); - var connectionStringParams = new Startup.ConnectionStringTemplateParameters("your_user", "your_password"); - startup.ConfigureContainer(connectionStringParams); - }) - .ConfigureServices((hostContext, services) => - { - // Konfigurer andre services her (hvis nødvendigt) - }) - .Build(); + static void Run() + { + + + Welcome(); + string userPass; + + try + { + do + { + Console.WriteLine("Input username:password for a superadmin role"); + userPass = Console.ReadLine() ?? string.Empty; + } while (!userPass.Contains(":") || userPass.Split(":").Length != 2 || + 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); + + if (IsSuperAdmin()) + { + Console.ForegroundColor = ConsoleColor.Green; + var sw = new Stopwatch(); + + Console.Write("Database.Core.DCL.SetupDbAdmin..."); + sw.Start(); + var setupDbAdmin = _container.Resolve(); + setupDbAdmin.With(new SetupDbAdmin.Command { Password = "3911", Schema = "system", User = "heimdall" }); + 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")); + + var setupIdentitySystem = _container.Resolve(); + setupIdentitySystem.With(new SetupIdentitySystem.Command { Schema = "system" }); + Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); + + Console.WriteLine("::"); + Console.WriteLine("::"); + Console.Write("Database.ConfigurationManagementSystem.SetupConfiguration..."); + sw.Restart(); + var setupConfigurationSystem = _container.Resolve(); + setupConfigurationSystem.With(new SetupConfiguration.Command()); + Console.Write($"DONE, took: {sw.ElapsedMilliseconds} ms"); + + + Console.WriteLine("::"); + Console.WriteLine("::"); + Console.Write("Database.Core.DCL.SetupApplicationUser..."); + sw.Start(); + var setupApplicationUser = _container.Resolve(); + setupApplicationUser.With(new SetupApplicationUser.Command { Password = "3911", Schema = "system", User = "sathumper" }); + Console.WriteLine($"DONE, took: {sw.ElapsedMilliseconds} ms"); + + + //and a lot of other tables that we haven't defined yet + + // input configurations!!! TODO:Missing + + Console.ForegroundColor = _foregroundColor; + } + + + } + + catch (Exception e) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(e); + + } + + } + } - await host.StartAsync(); - Console.WriteLine("Host has started."); - await host.WaitForShutdownAsync(); - } - catch (Exception ex) - { - Console.WriteLine($"Host failed to start: {ex}"); - } - } - } - } diff --git a/SetupInfrastructure/Startup.cs b/SetupInfrastructure/Startup.cs index 12ea020..b6a6138 100644 --- a/SetupInfrastructure/Startup.cs +++ b/SetupInfrastructure/Startup.cs @@ -6,47 +6,48 @@ using PlanTempus.Database.Core; namespace PlanTempus.SetupInfrastructure { - public class Startup - { - public virtual IConfigurationRoot Configuration() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appconfiguration.json") - .Build(); + public class Startup + { - return configuration; - } + public virtual IConfigurationRoot Configuration() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.json") + .Build(); - public IContainer ConfigureContainer(ConnectionStringTemplateParameters ctp) - { - var builder = new ContainerBuilder(); - var configuration = Configuration(); + return configuration; + } + + public IContainer ConfigureContainer() + { + + var builder = new ContainerBuilder(); + var configuration = Configuration(); - builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule - { - ConnectionString = configuration.GetConnectionString("DefaultConnection").Replace("{usr}", ctp.user).Replace("{pwd}", ctp.pwd) - }); + builder.RegisterModule(new Database.ModuleRegistry.DbPostgreSqlModule + { + ConnectionString = configuration.GetConnectionString("DefaultConnection") + }); - builder.RegisterModule(new TelemetryModule - { - TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() - }); + builder.RegisterModule(new TelemetryModule + { + TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() + }); - builder.RegisterModule(new SeqLoggingModule - { - SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject() - }); + builder.RegisterModule(new SeqLoggingModule + { + SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject() + }); - builder.RegisterAssemblyTypes(typeof(IDbConfigure<>).Assembly) - .AsClosedTypesOf(typeof(IDbConfigure<>)) - .AsSelf(); + builder.RegisterAssemblyTypes(typeof(IDbConfigure<>).Assembly) + .AsClosedTypesOf(typeof(IDbConfigure<>)) + .AsSelf(); - return builder.Build(); - } + return builder.Build(); + } - public record ConnectionStringTemplateParameters(string user, string pwd); - } + } } diff --git a/SetupInfrastructure/appconfiguration.json b/SetupInfrastructure/appconfiguration.json index c43a4d2..ae27c49 100644 --- a/SetupInfrastructure/appconfiguration.json +++ b/SetupInfrastructure/appconfiguration.json @@ -3,7 +3,7 @@ "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;User Id={usr};Password={pwd};" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/", + "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", "UseSeqLoggingTelemetryChannel": true }, "SeqConfiguration": { diff --git a/Tests/ConfigurationTests/appconfiguration.dev.json b/Tests/ConfigurationTests/appconfiguration.dev.json index 56202bc..9fe57de 100644 --- a/Tests/ConfigurationTests/appconfiguration.dev.json +++ b/Tests/ConfigurationTests/appconfiguration.dev.json @@ -4,7 +4,7 @@ "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;User Id=sathumper;Password=3911;" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/", + "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", "UseSeqLoggingTelemetryChannel": true }, "Authentication": "SHA256", diff --git a/Tests/PostgresTests.cs b/Tests/PostgresTests.cs index c31b713..a3732a2 100644 --- a/Tests/PostgresTests.cs +++ b/Tests/PostgresTests.cs @@ -1,58 +1,84 @@ using Autofac; using System.Data; using Insight.Database; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using PlanTempus.Database.Core.Sql; namespace PlanTempus.Tests { [TestClass] public class PostgresTests : TestFixture { + Database.ModuleRegistry.IDbConnectionFactory _connFactory; + IDatabaseOperations _databaseOperations; + + [TestInitialize] + public void MyTestMethod() + { + _connFactory = Container.Resolve(); + _databaseOperations = Container.Resolve(); + } [TestMethod] public void TestDefaultConnection() { - var conn = Container.Resolve(); - //https://www.reddit.com/r/dotnet/comments/6wdoyn/how_to_properly_register_dapper_on_net_core_2_di/ - //https://www.code4it.dev/blog/postgres-crud-dapper/ //https://stackoverflow.com/questions/69169247/how-to-create-idbconnection-factory-using-autofac-for-dapper - conn.ExecuteSql("SELECT 1 as p"); + using (var conn = _connFactory.Create()) + conn.ExecuteSql("SELECT 1 as p"); } - //static HttpClient _client = new HttpClient(); - //[TestMethod] - //public async Task MyTestMethod() - //{ - // _client.BaseAddress = new Uri("http://localhost:5341"); - - // var l = new SeqLogger(_client, "", ""); - - // for (int i = 0; i < 20; i++) - // { - - // await l.LogToSeq( - // "Bruger {UserId} loggede ind", - // "Debug", - // new Dictionary { { "UserId", "12345" }, { "Counter", i++ } } - // ); - // } - // var logger = Container.Resolve(); - - - // for (int i = 0; i < 5; i++) - // { - - // logger.TrackTrace("Hello 23", Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information); - - // } - - //} [TestMethod] - public void TryOrganizationSetupService() + public async Task TestScopeConnectionWithLogging() { - var conn = Container.Resolve(); - } + using var db = _databaseOperations.CreateScope(nameof(TestScopeConnectionWithLogging)); + try + { + var user = await db.Connection.QuerySqlAsync( + "SELECT tablename FROM pg_tables limit 5"); + db.Success(); + } + catch (Exception ex) + { + db.Error(ex); + throw; + } + } + [TestMethod] + public async Task TestScopeConnectionWithErrorLogging() + { + using var db = _databaseOperations.CreateScope(nameof(TestScopeConnectionWithLogging)); + try + { + var user = await db.Connection.QuerySqlAsync( + "SELECT tablename FROM pg_tabless limit 5"); + + db.Success(); + } + catch (Exception ex) + { + db.Error(ex); + throw; + } + } + [TestMethod] + public async Task TestSimpleDatabaseOperation() + { + try + { + await _databaseOperations.ExecuteAsync(async connection => + { + return await connection.QuerySqlAsync( + "SELECT tablename FROM pg_tables limit 5"); + }, nameof(TestSimpleDatabaseOperation)); + + } + catch (Exception ex) + { + throw; + } + } [TestMethod] public async Task TryDbSetup() { @@ -65,6 +91,7 @@ namespace PlanTempus.Tests [TestMethod] public void SetupPostgresql_LISTEN() { + //this is not in use a the moment... kind of premature test to get my mind in to gear //var builder = new ConfigurationBuilder() // .AddPostgresConfiguration(options => // { diff --git a/Tests/appconfiguration.dev.json b/Tests/appconfiguration.dev.json index 630dcb5..7ad4412 100644 --- a/Tests/appconfiguration.dev.json +++ b/Tests/appconfiguration.dev.json @@ -3,7 +3,7 @@ "DefaultConnection": "Host=192.168.1.57;Port=5432;Database=ptmain;User Id=sathumper;Password=3911;" }, "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/", + "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", "UseSeqLoggingTelemetryChannel": true }, "SeqConfiguration": { @@ -11,4 +11,4 @@ "ApiKey": null, "Environment": "MSTEST" } -} \ No newline at end of file +}