Initial commit: SWP.Core enterprise framework with multi-tenant architecture, configuration management, security, telemetry and comprehensive test suite

This commit is contained in:
Janus C. H. Knudsen 2025-08-02 22:16:39 +02:00
commit 5275a75502
87 changed files with 6140 additions and 0 deletions

View file

@ -0,0 +1,14 @@
namespace SWP.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; }
}

View file

@ -0,0 +1,7 @@
namespace SWP.Core.Configurations.SmartConfigProvider;
public interface IConfigurationRepository
{
string ConnectionString { get; set; }
IEnumerable<AppConfiguration> GetActiveConfigurations();
}

View file

@ -0,0 +1,35 @@
using System.Data;
using Insight.Database;
using SWP.Core.Configurations.SmartConfigProvider;
namespace SWP.Core.Configurations.SmartConfigProvider.Repositories;
public class PostgresConfigurationRepository : IConfigurationRepository
{
private IDbConnection _connection;
public string ConnectionString { get; set; }
public PostgresConfigurationRepository(string connectionString)
{
_connection = new Npgsql.NpgsqlConnection(connectionString);
}
public PostgresConfigurationRepository()
{
}
public IEnumerable<AppConfiguration> GetActiveConfigurations()
{
_connection ??= new Npgsql.NpgsqlConnection(ConnectionString);
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<AppConfiguration>(sql);
}
}

View file

@ -0,0 +1,33 @@
namespace SWP.Core.Configurations.SmartConfigProvider
{
/// <summary>
/// Extension methods for adding smart configuration providers to IConfigurationBuilder.
/// </summary>
public static class SmartConfigExtension
{
/// <summary>
/// Adds a smart configuration provider using a connection string from appsettings.
/// </summary>
/// <param name="builder">The configuration builder to add to</param>
/// <param name="configKey">The key to find the connection string in the ConnectionStrings section. Defaults to "DefaultConnection"</param>
/// <param name="path">Optional path to configuration file if different from default appsettings location</param>
/// <returns>The configuration builder</returns>
public static IConfigurationBuilder AddSmartConfig(this IConfigurationBuilder builder, string configKey = "DefaultConnection", string path = null)
{
return builder.AddProvider(new SmartConfigProvider(builder, configKey, path));
}
/// <summary>
/// Adds a smart configuration provider with custom configuration options.
/// </summary>
/// <param name="builder">The configuration builder to add to</param>
/// <param name="setupAction">Action to configure the smart configuration options</param>
/// <returns>The configuration builder</returns>
public static IConfigurationBuilder AddSmartConfig(this IConfigurationBuilder builder, Action<SmartConfigOptions> setupAction)
{
var options = new SmartConfigOptions();
setupAction(options);
return builder.AddProvider(new SmartConfigProvider(builder, options));
}
}
}

View file

@ -0,0 +1,45 @@
namespace SWP.Core.Configurations.SmartConfigProvider
{
/// <summary>
/// Configuration options for setting up smart configuration providers.
/// Provides fluent configuration methods for specifying the repository type and settings.
/// </summary>
public class SmartConfigOptions
{
private IConfigurationRepository _repository;
internal string _configKey;
/// <summary>
/// Configures the smart configuration to use PostgreSQL as the configuration store.
/// </summary>
/// <param name="configKey">The configuration key used to find the connection string</param>
/// <returns>The configuration options instance for method chaining</returns>
public SmartConfigOptions UsePostgres(string configKey)
{
_configKey = configKey;
_repository = new Repositories.PostgresConfigurationRepository();
return this;
}
/// <summary>
/// Configures the smart configuration to use SQL Server as the configuration store.
/// </summary>
/// <returns>The configuration options instance for method chaining</returns>
/// <exception cref="NotImplementedException">This feature is not yet implemented</exception>
public SmartConfigOptions UseSqlServer()
{
throw new NotImplementedException();
}
/// <summary>
/// Configures the smart configuration to use a custom configuration repository.
/// </summary>
/// <param name="repository">The configuration repository to use</param>
/// <returns>The configuration options instance for method chaining</returns>
public SmartConfigOptions UseRepository(IConfigurationRepository repository)
{
_repository = repository;
return this;
}
internal IConfigurationRepository GetRepository() => _repository;
}
}

View file

@ -0,0 +1,85 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SWP.Core.Exceptions;
using SWP.Core.Configurations.JsonConfigProvider;
namespace SWP.Core.Configurations.SmartConfigProvider
{
/// <summary>
/// Configuration provider that loads configuration from a smart configuration source (e.g. database).
/// The provider reads connection details from a JSON file and uses them to connect to a configuration repository.
/// </summary>
/// <remarks>
/// The provider supports multiple initialization methods:
/// - Through SmartConfigOptions for flexible repository configuration
/// - Through direct configuration key and file path
/// Configuration is loaded from the repository during Build() and converted to a JSON structure.
/// </remarks>
public class SmartConfigProvider : IConfigurationProvider
{
string _configKey;
string _connectionString;
string _path;
IConfigurationBuilder _builder;
JObject _configuration;
SmartConfigOptions _smartConfigOptions;
public SmartConfigProvider() { }
public SmartConfigProvider(IConfigurationBuilder builder, SmartConfigOptions smartConfigOptions)
{
_builder = builder;
_smartConfigOptions = smartConfigOptions;
_configKey = smartConfigOptions._configKey;
SetConnectionString();
}
public SmartConfigProvider(IConfigurationBuilder builder, string configKey, string configurationFilePath)
{
_builder = builder;
_configKey = configKey;
_path = configurationFilePath;
SetConnectionString();
}
void SetConnectionString()
{
var carrier = _builder.ConfigurationProviders.OfType<IHasConfigurationFilePath>().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 = (JObject)JToken.ReadFrom(reader);
_connectionString = jsonConfiguration.SelectToken($"ConnectionStrings.{_configKey}")?.ToString();
}
}
public void Build()
{
var repository = _smartConfigOptions.GetRepository();
repository.ConnectionString = _connectionString;
var configs = repository.GetActiveConfigurations();
var pairs = configs.Select(x => new KeyValuePair<string, JToken>(x.Key, JToken.Parse(x.Value.ToString())));
_configuration = Common.KeyValueToJson.Convert(pairs);
}
public JObject Configuration()
{
return _configuration;
}
}
}