Compare commits

..

1 Commits

Author SHA1 Message Date
khwezi f44acd8c82 Merge commit '3656223b5f596095383a95825e553b776a50145a' 2026-05-20 19:15:01 +00:00
18 changed files with 54 additions and 191 deletions
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -1,7 +1,6 @@
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Notifications;
using LiteCharms.Features.Shop.Notifications.Events;
using static LiteCharms.Features.Extensions.Email;
namespace LiteCharms.Features.Tests;
@@ -21,8 +20,8 @@ public class NotificationsFeatureTests(CommonFixture fixture, ITestOutputHelper
Priority = Shop.Priorities.Medium,
Sender = "xUnit Test",
SenderAddress = "khwezi@mngoma.africa",
Recipient = $"{ShopEmailFromName} [Test]",
RecipientAddress = ShopEmailFromAddress,
Recipient = $"{Email.Extensions.Constants.ShopEmailFromName} [Test]",
RecipientAddress = Email.Extensions.Constants.ShopEmailFromAddress,
Subject = "Test Message",
Message = "This is an automation test",
IsHtml = false,
@@ -1,19 +0,0 @@
using LiteCharms.Features.Shop.Products;
namespace LiteCharms.Features.Tests;
public class ProductsFeatureTests(CommonFixture fixture, ITestOutputHelper output) : IClassFixture<CommonFixture>
{
[Fact]
public async Task GetProductsAsync_ReturnsProducts()
{
var productService = fixture.Services.GetRequiredService<ProductService>();
var result = await productService.GetProductsAsync();
Assert.True(result.IsSuccess);
Assert.NotNull(result.Value);
output.WriteLine($"Retrieved {result.Value.Length} products.");
}
}
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop;
using LiteCharms.Features.Shop.Notifications;
using static LiteCharms.Features.Extensions.Email;
using static LiteCharms.Features.Email.Extensions.Constants;
namespace LiteCharms.Features.Email.Events.Handlers;
@@ -0,0 +1,8 @@
namespace LiteCharms.Features.Email.Extensions;
public static class Constants
{
public const string ShopSchedulerName = "shop";
public const string ShopEmailFromName = "Khongisa Shop";
public const string ShopEmailFromAddress = "shop@litecharms.co.za";
}
+1 -4
View File
@@ -4,10 +4,7 @@ using LiteCharms.Features.Email.Configuration;
namespace LiteCharms.Features.Extensions;
public static class Email
{
public const string ShopEmailFromName = "Khongisa Shop";
public const string ShopEmailFromAddress = "shop@litecharms.co.za";
{
public static IServiceCollection AddEmailServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<SmtpSettings>(configuration.GetSection("Email"));
+4 -19
View File
@@ -1,34 +1,19 @@
using LiteCharms.Features.HealthChecks;
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Extensions;
public static class HealthChecks
{
public static IServiceCollection AddShopQuartzHealthCheck(this IServiceCollection services)
public static IServiceCollection AddQuartzHealtchCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<ShopQuartzHealthCheck>("ShopQuartz");
services.AddHealthChecks().AddCheck<QuartzHealthCheck>("Quartz");
return services;
}
public static IServiceCollection AddMidrandShopQuartzHealthCheck(this IServiceCollection services)
public static IServiceCollection AddPostgresHealtchCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<MidrandShopQuartzHealthCheck>("MidrandShopQuartz");
return services;
}
public static IServiceCollection AddShopPostgresHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<PostgresShopHealthCheck>(ShopDbConfigName);
return services;
}
public static IServiceCollection AddMidrandShopPostgresHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<PostgresMidrandShopHealthCheck>(MidrandShopDbConfigName);
services.AddHealthChecks().AddCheck<PostgresHealthCheck>("PostgreSQL");
return services;
}
+2 -15
View File
@@ -1,26 +1,13 @@
using LiteCharms.Features.MidrandShop.Postgres;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.Postgres;
namespace LiteCharms.Features.Extensions;
public static class Postgres
{
public const string MidrandShopDbConfigName = "PostgresMidrandShop";
public const string ShopDbConfigName = "PostgresShop";
public const string SchedulerDbConfigName = "PostgresScheduler";
public static IServiceCollection AddShopDatabase(this IServiceCollection services, IConfiguration configuration)
{
services.AddPooledDbContextFactory<ShopDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(ShopDbConfigName)));
return services;
}
public static IServiceCollection AddMidrandShopDatabase(this IServiceCollection services, IConfiguration configuration)
{
services.AddPooledDbContextFactory<MidrandShopDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(MidrandShopDbConfigName)));
options.UseNpgsql(configuration.GetConnectionString("PostgresShop")));
return services;
}
+3 -5
View File
@@ -1,17 +1,15 @@
using LiteCharms.Features.Quartz;
using LiteCharms.Features.Quartz.Abstractions;
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Extensions;
public static class Quartz
{
public const string ShopSchedulerName = "shop";
public const string MidrandShopSchedulerName = "midrandshop";
private const string databaseConfigName = "PostgresScheduler";
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
var connectionString = configuration.GetConnectionString(databaseConfigName);
services.ConfigureCommon();
@@ -46,7 +44,7 @@ public static class Quartz
public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
var connectionString = configuration.GetConnectionString(databaseConfigName);
services.ConfigureCommon();
@@ -1,28 +0,0 @@
using static LiteCharms.Features.Extensions.Quartz;
namespace LiteCharms.Features.HealthChecks;
public class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var scheduler = await schedulerFactory.GetScheduler(MidrandShopSchedulerName, cancellationToken);
if(scheduler == null)
return HealthCheckResult.Unhealthy($"Scheduler with name '{MidrandShopSchedulerName}' not found.");
if (!scheduler.IsStarted)
return HealthCheckResult.Unhealthy($"{MidrandShopSchedulerName} Quartz scheduler is not running");
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
return HealthCheckResult.Healthy($"{MidrandShopSchedulerName} Quartz scheduler is ready");
}
catch (SchedulerException)
{
return HealthCheckResult.Unhealthy($"{MidrandShopSchedulerName} Quartz scheduler cannot connect to the store");
}
}
}
@@ -1,10 +1,8 @@
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.HealthChecks;
namespace LiteCharms.Features.HealthChecks;
public class PostgresShopHealthCheck(IConfiguration configuration) : IHealthCheck
public class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
{
private readonly string connectionString = configuration.GetConnectionString(ShopDbConfigName)!;
private readonly string connectionString = configuration.GetConnectionString("PostgresShop")!;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
@@ -18,11 +16,11 @@ public class PostgresShopHealthCheck(IConfiguration configuration) : IHealthChec
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy($"{ShopDbConfigName} is responsive.");
return HealthCheckResult.Healthy("PostgreSQL is responsive.");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy($"{ShopDbConfigName} is unreachable.", ex);
return HealthCheckResult.Unhealthy("PostgreSQL is unreachable.", ex);
}
}
}
@@ -1,28 +0,0 @@
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.HealthChecks;
public class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck
{
private readonly string connectionString = configuration.GetConnectionString(MidrandShopDbConfigName)!;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
await using var dataSource = NpgsqlDataSource.Create(connectionString);
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
await using var command = connection.CreateCommand();
command.CommandText = "SELECT 1";
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy($"{MidrandShopDbConfigName} is responsive.");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy($"{MidrandShopDbConfigName} is unreachable.", ex);
}
}
}
@@ -0,0 +1,23 @@
namespace LiteCharms.Features.HealthChecks;
public class QuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
if (!scheduler.IsStarted)
return HealthCheckResult.Unhealthy("Quartz scheduler is not running");
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
return HealthCheckResult.Healthy("Quartz scheduler is ready");
}
catch (SchedulerException)
{
return HealthCheckResult.Unhealthy("Quartz scheduler cannot connect to the store");
}
}
}
@@ -1,28 +0,0 @@
using static LiteCharms.Features.Extensions.Quartz;
namespace LiteCharms.Features.HealthChecks;
public class ShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var scheduler = await schedulerFactory.GetScheduler(ShopSchedulerName, cancellationToken);
if(scheduler == null)
return HealthCheckResult.Unhealthy($"Scheduler with name '{ShopSchedulerName}' not found.");
if (!scheduler.IsStarted)
return HealthCheckResult.Unhealthy($"{ShopSchedulerName} Quartz scheduler is not running");
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
return HealthCheckResult.Healthy($"{ShopSchedulerName} Quartz scheduler is ready");
}
catch (SchedulerException)
{
return HealthCheckResult.Unhealthy($"{ShopSchedulerName} Quartz scheduler cannot connect to the store");
}
}
}
@@ -131,8 +131,8 @@
<!-- Amazon S3 SDK -->
<ItemGroup>
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.4.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.23.4" />
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.3.40" />
<PackageReference Include="AWSSDK.S3" Version="4.0.23.3" />
<!-- global Usings -->
<Using Include="Amazon.S3" />
@@ -1,6 +0,0 @@
namespace LiteCharms.Features.MidrandShop.Postgres;
public class MidrandShopDbContext(DbContextOptions<MidrandShopDbContext> options) : DbContext(options)
{
}
@@ -1,21 +0,0 @@
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.MidrandShop.Postgres;
public class MidrandShopDbContextFactory : IDesignTimeDbContextFactory<MidrandShopDbContext>
{
public MidrandShopDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets(typeof(MidrandShopDbContext).Assembly)
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
var optionsBuilder = new DbContextOptionsBuilder<MidrandShopDbContext>();
optionsBuilder.UseNpgsql(configuration.GetConnectionString(MidrandShopDbConfigName));
return new MidrandShopDbContext(optionsBuilder.Options);
}
}
@@ -1,6 +1,4 @@
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Shop.Postgres;
namespace LiteCharms.Features.Shop.Postgres;
public class ShopDbContextFactory : IDesignTimeDbContextFactory<ShopDbContext>
{
@@ -14,7 +12,7 @@ public class ShopDbContextFactory : IDesignTimeDbContextFactory<ShopDbContext>
.Build();
var optionsBuilder = new DbContextOptionsBuilder<ShopDbContext>();
optionsBuilder.UseNpgsql(configuration.GetConnectionString(ShopDbConfigName));
optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgresShop"));
return new ShopDbContext(optionsBuilder.Options);
}