Compare commits

...

41 Commits

Author SHA1 Message Date
khwezi b70d9559b0 Merge pull request 'midrandshop' (#38) from midrandshop into master
Reviewed-on: #38
2026-05-23 11:55:02 +02:00
Khwezi Mngoma 032b9e1818 Updated nuget packages
continuous-integration/drone/pr Build is passing
2026-05-23 11:54:32 +02:00
Khwezi Mngoma 424c1c6f8c Fixed email test 2026-05-23 11:52:47 +02:00
khwezi 81d5e8f07c Merge pull request 'Added MidrandShop feature and spl;it extensions and healthchecks' (#37) from midrandshop into master
Reviewed-on: #37
2026-05-23 11:49:27 +02:00
Khwezi Mngoma 7d5e9a18d8 Added MidrandShop feature and spl;it extensions and healthchecks
continuous-integration/drone/pr Build is failing
2026-05-23 11:48:47 +02:00
khwezi 20a53942b5 Merge pull request 'Added product metadata' (#36) from s3service into master
Reviewed-on: #36
2026-05-20 21:15:01 +02:00
khwezi 9edb2aa4aa Merge pull request 'Optimised UploadFileAsync()' (#35) from s3service into master
Reviewed-on: #35
2026-05-20 15:33:47 +02:00
khwezi 6ed023f2cf Merge pull request 'Refactored the S3 services to properly upload the file' (#34) from s3service into master
Reviewed-on: #34
2026-05-20 08:03:43 +02:00
khwezi 2c9f5a846c Merge pull request 'Updated how i use configs' (#33) from s3service into master
Reviewed-on: #33
2026-05-19 14:57:59 +02:00
khwezi 41f7c05be3 Merge pull request 'Refactored service to internalise the CDN' (#32) from s3service into master
Reviewed-on: #32
2026-05-19 11:34:51 +02:00
khwezi 1a03355e84 Merge pull request 'Added S3 support' (#31) from s3service into master
Reviewed-on: #31
2026-05-19 10:24:05 +02:00
khwezi 7743c3178e Merge pull request 'Simplified notification updating' (#30) from emailjobs into master
Reviewed-on: #30
2026-05-17 16:01:24 +02:00
khwezi ab3d8e6e9a Merge pull request 'Refactored GetNotificationsAsync() date handling' (#29) from emailjobs into master
Reviewed-on: #29
2026-05-17 13:14:01 +02:00
khwezi db4c348288 Merge pull request 'Fixed email sending logic' (#28) from emailjobs into master
Reviewed-on: #28
2026-05-16 00:29:01 +02:00
khwezi 6683234642 Merge pull request 'Refactored batch drop logic' (#27) from emailjobs into master
Reviewed-on: #27
2026-05-16 00:05:51 +02:00
khwezi 6ddbb9479a Merge pull request 'Added an empty constructor to ProcessEmailNotificationEvent' (#26) from emailjobs into master
Reviewed-on: #26
2026-05-15 23:53:09 +02:00
khwezi 6c7349a0f8 Merge pull request 'Added additional logging and traces' (#25) from emailjobs into master
Reviewed-on: #25
2026-05-15 23:21:52 +02:00
khwezi e97fd6cd3f Merge pull request 'Added debug logging' (#24) from emailjobs into master
Reviewed-on: #24
2026-05-15 23:09:21 +02:00
khwezi 184c7c252a Merge pull request 'Set misfireThreshold to 2min and eased Cluster node checkin limit' (#23) from emailjobs into master
Reviewed-on: #23
2026-05-15 22:29:08 +02:00
khwezi bfe8c458d6 Merge pull request 'Optimised quartz' (#22) from emailjobs into master
Reviewed-on: #22
2026-05-15 09:52:06 +02:00
khwezi e6e0475db1 Merge pull request 'emailjobs' (#21) from emailjobs into master
Reviewed-on: #21
2026-05-15 08:39:36 +02:00
khwezi 5090c60797 Merge pull request 'Fixed Lead->Customer Relationship' (#20) from emailjobs into master
Reviewed-on: #20
2026-05-15 07:55:56 +02:00
khwezi 9432252e15 Merge pull request 'Added khongisa host entry on pipeline commands' (#19) from emailjobs into master
Reviewed-on: #19
2026-05-14 02:54:00 +02:00
khwezi 47111a1a3a Merge pull request 'emailjobs' (#18) from emailjobs into master
Reviewed-on: #18
2026-05-14 02:49:34 +02:00
khwezi 6eb3d50375 Merge pull request 'Updated job scheduler' (#17) from emailjobs into master
Reviewed-on: #17
2026-05-10 17:33:23 +02:00
khwezi 4deb732804 Merge pull request 'emailjobs' (#16) from emailjobs into master
Reviewed-on: #16
2026-05-10 16:51:24 +02:00
khwezi 20d9387d0b Merge pull request 'Migrated database changes' (#15) from develop into master
Reviewed-on: #15
2026-05-10 11:17:54 +02:00
khwezi 9f6d0ccaa0 Merge pull request 'Populated README' (#14) from develop into master
Reviewed-on: #14
2026-05-10 09:48:35 +02:00
khwezi 1acbc4d213 Merge pull request 'fixed git repo naming' (#13) from develop into master
Reviewed-on: #13
2026-05-10 09:22:37 +02:00
khwezi 8c99668fac Merge pull request 'Added tag and release step after publish' (#12) from develop into master
Reviewed-on: #12
2026-05-10 09:19:33 +02:00
khwezi ad44f46204 Merge pull request 'Refactored Quartz instance id to AUTO, removed constant' (#11) from develop into master
Reviewed-on: #11
2026-05-10 08:21:08 +02:00
khwezi 49d999c1e3 Merge pull request 'Refactored postgres extension' (#10) from develop into master
Reviewed-on: #10
2026-05-09 17:45:00 +02:00
khwezi 9ed4777a18 Merge pull request 'Refactored database references' (#9) from develop into master
Reviewed-on: #9
2026-05-09 17:01:02 +02:00
khwezi 0cf44f68cc Merge pull request 'Added scheduler constants' (#8) from develop into master
Reviewed-on: #8
2026-05-09 15:27:50 +02:00
khwezi 41ed5a4288 Merge pull request 'Fixed quartz host config table prefix' (#7) from develop into master
Reviewed-on: #7
2026-05-09 13:30:33 +02:00
khwezi bbcba5e06c Merge pull request 'Fixed quartz table name prefix' (#6) from develop into master
Reviewed-on: #6
2026-05-09 13:28:29 +02:00
khwezi 502cc326dd Merge pull request 'Updated pipeline to use major version with minor always 0' (#5) from develop into master
Reviewed-on: #5
2026-05-09 12:02:19 +02:00
khwezi 4675d4c5fc Merge pull request 'Updated pipeline to use major versions only' (#4) from develop into master
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is failing
Reviewed-on: #4
2026-05-09 11:56:20 +02:00
khwezi f80bb2fff9 Merge pull request 'Changed target branch from nain to master' (#3) from dronepipeline into master
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: #3
2026-05-09 11:37:39 +02:00
khwezi 6767906b0d Merge pull request 'Added pipeline separator' (#2) from dronepipeline into master
Reviewed-on: #2
2026-05-09 11:25:34 +02:00
khwezi a344af4498 Merge pull request 'Added .drone.yml pipeline' (#1) from dronepipeline into master
Reviewed-on: #1
2026-05-09 11:12:38 +02:00
18 changed files with 191 additions and 54 deletions
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -1,6 +1,7 @@
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;
@@ -20,8 +21,8 @@ public class NotificationsFeatureTests(CommonFixture fixture, ITestOutputHelper
Priority = Shop.Priorities.Medium,
Sender = "xUnit Test",
SenderAddress = "khwezi@mngoma.africa",
Recipient = $"{Email.Extensions.Constants.ShopEmailFromName} [Test]",
RecipientAddress = Email.Extensions.Constants.ShopEmailFromAddress,
Recipient = $"{ShopEmailFromName} [Test]",
RecipientAddress = ShopEmailFromAddress,
Subject = "Test Message",
Message = "This is an automation test",
IsHtml = false,
@@ -0,0 +1,19 @@
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.Email.Extensions.Constants;
using static LiteCharms.Features.Extensions.Email;
namespace LiteCharms.Features.Email.Events.Handlers;
@@ -1,8 +0,0 @@
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";
}
+4 -1
View File
@@ -4,7 +4,10 @@ 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"));
+19 -4
View File
@@ -1,19 +1,34 @@
using LiteCharms.Features.HealthChecks;
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Extensions;
public static class HealthChecks
{
public static IServiceCollection AddQuartzHealtchCheck(this IServiceCollection services)
public static IServiceCollection AddShopQuartzHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<QuartzHealthCheck>("Quartz");
services.AddHealthChecks().AddCheck<ShopQuartzHealthCheck>("ShopQuartz");
return services;
}
public static IServiceCollection AddPostgresHealtchCheck(this IServiceCollection services)
public static IServiceCollection AddMidrandShopQuartzHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<PostgresHealthCheck>("PostgreSQL");
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);
return services;
}
+15 -2
View File
@@ -1,13 +1,26 @@
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.MidrandShop.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("PostgresShop")));
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)));
return services;
}
+5 -3
View File
@@ -1,15 +1,17 @@
using LiteCharms.Features.Quartz;
using LiteCharms.Features.Quartz.Abstractions;
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Extensions;
public static class Quartz
{
private const string databaseConfigName = "PostgresScheduler";
public const string ShopSchedulerName = "shop";
public const string MidrandShopSchedulerName = "midrandshop";
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString(databaseConfigName);
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
services.ConfigureCommon();
@@ -44,7 +46,7 @@ public static class Quartz
public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{
var connectionString = configuration.GetConnectionString(databaseConfigName);
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
services.ConfigureCommon();
@@ -0,0 +1,28 @@
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");
}
}
}
@@ -0,0 +1,28 @@
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);
}
}
}
@@ -1,8 +1,10 @@
namespace LiteCharms.Features.HealthChecks;
using static LiteCharms.Features.Extensions.Postgres;
public class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
namespace LiteCharms.Features.HealthChecks;
public class PostgresShopHealthCheck(IConfiguration configuration) : IHealthCheck
{
private readonly string connectionString = configuration.GetConnectionString("PostgresShop")!;
private readonly string connectionString = configuration.GetConnectionString(ShopDbConfigName)!;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
@@ -16,11 +18,11 @@ public class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy("PostgreSQL is responsive.");
return HealthCheckResult.Healthy($"{ShopDbConfigName} is responsive.");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("PostgreSQL is unreachable.", ex);
return HealthCheckResult.Unhealthy($"{ShopDbConfigName} is unreachable.", ex);
}
}
}
@@ -1,23 +0,0 @@
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");
}
}
}
@@ -0,0 +1,28 @@
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.3.40" />
<PackageReference Include="AWSSDK.S3" Version="4.0.23.3" />
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.4.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.23.4" />
<!-- global Usings -->
<Using Include="Amazon.S3" />
@@ -0,0 +1,6 @@
namespace LiteCharms.Features.MidrandShop.Postgres;
public class MidrandShopDbContext(DbContextOptions<MidrandShopDbContext> options) : DbContext(options)
{
}
@@ -0,0 +1,21 @@
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,4 +1,6 @@
namespace LiteCharms.Features.Shop.Postgres;
using static LiteCharms.Features.Extensions.Postgres;
namespace LiteCharms.Features.Shop.Postgres;
public class ShopDbContextFactory : IDesignTimeDbContextFactory<ShopDbContext>
{
@@ -12,7 +14,7 @@ public class ShopDbContextFactory : IDesignTimeDbContextFactory<ShopDbContext>
.Build();
var optionsBuilder = new DbContextOptionsBuilder<ShopDbContext>();
optionsBuilder.UseNpgsql(configuration.GetConnectionString("PostgresShop"));
optionsBuilder.UseNpgsql(configuration.GetConnectionString(ShopDbConfigName));
return new ShopDbContext(optionsBuilder.Options);
}