diff --git a/Application/Startup.cs b/Application/Startup.cs index 4f1c1b4..2f9c0fa 100644 --- a/Application/Startup.cs +++ b/Application/Startup.cs @@ -41,7 +41,7 @@ namespace PlanTempus builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule { - TelemetryConfig = ConfigurationRoot.GetSection("ApplicationInsights").Get() + TelemetryConfig = ConfigurationRoot.GetSection(nameof(Core.ModuleRegistry.TelemetryConfig)).Get() }); } diff --git a/Application/appsettings.json b/Application/appsettings.json index 879d6f6..3d19a1f 100644 --- a/Application/appsettings.json +++ b/Application/appsettings.json @@ -1,12 +1,9 @@ { "AllowedHosts": "*", "ConnectionStrings": { - "ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=postgres;Password=3911;" - }, - "SendInBlue": { - "APIkey": "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" - }, - "ApplicationInsights": { + "ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=sathumper;Password=3911;" + }, + "TelemetryConfig": { "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" } } \ No newline at end of file diff --git a/Core/Configurations/Class1.cs b/Core/Configurations/Class1.cs new file mode 100644 index 0000000..4094f9d --- /dev/null +++ b/Core/Configurations/Class1.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Configurations +{ + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + public class KeyValueNester + { + private JObject _rootObject = new JObject(); + + public void AddKeyValue(string key, string jsonValue) + { + try + { + // Parse input JSON value + var valueObject = JsonConvert.DeserializeObject(jsonValue); + var parts = key.Split(':'); + + // Start med root object eller nuværende struktur + JObject current = _rootObject; + + // Gennemgå hver del af key'en + for (int i = 0; i < parts.Length - 1; i++) + { + string part = parts[i]; + if (!current.ContainsKey(part)) + { + current[part] = new JObject(); + } + current = (JObject)current[part]; + } + + // Tilføj den sidste værdi + current[parts[^1]] = valueObject; + } + catch (JsonReaderException ex) + { + throw new ArgumentException("Invalid JSON value", nameof(jsonValue), ex); + } + } + + public JObject GetResult() + { + return _rootObject; + } + + public void Clear() + { + _rootObject = new JObject(); + } + } +} diff --git a/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs b/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs new file mode 100644 index 0000000..a4c8888 --- /dev/null +++ b/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs @@ -0,0 +1,115 @@ +using Microsoft.Extensions.Configuration; +using Npgsql; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Configurations.PostgresqlConfigurationBuilder +{ + public class PostgresConfigurationSource : IConfigurationSource + { + private readonly string _connectionString; + private readonly string _channel; + private readonly string _configurationQuery; + + public PostgresConfigurationSource(string connectionString, string channel, string configurationQuery) + { + _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + _channel = channel ?? throw new ArgumentNullException(nameof(channel)); + _configurationQuery = configurationQuery ?? throw new ArgumentNullException(nameof(configurationQuery)); + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new PostgresConfigurationProvider(_connectionString, _channel, _configurationQuery); + } + } + + public class PostgresConfigurationProvider : ConfigurationProvider, IDisposable + { + private readonly string _connectionString; + private readonly string _channel; + private readonly string _configurationQuery; + private readonly NpgsqlConnection _listenerConnection; + private bool _disposedValue; + + public PostgresConfigurationProvider(string connectionString, string channel, string configurationQuery) + { + _connectionString = connectionString; + _channel = channel; + _configurationQuery = configurationQuery; + _listenerConnection = new NpgsqlConnection(connectionString); + + // Start listening for notifications + StartListening(); + } + + private async void StartListening() + { + try + { + await _listenerConnection.OpenAsync(); + _listenerConnection.Notification += OnNotificationReceived; + + using var cmd = new NpgsqlCommand($"LISTEN {_channel};", _listenerConnection); + await cmd.ExecuteNonQueryAsync(); + } + catch (Exception ex) + { + // Log error and possibly retry + Console.WriteLine($"Error starting listener: {ex.Message}"); + } + } + + private void OnNotificationReceived(object sender, NpgsqlNotificationEventArgs e) + { + if (e.Channel == _channel) + { + // Reload configuration and notify + Load(); + OnReload(); + } + } + + public override void Load() + { + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + using (var connection = new NpgsqlConnection(_connectionString)) + { + connection.Open(); + using var cmd = new NpgsqlCommand(_configurationQuery, connection); + using var reader = cmd.ExecuteReader(); + + while (reader.Read()) + { + string key = reader.GetString(0); + string value = reader.GetString(1); + data[key] = value; + } + } + + Data = data; + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _listenerConnection?.Dispose(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs b/Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs new file mode 100644 index 0000000..f1cb84f --- /dev/null +++ b/Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Configurations.PostgresqlConfigurationBuilder +{ + public static class PostgresConfigurationExtensions + { + public static IConfigurationBuilder AddPostgresConfiguration( + this IConfigurationBuilder builder, + Action setupAction) + { + var options = new PostgresConfigurationOptions(); + setupAction(options); + + builder.Add(new PostgresConfigurationSource( + options.ConnectionString, + options.Channel, + options.ConfigurationQuery)); + + return builder; + } + } + + public class PostgresConfigurationOptions + { + public string ConnectionString { get; set; } + public string Channel { get; set; } + public string ConfigurationQuery { get; set; } + } +} diff --git a/Core/ModuleRegistry/TelemetryModule.cs b/Core/ModuleRegistry/TelemetryModule.cs index e960ded..0983ca4 100644 --- a/Core/ModuleRegistry/TelemetryModule.cs +++ b/Core/ModuleRegistry/TelemetryModule.cs @@ -25,7 +25,6 @@ namespace Core.ModuleRegistry public class TelemetryConfig { - public string InstrumentationKey { get; set; } public string ConnectionString { get; set; } } } diff --git a/Database/Tenants/TenantSetupService.cs b/Database/Tenants/TenantSetupService.cs index 6d0e466..81a646c 100644 --- a/Database/Tenants/TenantSetupService.cs +++ b/Database/Tenants/TenantSetupService.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Insight.Database; namespace Database.Tenants { @@ -18,13 +14,13 @@ namespace Database.Tenants _db = db; } - public async Task CreateTenant(string schema) + 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 CreateRolesTable(schema); await CreatePermissionsTable(schema); diff --git a/PlanTempus.sln b/PlanTempus.sln index 08b21ed..add7632 100644 --- a/PlanTempus.sln +++ b/PlanTempus.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35013.160 @@ -9,7 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPostgresLISTEN", "TestPostgresLISTEN\TestPostgresLISTEN.csproj", "{743EF625-6C74-419C-A492-AA069956F471}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,6 +18,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {743EF625-6C74-419C-A492-AA069956F471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {743EF625-6C74-419C-A492-AA069956F471}.Debug|Any CPU.Build.0 = Debug|Any CPU + {743EF625-6C74-419C-A492-AA069956F471}.Release|Any CPU.ActiveCfg = Release|Any CPU + {743EF625-6C74-419C-A492-AA069956F471}.Release|Any CPU.Build.0 = Release|Any CPU {7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B554252-1CE4-44BD-B108-B0BDCCB24742}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/TestPostgresLISTEN/Program.cs b/TestPostgresLISTEN/Program.cs new file mode 100644 index 0000000..71dc835 --- /dev/null +++ b/TestPostgresLISTEN/Program.cs @@ -0,0 +1,51 @@ +using Npgsql; +using System; +using System.Threading.Tasks; + +class Program +{ + static async Task Main(string[] args) + { + // Tilpas connection string til dine behov + var connectionString = "Host=192.168.1.57;Database=ptdb01;Username=postgres;Password=3911"; + + try + { + await using NpgsqlConnection conn = new NpgsqlConnection(connectionString); + await conn.OpenAsync(); + + Console.WriteLine("Forbundet til databasen. Lytter efter notifikationer..."); + + // Opsæt notification handling + conn.Notification += (o, e) => + { + Console.WriteLine($"Notifikation modtaget:"); + Console.WriteLine($" PID: {e.PID}"); + Console.WriteLine($" Kanal: {e.Channel}"); + Console.WriteLine($" Payload: {e.Payload}"); + Console.WriteLine("------------------------"); + }; + + // Start lytning + await using (var cmd = new NpgsqlCommand("LISTEN config_changes;", conn)) + { + await cmd.ExecuteNonQueryAsync(); + } + + // Hold programmet kørende og lyt efter notifikationer + Console.WriteLine("Tryk på en tast for at stoppe..."); + + // Mens vi venter på input, skal vi huske at wait for notifikationer + while (!Console.KeyAvailable) + { + // Wait for notification for 1 second, then continue loop + await conn.WaitAsync(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Der opstod en fejl: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + } + } +} \ No newline at end of file diff --git a/TestPostgresLISTEN/TestPostgresLISTEN.csproj b/TestPostgresLISTEN/TestPostgresLISTEN.csproj new file mode 100644 index 0000000..d0f3322 --- /dev/null +++ b/TestPostgresLISTEN/TestPostgresLISTEN.csproj @@ -0,0 +1,13 @@ + + + + Exe + net8.0 + enable + + + + + + + diff --git a/Tests/UnitTest1.cs b/Tests/UnitTest1.cs index 1ceca93..675313e 100644 --- a/Tests/UnitTest1.cs +++ b/Tests/UnitTest1.cs @@ -1,13 +1,16 @@ using Autofac; using System.Data; using Insight.Database; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.Extensions.Configuration; +using Core.Configurations.PostgresqlConfigurationBuilder; namespace Tests { [TestClass] public class UnitTest1 : TestFixture { [TestMethod] - public void MyTestMethod() + 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/ @@ -16,8 +19,28 @@ namespace Tests conn.ExecuteSql("SELECT 1 as p"); - var sql = "SELECT * FROM public.Foo"; + var sql = "SELECT * FROM swp.foo"; var customers = conn.Query(sql, commandType:CommandType.Text); } + + + [TestMethod] + public void TryTenantSetupService() + { + var conn = Container.Resolve(); + } + + + [TestMethod] + public void MyTestMethod() + { + var builder = new ConfigurationBuilder() + .AddPostgresConfiguration(options => + { + options.ConnectionString = "Host=192.168.1.57;Database=ptdb01;Username=postgres;Password=3911"; + options.Channel = "config_changes"; + options.ConfigurationQuery = @"select * from dev.app_configuration"; + }); + } } } \ No newline at end of file diff --git a/Tests/appsettings.dev.json b/Tests/appsettings.dev.json index 8d8f823..0e3cd9a 100644 --- a/Tests/appsettings.dev.json +++ b/Tests/appsettings.dev.json @@ -1,7 +1,7 @@ { "AllowedHosts": "*", "ConnectionStrings": { - "ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=postgres;Password=3911;" + "ptdb": "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/"