diff --git a/Application/Startup.cs b/Application/Startup.cs index 2f9c0fa..757db74 100644 --- a/Application/Startup.cs +++ b/Application/Startup.cs @@ -1,30 +1,29 @@ using Autofac; - +using Core.Configurations.JsonConfigProvider; +using Core.Configurations; namespace PlanTempus { - public class Startup - { - public Startup(IWebHostEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); + public class Startup + { + public Startup(IWebHostEnvironment env) + { + var builder = new Core.Configurations.ConfigurationBuilder() + .AddJsonFile("appconfiguration.json", optional: true, reloadOnChange: true); - ConfigurationRoot = builder.Build(); - } + ConfigurationRoot = builder.Build(); + } - public IConfigurationRoot ConfigurationRoot { get; private set; } + public Core.Configurations.IConfigurationRoot ConfigurationRoot { get; private set; } - public ILifetimeScope AutofacContainer { get; private set; } + public ILifetimeScope AutofacContainer { get; private set; } - public void ConfigureServices(IServiceCollection services) - { + public void ConfigureServices(IServiceCollection services) + { services.AddControllers(); - services.AddOptions(); - services.AddRazorPages(); - //services.AddApplicationInsightsTelemetry(ConfigurationRoot["ApplicationInsights:InstrumentationKey"]); - services.AddAntiforgery(x => x.HeaderName = "X-ANTI-FORGERY-TOKEN"); + services.AddOptions(); + services.AddRazorPages(); + //services.AddApplicationInsightsTelemetry(ConfigurationRoot["ApplicationInsights:InstrumentationKey"]); + services.AddAntiforgery(x => x.HeaderName = "X-ANTI-FORGERY-TOKEN"); services.Configure(options => { @@ -32,42 +31,43 @@ namespace PlanTempus }); } - public void ConfigureContainer(ContainerBuilder builder) - { - builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule - { - ConnectionString = ConfigurationRoot.GetConnectionString("ptdb") - }); + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule + { + ConnectionString = ConfigurationRoot.GetConnectionString("ptdb") + }); + + builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule + { + TelemetryConfig = ConfigurationRoot.GetSection(nameof(Core.ModuleRegistry.TelemetryConfig)).Get() + }); + - builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule - { - TelemetryConfig = ConfigurationRoot.GetSection(nameof(Core.ModuleRegistry.TelemetryConfig)).Get() - }); - } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { - if (env.IsDevelopment()) - app.UseDeveloperExceptionPage(); - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - app.UseHttpsRedirection(); - app.UseRouting(); + if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + else + { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + app.UseHttpsRedirection(); + app.UseRouting(); - app.UseStaticFiles(); - app.UseEndpoints(endpoints => - { - //endpoints.MapHub("/motionhub"); - endpoints.MapRazorPages(); - endpoints.MapControllers(); - }); + app.UseStaticFiles(); + app.UseEndpoints(endpoints => + { + //endpoints.MapHub("/motionhub"); + endpoints.MapRazorPages(); + endpoints.MapControllers(); + }); - } - } + } + } } diff --git a/Application/appsettings.Development.json b/Application/appconfiguration.Development.json similarity index 100% rename from Application/appsettings.Development.json rename to Application/appconfiguration.Development.json diff --git a/Application/appsettings.json b/Application/appconfiguration.json similarity index 79% rename from Application/appsettings.json rename to Application/appconfiguration.json index 3d19a1f..3636840 100644 --- a/Application/appsettings.json +++ b/Application/appconfiguration.json @@ -2,8 +2,13 @@ "AllowedHosts": "*", "ConnectionStrings": { "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" + }, + "Feature": { + "Enabled": true, + "RolloutPercentage": 25, + "AllowedUserGroups": [ "beta" ] } } \ No newline at end of file diff --git a/Core/Configurations/AzureConfigurationManager.cs b/Core/Configurations/AzureConfigurationManager.cs deleted file mode 100644 index 772ddaa..0000000 --- a/Core/Configurations/AzureConfigurationManager.cs +++ /dev/null @@ -1,50 +0,0 @@ - -using Microsoft.Extensions.Configuration; - -namespace Core.Configurations -{ - public class AzureConfigurationManager - { - private static IConfigurationBuilder _configurationBuilder; - - /// - /// This AppConfigBuilder assumes that AppConfigEndpoint and AppConfigLabelFilter are configured as Settings on Azure for the Application that needs them. - /// AppConfigEndpoint would look like this: Endpoint=https://config-dec-test.azconfig.io;Id=0-l9-s0:foo;Secret=somesecret/bar - /// - /// - /// Path relative to the base path stored in Microsoft.Extensions.Configuration.IConfigurationBuilder.Properties of builder. - /// - public static IConfigurationBuilder AppConfigBuilder(string localSettingsFile) - { - if (_configurationBuilder == null) - { - var envConfiguration = new Microsoft.Extensions.Configuration.ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - - System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out)); - - var appConfigEndpoint = envConfiguration["AppConfigEndpoint"]; - var appConfigLabel = envConfiguration["AppConfigLabelFilter"]; - - _configurationBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder(); - if (!string.IsNullOrEmpty(appConfigEndpoint)) - { - _configurationBuilder - .AddAzureAppConfiguration(options => - { - options.Connect(appConfigEndpoint); - options.Select(keyFilter: "*", labelFilter: appConfigLabel); - }) - .AddEnvironmentVariables(); - } - else - { - _configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()); - _configurationBuilder.AddJsonFile(localSettingsFile, optional: false); - } - } - return _configurationBuilder; - } - } -} diff --git a/Core/Configurations/Common/KeyValueToJson.cs b/Core/Configurations/Common/KeyValueToJson.cs new file mode 100644 index 0000000..073daa7 --- /dev/null +++ b/Core/Configurations/Common/KeyValueToJson.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Configurations.Common +{ + + using Newtonsoft.Json.Linq; + using System.Collections.Generic; + + public static class KeyValueToJson + { + public static JObject Convert(IEnumerable> pairs) + { + var root = new JObject(); + + foreach (var pair in pairs) + { + var keys = pair.Key.Split(':'); + var current = root; + + // Gennemgå hierarkiet og opret underobjekter, hvis de ikke eksisterer + for (int i = 0; i < keys.Length - 1; i++) + { + var key = keys[i]; + + if (current[key] == null) + { + current[key] = new JObject(); + } + + current = (JObject)current[key]; + } + + // Håndter den sidste nøgle og tilføj værdien + var lastKey = keys[keys.Length - 1]; + var value = ConvertValue(pair.Value); + + // Hvis den sidste nøgle allerede eksisterer, tilføj til en liste + if (current[lastKey] != null) + { + // Hvis den allerede er en liste, tilføj til listen + if (current[lastKey].Type == JTokenType.Array) + { + ((JArray)current[lastKey]).Add(value); + } + // Hvis den ikke er en liste, konverter til en liste + else + { + var existingValue = current[lastKey]; + current[lastKey] = new JArray { existingValue, value }; + } + } + // Ellers tilføj som en enkelt værdi + else + { + current[lastKey] = value; + } + } + + return root; + } + + private static JToken ConvertValue(object value) + { + // Hvis værdien allerede er en JToken, returner den direkte + if (value is JToken token) + { + return token; + } + + // Konverter andre typer + return value switch + { + int i => new JValue(i), + double d => new JValue(d), + bool b => new JValue(b), + string s => new JValue(s), + _ => new JValue(value.ToString()) + }; + } + } +} \ No newline at end of file diff --git a/Core/Configurations/ConfigurationBuilder.cs b/Core/Configurations/ConfigurationBuilder.cs index 1c31410..e771483 100644 --- a/Core/Configurations/ConfigurationBuilder.cs +++ b/Core/Configurations/ConfigurationBuilder.cs @@ -1,61 +1,116 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - +using Newtonsoft.Json.Linq; namespace Core.Configurations { - public class ConfigurationBuilder - { - private readonly List _providers = new(); + public interface IConfigurationBuilder + { + ConfigurationBuilder AddProvider(IConfigurationProvider provider); + IConfigurationRoot Build(); + List ConfigurationProviders { get; } + } - public ConfigurationBuilder AddProvider(IConfigurationProvider provider) - { - _providers.Add(provider); - return this; - } + public class ConfigurationBuilder : IConfigurationBuilder + { + public List ConfigurationProviders { get; private set; } = []; - public IConfigurationRoot Build() - { - // Her kan du implementere din egen sammenlægningslogik - return new ConfigurationRoot(_providers); - } - } + public ConfigurationBuilder AddProvider(IConfigurationProvider provider) + { + ((IConfigurationBuilder)this).ConfigurationProviders.Add(provider); + return this; + } + public IConfigurationRoot Build() + { + foreach (var provider in ConfigurationProviders) + { + provider.Build(); + } + //TODO: we need to come up with merge strategy - public class ConfigurationRoot : IConfigurationRoot - { - public ConfigurationRoot(List configurationProviders) - { + return new ConfigurationRoot(ConfigurationProviders); + } + } - } - public T GetSection(string key) - { - throw new NotImplementedException(); - } - } - public static class ConfigurationPredicateExtensions - { + public class ConfigurationRoot : IConfigurationRoot + { + List IConfigurationRoot.ConfigurationProviders { get; set; } - public static IConfigurationSection GetSection(this IConfigurationRoot configurationSection, string key) - { - return null; - } + public ConfigurationRoot(List configurationProviders) + { + ((IConfigurationRoot)this).ConfigurationProviders = configurationProviders; + } - public static T Get(this IConfigurationSection configuration, string key) - { - return default(T); - } - } - public interface IConfigurationProvider - { - Dictionary Configuration(); - } + } + public static class ConfigurationBinder + { + private static string NormalizePath(string path) + { + return path?.Replace(":", ".", StringComparison.Ordinal) ?? string.Empty; + } + public static string GetConnectionString(this IConfigurationRoot configuration, string name) + { + return configuration.GetSection("ConnectionStrings").Get(name); + } + public static IConfigurationSection GetSection(this IConfigurationRoot configuration, string path) + { + JToken value = null; + foreach (var provider in configuration.ConfigurationProviders) + { + var test = provider.Configuration().SelectToken(NormalizePath(path)); - public interface IConfigurationSection - { - string Key { get; } - string Path { get; } - string Value { get; set; } - } + if (test != null) + value = test; + } + + return new ConfigurationSection { Path = path, Key = path.Split(':').Last(), Value = value }; + } + public static T Get(this IConfigurationRoot configuration, string path) + { + JToken value = null; + foreach (var provider in configuration.ConfigurationProviders) + { + var test = provider.Configuration().SelectToken(NormalizePath(path)); + + if (test != null) + value = test; + } + + return value.ToObject(); + } + public static T Get(this IConfigurationSection configuration, string path) + { + var value = configuration.Value.SelectToken(NormalizePath(path)).ToObject(); + return value; + } + public static T ToObject(this IConfigurationSection configuration) + { + var value = configuration.Value.ToObject(); + return value; + } + + [Obsolete("Use ToObject")] + public static T Get(this IConfigurationSection configuration) + { + return configuration.Value.ToObject(); + } + + } + public interface IConfigurationProvider + { + void Build(); + Newtonsoft.Json.Linq.JObject Configuration(); + } + + public class ConfigurationSection : IConfigurationSection + { + public required string Path { get; set; } + public required string Key { get; set; } + + public required JToken Value { get; set; } + + } + public interface IConfigurationSection + { + string Path { get; } + string Key { get; } + Newtonsoft.Json.Linq.JToken Value { get; set; } + } } diff --git a/Core/Configurations/IConfigurationRoot.cs b/Core/Configurations/IConfigurationRoot.cs index ff2c5f1..2fb16c9 100644 --- a/Core/Configurations/IConfigurationRoot.cs +++ b/Core/Configurations/IConfigurationRoot.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Core.Configurations +namespace Core.Configurations { public interface IConfigurationRoot { - - } - + internal List ConfigurationProviders { get; set; } + } } diff --git a/Core/Configurations/JsonConfigProvider/JsonConfigExtension.cs b/Core/Configurations/JsonConfigProvider/JsonConfigExtension.cs new file mode 100644 index 0000000..9061f17 --- /dev/null +++ b/Core/Configurations/JsonConfigProvider/JsonConfigExtension.cs @@ -0,0 +1,54 @@ +using Core.Exceptions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Core.Configurations.JsonConfigProvider +{ + public static class JsonConfigExtension + { + public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, string configurationFilePath = "appconfiguration.json", bool? optional = true, bool? reloadOnChange = false) + { + return builder.AddProvider(new JsonConfigProvider(builder, configurationFilePath, optional ?? true, reloadOnChange ?? false)); + } + } + + public interface IHasConfigurationFilePath + { + string ConfigurationFilePath { get; } + } + public class JsonConfigProvider : IConfigurationProvider, IHasConfigurationFilePath + { + private readonly IConfigurationBuilder _builder; + private readonly bool _reloadOnChange; + JObject _configuration; + public string ConfigurationFilePath { get; private set; } + + public JsonConfigProvider() { } + + public JsonConfigProvider(IConfigurationBuilder builder, string configurationFilePath, bool optional, bool reloadOnChange) + { + if (!optional && !File.Exists(configurationFilePath)) + throw new ConfigurationException($"File not found, path: {configurationFilePath}"); + if (optional && !File.Exists(configurationFilePath)) + return; + + ConfigurationFilePath = configurationFilePath; + _builder = builder; + _reloadOnChange = reloadOnChange; + } + + public void Build() + { + using (StreamReader file = File.OpenText(ConfigurationFilePath)) + using (JsonTextReader reader = new JsonTextReader(file)) + { + _configuration = (JObject)JToken.ReadFrom(reader); + } + } + + public JObject Configuration() + { + return _configuration; + } + } +} diff --git a/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs b/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs deleted file mode 100644 index 252bf2a..0000000 --- a/Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs +++ /dev/null @@ -1,115 +0,0 @@ -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 : Microsoft.Extensions.Configuration.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 Microsoft.Extensions.Configuration.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 deleted file mode 100644 index 1c8e1cb..0000000 --- a/Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Extensions.Configuration; -namespace Core.Configurations.PostgresqlConfigurationBuilder -{ - /// - /// LISTEN / NOTIFY in Postgresql - /// - 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/Configurations/SmartConfigProvider/AppConfiguration.cs b/Core/Configurations/SmartConfigProvider/AppConfiguration.cs new file mode 100644 index 0000000..1ce705a --- /dev/null +++ b/Core/Configurations/SmartConfigProvider/AppConfiguration.cs @@ -0,0 +1,14 @@ +namespace Core.Configurations.SmartConfigProvider; +public class AppConfiguration +{ + public long Id { get; set; } + public string Key { get; set; } + public object Value { get; set; } + public string Label { get; set; } + public string ContentType { get; set; } + public DateTime? ValidFrom { get; set; } + public DateTime? ExpiresAt { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public Guid? Etag { get; set; } +} \ No newline at end of file diff --git a/Core/Configurations/SmartConfigProvider/ConfigurationRepository.cs b/Core/Configurations/SmartConfigProvider/ConfigurationRepository.cs new file mode 100644 index 0000000..8c8b417 --- /dev/null +++ b/Core/Configurations/SmartConfigProvider/ConfigurationRepository.cs @@ -0,0 +1,31 @@ +using System.Data; +using Core.Configurations.SmartConfiguration; +using Insight.Database; + +namespace Core.Configurations.SmartConfigProvider; +public class ConfigurationRepository : IConfigurationRepository +{ + private readonly IDbConnection _connection; + + public ConfigurationRepository(IDbConnection connection) + { + _connection = connection; + } + public ConfigurationRepository(string connectionString) + { + _connection = new Npgsql.NpgsqlConnection(connectionString); + } + public IEnumerable GetActiveConfigurations() + { + const string sql = @" + SELECT id, ""key"", value, label, content_type, + valid_from, expires_at, created_at, modified_at, etag + FROM app_configuration + WHERE CURRENT_TIMESTAMP BETWEEN valid_from AND expires_at + OR (valid_from IS NULL AND expires_at IS NULL)"; + + + return _connection.QuerySql(sql); + + } +} \ No newline at end of file diff --git a/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs b/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs new file mode 100644 index 0000000..65de740 --- /dev/null +++ b/Core/Configurations/SmartConfigProvider/IConfigurationRepository.cs @@ -0,0 +1,7 @@ +using Core.Configurations.SmartConfigProvider; + +namespace Core.Configurations.SmartConfiguration; +public interface IConfigurationRepository +{ + IEnumerable GetActiveConfigurations(); +} \ No newline at end of file diff --git a/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs b/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs new file mode 100644 index 0000000..62f7c29 --- /dev/null +++ b/Core/Configurations/SmartConfigProvider/SmartConfigExtension.cs @@ -0,0 +1,74 @@ +using Core.Configurations.JsonConfigProvider; +using Core.Configurations.SmartConfigProvider; +using Core.Exceptions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Core.Configurations.SmartConfig +{ + public static class SmartConfigExtension + { + public static IConfigurationBuilder AddSmartConfig(this IConfigurationBuilder builder, string configKey = "DefaultConnection", string path = null) + { + return builder.AddProvider(new SmartConfigProvider(builder, configKey, path)); + } + } + + public class SmartConfigProvider : IConfigurationProvider + { + string _configKey; + string _connectionString; + string _path; + IConfigurationBuilder _builder; + + Newtonsoft.Json.Linq.JObject _configuration; + + + public SmartConfigProvider() { } + + public SmartConfigProvider(IConfigurationBuilder builder, string configKey, string configurationFilePath) + { + _builder = builder; + _configKey = configKey; + _path = configurationFilePath; + SetConnectionString(); + } + + void SetConnectionString() + { + var carrier = _builder.ConfigurationProviders.OfType().SingleOrDefault(); + + if (carrier?.ConfigurationFilePath is null && _path is null) + throw new ConfigurationException($"Expected a previous added ConfigurationProvider with IHasConfigurationFilePath or a configurationFilePath where to find the appsettingsfile"); + + _path ??= carrier.ConfigurationFilePath; + + if (!File.Exists(_path)) + throw new ConfigurationException($"File not found, configurationFilePath: {_path}"); + + + using (StreamReader file = File.OpenText(_path)) + using (JsonTextReader reader = new JsonTextReader(file)) + { + var jsonConfiguration = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.Linq.JToken.ReadFrom(reader); + + _connectionString = jsonConfiguration.SelectToken($"ConnectionStrings.{_configKey}")?.ToString(); + } + } + public void Build() + { + var repository = new ConfigurationRepository(_connectionString); + var configs = repository.GetActiveConfigurations(); + + var pairs = configs.Select(x => new KeyValuePair(x.Key, JToken.Parse(x.Value.ToString()))); + + _configuration = Common.KeyValueToJson.Convert(pairs); + + } + + public Newtonsoft.Json.Linq.JObject Configuration() + { + return _configuration; + } + } +} diff --git a/Core/Configurations/SmartConfiguration/AppConfiguration.cs b/Core/Configurations/SmartConfiguration/AppConfiguration.cs deleted file mode 100644 index 0dc9b98..0000000 --- a/Core/Configurations/SmartConfiguration/AppConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Core.Configurations.SmartConfiguration; -public class AppConfiguration -{ - public long Id { get; set; } - public string Key { get; set; } - public string Value { get; set; } - public string Label { get; set; } - public string ContentType { get; set; } - public DateTime? ValidFrom { get; set; } - public DateTime? ExpiresAt { get; set; } - public DateTime? CreatedAt { get; set; } - public DateTime? ModifiedAt { get; set; } - public Guid? Etag { get; set; } -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/ConfigurationExtensions.cs b/Core/Configurations/SmartConfiguration/ConfigurationExtensions.cs deleted file mode 100644 index 8b4732a..0000000 --- a/Core/Configurations/SmartConfiguration/ConfigurationExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Extensions.Configuration; - -namespace Core.Configurations.SmartConfiguration -{ - public static class ConfigurationExtensions - { - public static T Get(this IConfigurationSection section) where T : class - { - if (section is JsonConfigurationSection jsonSection) - { - var token = jsonSection.GetToken(); - return token?.ToObject(); - } - throw new InvalidOperationException("Section is not a JsonConfigurationSection"); - } - - public static T GetValue(this IConfigurationSection section, string key) - { - if (section is JsonConfigurationSection jsonSection) - { - var token = jsonSection.GetToken().SelectToken(key.Replace(":", ".")); - return token.ToObject(); - } - throw new InvalidOperationException("Section is not a JsonConfigurationSection"); - } - } -} diff --git a/Core/Configurations/SmartConfiguration/ConfigurationRepository.cs b/Core/Configurations/SmartConfiguration/ConfigurationRepository.cs deleted file mode 100644 index 8bd78a9..0000000 --- a/Core/Configurations/SmartConfiguration/ConfigurationRepository.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Data; -using Insight.Database; - -namespace Core.Configurations.SmartConfiguration; -public class ConfigurationRepository : IConfigurationRepository -{ - private readonly IDbConnection _connection; - - public ConfigurationRepository(IDbConnection connection) - { - _connection = connection; - } - - public async Task> GetActiveConfigurations() - { - const string sql = @" - SELECT id, key, value, label, content_type, valid_from, expires_at, created_at, modified_at, etag - FROM prod.app_configuration - WHERE (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP) - AND (valid_from IS NULL OR valid_from < CURRENT_TIMESTAMP)"; - - return await _connection.QueryAsync(sql); - } -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/IConfigurationRepository.cs b/Core/Configurations/SmartConfiguration/IConfigurationRepository.cs deleted file mode 100644 index 0bd3d14..0000000 --- a/Core/Configurations/SmartConfiguration/IConfigurationRepository.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Core.Configurations.SmartConfiguration; -public interface IConfigurationRepository -{ - Task> GetActiveConfigurations(); -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/JsonConfiguration.cs b/Core/Configurations/SmartConfiguration/JsonConfiguration.cs deleted file mode 100644 index 1659475..0000000 --- a/Core/Configurations/SmartConfiguration/JsonConfiguration.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Newtonsoft.Json.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Primitives; -using System.Data; - -namespace Core.Configurations.SmartConfiguration; -public class JsonConfiguration : Microsoft.Extensions.Configuration.IConfiguration -{ - private readonly JObject _data; - - public JsonConfiguration(JObject data) - { - _data = data; - } - - public string this[string key] - { - get => _data.SelectToken(key.Replace(":", "."))?.ToString(); - set => throw new NotImplementedException(); - } - - public Microsoft.Extensions.Configuration.IConfigurationSection GetSection(string key) => null; - //new JsonConfigurationSection(_data, key); - - public IEnumerable GetChildren() => - _data.Properties().Select(p => new JsonConfigurationSection(_data, p.Name)); - - public IChangeToken GetReloadToken() => throw new NotImplementedException(); - - IEnumerable IConfiguration.GetChildren() - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/JsonConfigurationSection.cs b/Core/Configurations/SmartConfiguration/JsonConfigurationSection.cs deleted file mode 100644 index f7f1583..0000000 --- a/Core/Configurations/SmartConfiguration/JsonConfigurationSection.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Newtonsoft.Json.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Primitives; - -namespace Core.Configurations.SmartConfiguration -{ - public class JsonConfigurationSection : IConfigurationSection - { - private readonly JObject _data; - private readonly string _path; - private readonly string _normalizedPath; - - public JsonConfigurationSection(JObject data, string path) - { - _data = data ?? throw new ArgumentNullException(nameof(data)); - _path = path ?? throw new ArgumentNullException(nameof(path)); - _normalizedPath = NormalizePath(_path); - } - - public string this[string key] - { - get - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); - - var token = _data.SelectToken($"{_normalizedPath}.{NormalizePath(key)}"); - return token?.ToString(); - } - set => throw new NotImplementedException("Setting values is not supported."); - } - - public string Key => _path.Split(':').Last(); - public string Path => _path; - - public string Value - { - get - { - var token = _data.SelectToken(_normalizedPath); - return token?.ToString(); - } - set => throw new NotImplementedException("Setting values is not supported."); - } - - public Microsoft.Extensions.Configuration.IConfigurationSection GetSection(string key) - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); - - return null;// new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? key : $"{_path}:{key}"); - } - - public JToken GetToken() => _data.SelectToken(_normalizedPath); - - public IEnumerable GetChildren() - { - var token = _data.SelectToken(_normalizedPath); - if (token is JObject obj) - { - return obj.Properties() - .Select(p => new JsonConfigurationSection(_data, string.IsNullOrEmpty(_path) ? p.Name : $"{_path}:{p.Name}")); - } - return Enumerable.Empty(); - } - - public T Get() where T : class - { - var token = _data.SelectToken(_normalizedPath); - return token?.ToObject(); - } - - public IChangeToken GetReloadToken() => new ConfigurationReloadToken(); - - private static string NormalizePath(string path) - { - return path?.Replace(":", ".", StringComparison.Ordinal) ?? string.Empty; - } - } -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/KeyValueConfigurationBuilder.cs b/Core/Configurations/SmartConfiguration/KeyValueConfigurationBuilder.cs deleted file mode 100644 index 1ce921c..0000000 --- a/Core/Configurations/SmartConfiguration/KeyValueConfigurationBuilder.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Microsoft.Extensions.Configuration; - -namespace Core.Configurations.SmartConfiguration -{ - public class KeyValueConfigurationBuilder - { - private readonly IConfigurationRepository _repository; - private readonly JObject _rootObject = new(); - private IConfiguration _configuration; - private readonly object _configurationLock = new(); - - public KeyValueConfigurationBuilder(IConfigurationRepository repository) - { - _repository = repository ?? throw new ArgumentNullException(nameof(repository)); - } - - /// - /// Loads configurations from the repository and builds the configuration tree. - /// - public async Task LoadConfiguration() - { - try - { - var configurations = await _repository.GetActiveConfigurations(); - foreach (var config in configurations) - AddKeyValue(config.Key, config.Value); - - } - catch (Exception ex) - { - // Log the exception or handle it as needed - throw new InvalidOperationException("Failed to load configurations.", ex); - } - } - - /// - /// Adds a key-value pair to the configuration tree. - /// - /// The key to add. - /// The JSON value to add. - public void AddKeyValue(string key, string jsonValue) - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); - if (string.IsNullOrEmpty(jsonValue)) - throw new ArgumentNullException(nameof(jsonValue)); - - try - { - var valueObject = JsonConvert.DeserializeObject(jsonValue); - var parts = key.Split(':'); - - JObject current = _rootObject; - for (int i = 0; i < parts.Length - 1; i++) - { - var part = parts[i]; - if (!current.ContainsKey(part)) - { - current[part] = new JObject(); - } - current = (JObject)current[part]; - } - - current[parts[^1]] = valueObject; - } - catch (JsonException ex) - { - throw new ArgumentException("Invalid JSON value.", nameof(jsonValue), ex); - } - } - /// - /// Builds the configuration instance. - /// - /// The built instance. - public IConfiguration Build() - { - _configuration = new JsonConfiguration(_rootObject); - return _configuration; - } - } -} \ No newline at end of file diff --git a/Core/Configurations/SmartConfiguration/SmartConfigManager.cs b/Core/Configurations/SmartConfiguration/SmartConfigManager.cs deleted file mode 100644 index ac27240..0000000 --- a/Core/Configurations/SmartConfiguration/SmartConfigManager.cs +++ /dev/null @@ -1,48 +0,0 @@ - -using Microsoft.Extensions.Configuration; - -namespace Core.Configurations -{ - public class SmartConfigManager - { - private static IConfigurationBuilder _configurationBuilder; - - /// - /// This AppConfigBuilder assumes that AppConfigEndpoint and AppConfigLabelFilter are configured as Settings on Azure for the Application that needs them. - /// AppConfigEndpoint would look like this: Endpoint=https://config-dec-test.azconfig.io;Id=0-l9-s0:foo;Secret=somesecret/bar - /// - /// - /// Path relative to the base path stored in Microsoft.Extensions.Configuration.IConfigurationBuilder.Properties of builder. - /// - public static IConfigurationBuilder AddSmartConfigBuilder(string localSettingsFile) - { - if (_configurationBuilder == null) - { - var envConfiguration = new Microsoft.Extensions.Configuration.ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - - var appConfigEndpoint = envConfiguration["AppConfigEndpoint"]; - var appConfigLabel = envConfiguration["AppConfigLabelFilter"]; - - _configurationBuilder = new Microsoft.Extensions.Configuration.ConfigurationBuilder(); - if (!string.IsNullOrEmpty(appConfigEndpoint)) - { - _configurationBuilder - .AddAzureAppConfiguration(options => - { - options.Connect(appConfigEndpoint); - options.Select(keyFilter: "*", labelFilter: appConfigLabel); - }) - .AddEnvironmentVariables(); - } - else - { - _configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()); - _configurationBuilder.AddJsonFile(localSettingsFile, optional: false); - } - } - return _configurationBuilder; - } - } -} diff --git a/Core/Core.csproj b/Core/Core.csproj index 0461ea9..cec383b 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -1,22 +1,23 @@  - - net8.0 - enable - + + net8.0 + enable + - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/Database/Common/Validations.cs b/Database/Common/Validations.cs new file mode 100644 index 0000000..5145b04 --- /dev/null +++ b/Database/Common/Validations.cs @@ -0,0 +1,13 @@ +using System.Text.RegularExpressions; + +namespace Database.Common +{ + internal class Validations + { + public static bool IsValidSchemaName(string schema) + { + return !string.IsNullOrEmpty(schema) && Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"); + } + + } +} diff --git a/Database/ConfigurationManagementSystem/ConfigurationDatabaseSetup.cs b/Database/ConfigurationManagementSystem/ConfigurationDatabaseSetup.cs index 0dd449d..5cb6070 100644 --- a/Database/ConfigurationManagementSystem/ConfigurationDatabaseSetup.cs +++ b/Database/ConfigurationManagementSystem/ConfigurationDatabaseSetup.cs @@ -1,7 +1,7 @@ using Insight.Database; using System.Data; -namespace Database.AppConfigurationSystem; +namespace Database.ConfigurationManagementSystem; public class ConfigurationDatabaseSetup { diff --git a/Database/IdentitySystem/Setup.cs b/Database/IdentitySystem/Setup.cs index a910494..42afa57 100644 --- a/Database/IdentitySystem/Setup.cs +++ b/Database/IdentitySystem/Setup.cs @@ -1,25 +1,66 @@ -using Insight.Database; +using Database.Common; +using Insight.Database; using System; using System.Data; using System.Threading.Tasks; -namespace Database.Tenants +namespace Database.IdentitySystem { public class DbSetup { - private readonly IDbConnection _db; + readonly IDbConnection _db; + IDbTransaction _transaction = null; + string _schema; public DbSetup(IDbConnection db) { _db = db ?? throw new ArgumentNullException(nameof(db)); } + /// + /// Creates the system tables in the specified schema within a transaction. + /// + /// The schema name where the tables will be created. + 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); + } + + /// /// Creates the users table in the ptmain schema. /// - public void CreateUsersTable() + public async Task CreateUsersTable() { - ExecuteInTransaction(@" + var sql = @" CREATE TABLE IF NOT EXISTS ptmain.users ( id SERIAL PRIMARY KEY, email VARCHAR(256) NOT NULL UNIQUE, @@ -32,85 +73,75 @@ namespace Database.Tenants 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); + } /// /// Creates the tenants table in the ptmain schema. /// - public void CreateTenantsTable() + public async Task CreateTenantsTable() { - ExecuteInTransaction(@" + 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); + } /// /// Creates the user_tenants table in the ptmain schema. /// - public void CreateUserTenantsTable() + public async Task CreateUserTenantsTable() { - ExecuteInTransaction(@" + 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); + } /// /// Sets up Row Level Security (RLS) for the tenants and user_tenants tables. /// - public void SetupRLS() + public async Task SetupRLS() { - ExecuteInTransaction( - "ALTER TABLE ptmain.tenants ENABLE ROW LEVEL SECURITY;", - "ALTER TABLE ptmain.user_tenants ENABLE ROW LEVEL SECURITY;", - "DROP POLICY IF EXISTS tenant_access ON ptmain.tenants;", - @" - CREATE POLICY tenant_access ON ptmain.tenants - USING (id IN ( - SELECT tenant_id - FROM ptmain.user_tenants - WHERE user_id = current_setting('app.user_id', TRUE)::INTEGER - ));", - "DROP POLICY IF EXISTS user_tenant_access ON ptmain.user_tenants;", - @" - CREATE POLICY user_tenant_access ON ptmain.user_tenants - USING (user_id = current_setting('app.user_id', TRUE)::INTEGER);" - ); - } - - /// - /// Executes one or more SQL commands within a transaction. - /// - /// The SQL commands to execute. - private void ExecuteInTransaction(params string[] sqlCommands) - { - if (_db.State != ConnectionState.Open) - _db.Open(); - - using var transaction = _db.BeginTransaction(); - try - { - foreach (var sql in sqlCommands) + var sql = new[] { - _db.Execute(sql, transaction: transaction); - } - transaction.Commit(); - } - catch (Exception ex) + "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) { - transaction.Rollback(); - throw new InvalidOperationException("Failed to execute SQL commands in transaction.", ex); + await ExecuteSqlAsync(statement).ConfigureAwait(false); } } + + } } \ No newline at end of file diff --git a/Database/IdentitySystem/UserService.cs b/Database/IdentitySystem/UserService.cs index 3c2b5e9..2008363 100644 --- a/Database/IdentitySystem/UserService.cs +++ b/Database/IdentitySystem/UserService.cs @@ -2,7 +2,7 @@ using Insight.Database; using System.Data; -namespace Database.Identity +namespace Database.IdentitySystem { public class UserService { diff --git a/Database/RolesPermissionSystem/Setup.cs b/Database/RolesPermissionSystem/Setup.cs index eb75a40..453d8b6 100644 --- a/Database/RolesPermissionSystem/Setup.cs +++ b/Database/RolesPermissionSystem/Setup.cs @@ -1,8 +1,9 @@ using System.Data; using System.Text.RegularExpressions; +using Database.Common; using Insight.Database; -namespace Database.Tenants +namespace Database.RolesPermissionSystem { public class Setup { @@ -19,7 +20,7 @@ namespace Database.Tenants /// The schema name where the tables will be created. public async Task CreateSystem(string schema) { - if (!IsValidSchemaName(schema)) + if (!Validations.IsValidSchemaName(schema)) throw new ArgumentException("Invalid schema name", nameof(schema)); using (var transaction = _db.BeginTransaction()) @@ -29,7 +30,7 @@ namespace Database.Tenants await CreateRolesTable(schema, transaction).ConfigureAwait(false); await CreatePermissionsTable(schema, transaction).ConfigureAwait(false); await CreatePermissionTypesTable(schema, transaction).ConfigureAwait(false); - await CreateRolePermissionsTable(schema, transaction).ConfigureAwait(false); + await CreateRolePermissionsTable(schema, transaction).ConfigureAwait(false); transaction.Commit(); } @@ -41,10 +42,6 @@ namespace Database.Tenants } } - private bool IsValidSchemaName(string schema) - { - return !string.IsNullOrEmpty(schema) && Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"); - } private async Task ExecuteSqlAsync(string sql, IDbTransaction transaction) { diff --git a/SetupInfrastructure/SetupInfrastructure.csproj b/SetupInfrastructure/SetupInfrastructure.csproj index e06d113..ac3a41d 100644 --- a/SetupInfrastructure/SetupInfrastructure.csproj +++ b/SetupInfrastructure/SetupInfrastructure.csproj @@ -12,7 +12,7 @@ - + Always diff --git a/SetupInfrastructure/appsettings.json b/SetupInfrastructure/appconfiguration.json similarity index 91% rename from SetupInfrastructure/appsettings.json rename to SetupInfrastructure/appconfiguration.json index bd7624b..0e3cd9a 100644 --- a/SetupInfrastructure/appsettings.json +++ b/SetupInfrastructure/appconfiguration.json @@ -1,7 +1,7 @@ { "AllowedHosts": "*", "ConnectionStrings": { - "ptdb": "Host=192.168.1.57;Port=5432;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/" diff --git a/SqlManagement/.dbeaver/.project-metadata.json.bak b/SqlManagement/.dbeaver/.project-metadata.json.bak index c517be1..a63e45b 100644 --- a/SqlManagement/.dbeaver/.project-metadata.json.bak +++ b/SqlManagement/.dbeaver/.project-metadata.json.bak @@ -1 +1 @@ -{"resources":{"Scripts/Script-2.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}} \ No newline at end of file +{"resources":{"Scripts/Script-1.sql":{"default-datasource":"postgres-jdbc-19484872d85-cd2a4a40116e706","default-catalog":"ptdb01","default-schema":"ptmain"},"Scripts/Script-2.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"},"Scripts/grant-privileges.sql":{"default-datasource":"postgres-jdbc-1948450a8b4-5fc9eec404e65c44","default-catalog":"ptdb01"}}} \ No newline at end of file diff --git a/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs new file mode 100644 index 0000000..ab48f42 --- /dev/null +++ b/Tests/ConfigurationTests/JsonConfigurationProviderTests.cs @@ -0,0 +1,119 @@ +using Core.Configurations.SmartConfig; +using Core.Configurations; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using Core.Configurations.JsonConfigProvider; +using Autofac; +using System.Data; +using Insight.Database; +using Npgsql; + +namespace Tests.ConfigurationTests +{ + [TestClass] + public class JsonConfigurationProviderTests : TestFixture + { + [TestMethod] + public void GetSection_ShouldReturnCorrectFeatureSection() + { + // Arrange + var expectedJObject = JObject.Parse(@"{ + 'Enabled': true, + 'RolloutPercentage': 25, + 'AllowedUserGroups': ['beta'] + }") as JToken; + + var builder = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .Build(); + + // Act + var section = builder.GetSection("Feature"); + + // Assert + section.Should().NotBeNull(); + section.Value.Should().BeEquivalentTo(expectedJObject); + } + + [TestMethod] + public void Get_ShouldReturnCorrectFeatureObject() + { + // Arrange + var expectedFeature = new Feature + { + Enabled = true, + RolloutPercentage = 25, + AllowedUserGroups = new() { "beta" } + }; + + var builder = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .Build(); + + // Act + var actualFeature = builder.GetSection("Feature").ToObject(); + var actualFeatureObsoleted = builder.GetSection("Feature").Get(); + // Assert + actualFeature.Should().BeEquivalentTo(expectedFeature); + actualFeatureObsoleted.Should().BeEquivalentTo(expectedFeature); + + } + [TestMethod] + public void Get_ShouldReturnCorrectValueAsString() + { + // Arrange + var expectedFeature = "123"; + + var builder = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .Build(); + + // Act + var actualFeature = builder.GetSection("AnotherSetting").Get("Thresholds:High"); + + // Assert + actualFeature.Should().BeEquivalentTo(expectedFeature); + } + [TestMethod] + public void Get_ShouldReturnCorrectValueAsInt() + { + // Arrange + var expectedFeature = 22; + + var builder = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .Build(); + + // Act + var actualFeature = builder.GetSection("AnotherSetting:Temperature").Get("Indoor:Max:Limit"); + + // Assert + actualFeature.Should().Be(expectedFeature); + } + [TestMethod] + public void Get_ShouldReturnCorrectValueAsBool() + { + // Arrange + var expectedFeature = true; + + var configRoot = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .AddSmartConfig() + .Build(); + + // Act + var actualFeature = configRoot.Get("Database:UseSSL"); + + // Assert + actualFeature.Should().Be(expectedFeature); + } + } + + public class Feature + { + public bool Enabled { get; set; } + public int RolloutPercentage { get; set; } + public List AllowedUserGroups { get; set; } + } + +} \ No newline at end of file diff --git a/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs b/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs index 033aa2e..2793e65 100644 --- a/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs +++ b/Tests/ConfigurationTests/KeyValueJsonHandlingTests.cs @@ -1,139 +1,78 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Moq; using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Linq; -namespace Tests.ConfigurationTests +using Tests; +using Core.Configurations.SmartConfig; +using Core.Configurations.Common; + +namespace Tests.ConfigurationTests; + +[TestClass] +public class ConfigurationTests : TestFixture { - [TestClass] - public class KeyValueJsonHandlingTests : TestFixture - { + [TestInitialize] + public void Init() + { + } - [TestMethod] - public void FunMixedTypesTest() - { - var pairs = new List> - { - new("developer:coffeeLevel", 9001.5), - new("developer:bugsFixed", 42), - new("developer:awake", false), - new("compiler:errors:semicolon:0", "Missing ;"), - new("compiler:errors:semicolon:1", "Found ; but why?"), - new("computer:ram:status", "Out of memory"), - new("computer:cpu:temperature", 99.9), - new("friday:beer:count", 6), - new("friday:code:working", true), - new("friday:bugs:count", int.MaxValue), - new("real:object", JObject.Parse(@"{ - ""beer"": { - ""count"": 6 - }, - ""code"": { - ""working"": true - }, - ""bugs"": { - ""count"": 2147483647 - } - }")) - }; - + [TestMethod] + public void ConfigurationSettingsTest() + { + var pairs = new List> + { + new("Debug", true), + // Database konfiguration + new("Database:ConnectionString", "Server=db.example.com;Port=5432"), + new("Database:Timeout", 30), + new("Database:UseSSL", true), - var result = KeyValueToJson.Convert(pairs); + // Logging konfiguration med JObject + new("Logging:FileOptions", JObject.Parse(@"{ + 'Path': '/var/logs/app.log', + 'MaxSizeMB': 100, + 'RetentionDays': 7 + }")), - var expected = JObject.Parse(@"{ - 'developer': { - 'coffeeLevel': 9001.5, - 'bugsFixed': 42, - 'awake': false - }, - 'compiler': { - 'errors': { 'semicolon': ['Missing ;', 'Found ; but why?'] } - }, - 'computer': { - 'ram': { 'status': 'Out of memory' }, - 'cpu': { 'temperature': 99.9 } - }, - 'friday': { - 'beer': { 'count': 6 }, - 'code': { 'working': true }, - 'bugs': { 'count': 2147483647 } + // Feature flags med kompleks konfiguration + new("Features:Experimental", JObject.Parse(@"{ + 'Enabled': true, + 'RolloutPercentage': 25, + 'AllowedUserGroups': ['beta'] + }")), + + // API endpoints med array + new("API:Endpoints", "/api/users"), + new("API:Endpoints", "/api/products") + }; + + var result = KeyValueToJson.Convert(pairs); + + var expected = JObject.Parse(@"{ + 'Debug' : true, + 'Database': { + 'ConnectionString': 'Server=db.example.com;Port=5432', + 'Timeout': 30, + 'UseSSL': true + }, + 'Logging': { + 'FileOptions': { + 'Path': '/var/logs/app.log', + 'MaxSizeMB': 100, + 'RetentionDays': 7 } - }"); + }, + 'Features': { + 'Experimental': { + 'Enabled': true, + 'RolloutPercentage': 25, + 'AllowedUserGroups': ['beta'] + } + }, + 'API': { + 'Endpoints': ['/api/users', '/api/products'] + } + }"); - Assert.IsTrue(JToken.DeepEquals(expected, result)); - } - } - - -public static class KeyValueToJson - { - public static JObject Convert(List> pairs) - { - var root = new JObject(); - - foreach (var pair in pairs) - { - var keys = pair.Key.Split(':'); - var current = root; - - // Gennemgå hierarkiet og opret underobjekter, hvis de ikke eksisterer - for (int i = 0; i < keys.Length - 1; i++) - { - var key = keys[i]; - - if (current[key] == null) - { - current[key] = new JObject(); - } - - current = (JObject)current[key]; - } - - // Håndter den sidste nøgle og tilføj værdien - var lastKey = keys[keys.Length - 1]; - var value = ConvertValue(pair.Value); - - // Hvis den sidste nøgle allerede eksisterer, tilføj til en liste - if (current[lastKey] != null) - { - // Hvis den allerede er en liste, tilføj til listen - if (current[lastKey].Type == JTokenType.Array) - { - ((JArray)current[lastKey]).Add(value); - } - // Hvis den ikke er en liste, konverter til en liste - else - { - var existingValue = current[lastKey]; - current[lastKey] = new JArray { existingValue, value }; - } - } - // Ellers tilføj som en enkelt værdi - else - { - current[lastKey] = value; - } - } - - return root; - } - - private static JToken ConvertValue(object value) - { - // Konverter værdien til det korrekte JToken-format - return value switch - { - int i => new JValue(i), - double d => new JValue(d), - bool b => new JValue(b), - string s => new JValue(s), - _ => new JValue(value.ToString()) // Fallback for andre typer - }; - } - } -} + Assert.IsTrue(JToken.DeepEquals(expected, result)); + } +} \ No newline at end of file diff --git a/Tests/ConfigurationTests/SmartConfigProviderTests.cs b/Tests/ConfigurationTests/SmartConfigProviderTests.cs new file mode 100644 index 0000000..1b49d74 --- /dev/null +++ b/Tests/ConfigurationTests/SmartConfigProviderTests.cs @@ -0,0 +1,102 @@ +using Core.Configurations.SmartConfig; +using Core.Configurations; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using Core.Configurations.JsonConfigProvider; +using Autofac; +using System.Data; +using Insight.Database; +using Npgsql; + +namespace Tests.ConfigurationTests +{ + [TestClass] + public class SmartConfigProviderTests : TestFixture + { + + [TestMethod] + public void Get_ShouldReturnCorrectValueAsBool() + { + // Arrange + var expectedFeature = true; + + var config = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .AddSmartConfig() + .Build(); + + // Act + var actualFeature = config.Get("Database:UseSSL"); + + // Assert + actualFeature.Should().Be(expectedFeature); + } + [TestMethod] + public void Get_ShouldReturnCorrectValueWhenSelectingIntoValueRowInConfigTable() + { + // Arrange + var expectedFeature = 100; + + var builder = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .AddSmartConfig() + .Build(); + + // Act + var actualFeature = builder.GetSection("Logging:FileOptions").Get("MaxSizeMB"); + var withoutSectionThisAlsoWorks = builder.Get("Logging:FileOptions:MaxSizeMB"); + + // Assert + actualFeature.Should().Be(expectedFeature); + actualFeature.Should().Be(withoutSectionThisAlsoWorks); + + } + + [TestMethod] + public void GetPostgresSearchPath() + { + //ALTER USER sathumper SET search_path TO ptmain, public; + + var conn = Container.Resolve(); + + var result = conn.QuerySql("SHOW search_path;"); + using (var connw = new NpgsqlConnection(conn.ConnectionString + ";Password=3911")) + { + connw.Open(); + using (var cmd = new NpgsqlCommand("SHOW search_path; SELECT current_user;", connw)) + { + using (var reader = cmd.ExecuteReader()) + { + reader.Read(); + var r1 = $"Search path: {reader.GetString(0)}"; + reader.NextResult(); + reader.Read(); + var r2 = $"Current schema: {reader.GetString(0)}"; + } + } + + } + + + } + [TestMethod] + public void TryGetActiveConfigurations() + { + + const string sql = @" + SELECT id, ""key"", value, label, content_type, + valid_from, expires_at, created_at, modified_at, etag + FROM app_configuration + WHERE CURRENT_TIMESTAMP BETWEEN valid_from AND expires_at + OR (valid_from IS NULL AND expires_at IS NULL)"; + + var conn = Container.Resolve(); + + var result = conn.QuerySql(sql); + + } + } + + + +} \ No newline at end of file diff --git a/Tests/PostgresTests.cs b/Tests/PostgresTests.cs new file mode 100644 index 0000000..da4e9d7 --- /dev/null +++ b/Tests/PostgresTests.cs @@ -0,0 +1,50 @@ +using Autofac; +using System.Data; +using Insight.Database; + +namespace Tests +{ + [TestClass] + public class PostgresTests : TestFixture + { + [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"); + + } + + + [TestMethod] + public void TryTenantSetupService() + { + var conn = Container.Resolve(); + } + + [TestMethod] + public async Task TryDbSetup() + { + var conn = Container.Resolve(); + + var dbSetup = new Database.IdentitySystem.DbSetup(conn); + await dbSetup.CreateSystem("swp"); + } + + [TestMethod] + public void SetupPostgresql_LISTEN() + { + //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/TestConfigurationManagement.cs b/Tests/TestConfigurationManagement.cs deleted file mode 100644 index ecd44da..0000000 --- a/Tests/TestConfigurationManagement.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Tests; -using Microsoft.Extensions.Configuration; -using Core.Configurations.SmartConfiguration; - -namespace Configuration.Core.Tests; - -[TestClass] -public class ConfigurationTests : TestFixture -{ - private Mock _mockRepo; - private KeyValueConfigurationBuilder _builder; - - [TestInitialize] - public void Init() - { - _mockRepo = new Mock(); - _builder = new KeyValueConfigurationBuilder(_mockRepo.Object); - } - - - [TestMethod] - public async Task LoadConfiguration_WithValidData_BuildsCorrectly() - { - // Arrange - var configurations = new List - { - new AppConfiguration - { - Key = "Email:Templates:Welcome", - Value = @"{""subject"":""Welcome"",""sender"":""test@company.com""}", - ValidFrom = DateTime.UtcNow.AddDays(-1), - ExpiresAt = DateTime.UtcNow.AddDays(1) - } - }; - - _mockRepo.Setup(r => r.GetActiveConfigurations()) - .ReturnsAsync(configurations); - - // Act - await _builder.LoadConfiguration(); - var config = _builder.Build(); - - // Assert - Assert.IsNotNull(config); - Assert.AreEqual("Welcome", config["Email:Templates:Welcome:subject"]); - } - - [TestMethod] - public async Task LoadConfiguration_WithExpiredData_NotIncluded() - { - // Arrange - var configurations = new List - { - new AppConfiguration - { - Key = "Test:Key", - Value = @"{""value"":""test""}", - ValidFrom = DateTime.UtcNow.AddDays(1), - ExpiresAt = DateTime.UtcNow.AddDays(2) - } - }; - - _mockRepo.Setup(r => r.GetActiveConfigurations()) - .ReturnsAsync(new List()); - - // Act - await _builder.LoadConfiguration(); - var config = _builder.Build(); - - // Assert - Assert.IsNull(config["Test:Key:value"]); - } - - [TestMethod] - public void AddKeyValue_WithNestedKeys_BuildsCorrectHierarchy() - { - // Arrange - var key = "Level1:Level2:Level3"; - var value = @"{""setting"":""value""}"; - - // Act - _builder.AddKeyValue(key, value); - var config = _builder.Build(); - - // Assert - Assert.AreEqual("value", config["Level1:Level2:Level3:setting"]); - } - - [TestMethod] - public async Task GetSection_ReturnsCorrectSubSection() - { - // Arrange - var configurations = new List - { - new AppConfiguration - { - Key = "Parent:Child", - Value = @"{""setting"":""value""}", - ValidFrom = DateTime.UtcNow.AddDays(-1) - } - }; - - _mockRepo.Setup(r => r.GetActiveConfigurations()) - .ReturnsAsync(configurations); - - // Act - await _builder.LoadConfiguration(); - var config = _builder.Build(); - var section = config.GetSection("Parent"); - - // Assert - Assert.AreEqual("value", section["Child:setting"]); - } - - [TestMethod] - public void Configuration_ShouldMapToTypedClass() - { - // Arrange - var configData = new JObject - { - ["Email"] = new JObject - { - ["Templates"] = new JObject - { - ["Welcome"] = new JObject - { - ["subject"] = "Welcome to our service", - ["template"] = "welcome-template.html", - ["sender"] = "noreply@test.com", - ["settings"] = new JObject - { - ["isEnabled"] = true, - ["retryCount"] = 3 - } - } - } - } - }; - - Microsoft.Extensions.Configuration.IConfiguration configuration = null;// new JsonConfiguration(configData, new Microsoft.Extensions.Configuration.ConfigurationReloadToken()); - - // Act - var welcomeConfig = configuration.GetSection("Email:Templates:Welcome").Get(); - - // Assert - Assert.IsNotNull(welcomeConfig); - Assert.AreEqual("Welcome to our service", welcomeConfig.Subject); - Assert.AreEqual("welcome-template.html", welcomeConfig.Template); - Assert.AreEqual("noreply@test.com", welcomeConfig.Sender); - Assert.IsTrue(welcomeConfig.Settings.IsEnabled); - Assert.AreEqual(3, welcomeConfig.Settings.RetryCount); - } - - public class WelcomeEmailConfig - { - public string Subject { get; set; } - public string Template { get; set; } - public string Sender { get; set; } - public EmailSettings Settings { get; set; } - } - - public class EmailSettings - { - public bool IsEnabled { get; set; } - public int RetryCount { get; set; } - } -} \ No newline at end of file diff --git a/Tests/TestFixture.cs b/Tests/TestFixture.cs index dde7f2b..8b0d0b5 100644 --- a/Tests/TestFixture.cs +++ b/Tests/TestFixture.cs @@ -1,97 +1,84 @@ using System; using System.Diagnostics; using Autofac; +using Core.Configurations; using Core.ModuleRegistry; using Microsoft.ApplicationInsights; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; - +using Core.Configurations.JsonConfigProvider; namespace Tests { - /// - /// Act as base class for tests. Avoids duplication of test setup code - /// - [TestClass] - public abstract partial class TestFixture - { - protected IContainer Container { get; private set; } - protected ContainerBuilder ContainerBuilder { get; private set; } + /// + /// Act as base class for tests. Avoids duplication of test setup code + /// + [TestClass] + public abstract partial class TestFixture + { + protected IContainer Container { get; private set; } + protected ContainerBuilder ContainerBuilder { get; private set; } + + public virtual IConfigurationRoot Configuration() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appconfiguration.dev.json") + .Build(); + + return configuration; + } + + /// + /// Should not be overriden. Rather override PreArrangeAll to setup data needed for a test class. + /// Override PrebuildContainer with a method that does nothing to prevent early build of IOC container + /// + [TestInitialize] + public void Setup() + { + CreateContainerBuilder(); + Container = ContainerBuilder.Build(); + Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider(); + } - [AssemblyInitialize] - public static void AssemblySetup(TestContext tc) - { - Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); + protected virtual void CreateContainerBuilder() + { + IConfigurationRoot configuration = Configuration(); - var envConfiguration = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); + var builder = new ContainerBuilder(); + builder.RegisterInstance(new LoggerFactory()) + .As(); - } + builder.RegisterGeneric(typeof(Logger<>)) + .As(typeof(ILogger<>)) + .SingleInstance(); - public virtual IConfigurationRoot Configuration() - { + builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule + { + ConnectionString = configuration.GetConnectionString("DefaultConnection") + }); - IConfigurationBuilder configBuilder = Core.Configurations.AzureConfigurationManager.AppConfigBuilder("appsettings.dev.json"); - IConfigurationRoot configuration = configBuilder.Build(); - - return configuration; - } - - /// - /// Should not be overriden. Rather override PreArrangeAll to setup data needed for a test class. - /// Override PrebuildContainer with a method that does nothing to prevent early build of IOC container - /// - [TestInitialize] - public void Setup() - { - CreateContainerBuilder(); - Container = ContainerBuilder.Build(); - Insight.Database.Providers.PostgreSQL.PostgreSQLInsightDbProvider.RegisterProvider(); - } - - - protected virtual void CreateContainerBuilder() - { - IConfigurationRoot configuration = Configuration(); - - var builder = new ContainerBuilder(); - builder.RegisterInstance(new LoggerFactory()) - .As(); - - builder.RegisterGeneric(typeof(Logger<>)) - .As(typeof(ILogger<>)) - .SingleInstance(); - - builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule - { - ConnectionString = configuration.GetConnectionString("ptdb") - }); - - builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule - { - TelemetryConfig = configuration.GetSection("ApplicationInsights").Get() - }); + builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule + { + TelemetryConfig = configuration.GetSection("ApplicationInsights").ToObject() + }); - ContainerBuilder = builder; - } + ContainerBuilder = builder; + } - [TestCleanup] - public void CleanUp() - { - Trace.Flush(); - var telemetryClient = Container.Resolve(); - telemetryClient.Flush(); + [TestCleanup] + public void CleanUp() + { + Trace.Flush(); + var telemetryClient = Container.Resolve(); + telemetryClient.Flush(); - if (Container != null) - { - Container.Dispose(); - Container = null; - } - } + if (Container != null) + { + Container.Dispose(); + Container = null; + } + } - } + } } \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index d9a077c..cdd90c6 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -10,6 +10,7 @@ + @@ -26,7 +27,7 @@ - + Always diff --git a/Tests/UnitTest1.cs b/Tests/UnitTest1.cs deleted file mode 100644 index d86ffae..0000000 --- a/Tests/UnitTest1.cs +++ /dev/null @@ -1,56 +0,0 @@ -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 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"); - - //var sql = "SELECT * FROM swp.foo"; - //var customers = conn.Query(sql, commandType:CommandType.Text); - } - - - [TestMethod] - public void TryTenantSetupService() - { - var conn = Container.Resolve(); - } - - [TestMethod] - public async Task TryDbSetup() - { - var conn = Container.Resolve(); - - var dbSetup = new Database.AppConfigurationSystem.ConfigurationDatabaseSetup(conn); - //await dbSetup..CreateDatabaseWithSchema("swp"); - } - - - - [TestMethod] - public void SetupPostgresql_LISTEN() - { - 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/appconfiguration.dev.json b/Tests/appconfiguration.dev.json new file mode 100644 index 0000000..6c52fa4 --- /dev/null +++ b/Tests/appconfiguration.dev.json @@ -0,0 +1,32 @@ +{ + "AllowedHosts": "*", + "ConnectionStrings": { + "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/" + }, + "Feature": { + "Enabled": true, + "RolloutPercentage": 25, + "AllowedUserGroups": [ "beta" ] + }, + "AnotherSetting": { + + "Thresholds": { + "High": "123", + "Low": "-1" + }, + "Temperature": { + + "Indoor": { + "Max": { "Limit": 22 }, + "Min": { "Limit": 18 } + }, + "Outdoor": { + "Max": { "Limit": 12 }, + "Min": { "Limit": 9 } + } + } + } +} \ No newline at end of file diff --git a/Tests/appsettings.dev.json b/Tests/appsettings.dev.json deleted file mode 100644 index bd7624b..0000000 --- a/Tests/appsettings.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "AllowedHosts": "*", - "ConnectionStrings": { - "ptdb": "Host=192.168.1.57;Port=5432;Database=ptdb01;User Id=postgres;Password=3911;" - }, - "ApplicationInsights": { - "ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/" - } -} \ No newline at end of file