Compare commits

...

43 Commits

Author SHA1 Message Date
khwezi 50a8a59d92 Merge pull request 'Split Features to create space for more projects' (#39) from midrandshop into master
Reviewed-on: #39
2026-05-24 13:19:55 +02:00
Khwezi Mngoma 70c6e0bfbc Split Features to create space for more projects
continuous-integration/drone/pr Build is passing
2026-05-24 13:19:09 +02:00
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
104 changed files with 781 additions and 337 deletions
+3 -1
View File
@@ -19,6 +19,8 @@ steps:
commands:
- dotnet pack LiteCharms.Features/LiteCharms.Features.csproj -c Release -p:PackageVersion=$VERSION -o dist/
- dotnet nuget push dist/LiteCharms.Features.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
- dotnet pack LiteCharms.Features.TechShop/LiteCharms.Features.TechShop.csproj -c Release -p:PackageVersion=$VERSION -o dist/
- dotnet nuget push dist/LiteCharms.Features.TechShop.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
- name: gitea-tag-release
image: alpine/git
@@ -41,7 +43,7 @@ steps:
\"tag_name\": \"$VERSION\",
\"target_commitish\": \"${DRONE_COMMIT_SHA}\",
\"name\": \"Library Suite $VERSION\",
\"body\": \"### Published NuGet Packages\nAll packages versioned as **$VERSION**:\n* LiteCharms.Features\n\n[View in Nexus](https://nexus.khongisa.co.za/repository/nuget-group/)\",
\"body\": \"### Published NuGet Packages\nAll packages versioned as **$VERSION**:\n* LiteCharms.Features\n* LiteCharms.Features.TechShop\n\n[View in Nexus](https://nexus.khongisa.co.za/repository/nuget-group/)\",
\"draft\": false,
\"prerelease\": false
}"
@@ -0,0 +1,38 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.TechShop.Extensions;
namespace LiteCharms.Features.TechShop.Tests;
public class Fixture : IDisposable
{
public IConfiguration Configuration { get; set; }
public IServiceProvider Services { get; set; }
public IMediator Mediator { get; set; }
public Fixture()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets<Fixture>()
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Services = new ServiceCollection()
.AddMediator()
.AddLogging()
.AddTechShopServices()
.AddEmailServiceBus()
.AddGarageS3(Configuration)
.AddTechShopDatabase(Configuration)
.AddEmailServices(Configuration)
.AddSingleton(Configuration)
.BuildServiceProvider();
Mediator = Services.GetRequiredService<IMediator>();
}
public void Dispose() { }
}
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<UserSecretsId>fa06c1a6-2ba8-4c47-b19b-11e0f78e7b92</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<!-- Global Usings -->
<ItemGroup>
<Using Include="Mediator" />
<Using Include="Xunit.Abstractions" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LiteCharms.Features.TechShop\LiteCharms.Features.TechShop.csproj" />
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -1,27 +1,28 @@
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Notifications;
using LiteCharms.Features.Shop.Notifications.Events;
using LiteCharms.Features.TechShop.Notifications;
using LiteCharms.Features.TechShop.Notifications.Events;
using static LiteCharms.Features.Extensions.Email;
namespace LiteCharms.Features.Tests;
namespace LiteCharms.Features.TechShop.Tests;
public class NotificationsFeatureTests(CommonFixture fixture, ITestOutputHelper output) : IClassFixture<CommonFixture>
public class NotificationsFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
{
private readonly NotificationService notificationService = fixture.Services.GetRequiredService<NotificationService>();
[Fact]
public async Task CreateNotificationCommand_ShouldSucceed()
{
Shop.Notifications.Models.CreateNotification request = new()
Notifications.Models.CreateNotification request = new()
{
CorrelationId = Guid.CreateVersion7().ToString(),
CorrelationIdType = Shop.CorrelationIdTypes.None,
Direction = Shop.NotificationDirection.Outgoing,
Platform = Shop.NotificationPlatforms.Email,
Priority = Shop.Priorities.Medium,
CorrelationIdType = CorrelationIdTypes.None,
Direction = NotificationDirection.Outgoing,
Platform = NotificationPlatforms.Email,
Priority = 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.TechShop.Products;
namespace LiteCharms.Features.TechShop.Tests;
public class ProductsFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
{
[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.");
}
}
@@ -0,0 +1,34 @@
{
"BookshopS3Settings": {
"ServiceUrl": "http://192.168.1.177:30900",
"Region": "garage",
"BucketName": "bookshop",
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
},
"BookshopQuotesS3Settings": {
"ServiceUrl": "http://192.168.1.177:30900",
"Region": "garage",
"BucketName": "bookshop.quotes",
"CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za"
},
"Email": {
"Credentials": {
"Username": "shop@litecharms.co.za"
},
"Port": 465,
"Host": "mail.litecharms.co.za",
"UseSsl": true
},
"Monitoring": {
"ApiKey": "",
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
"ServiceName": "LiteCharms.LeadGenerator"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Entities;
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
[EntityTypeConfiguration<PackageConfirguration, Package>]
public class Package : Models.Package
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Entities;
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
public class PackageConfirguration : IEntityTypeConfiguration<Package>
{
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.Products.Entities;
using LiteCharms.Features.TechShop.Products.Entities;
namespace LiteCharms.Features.Shop.CartPackages.Entities;
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
[EntityTypeConfiguration<PackageItemConfiguration, PackageItem>]
public class PackageItem : Models.PackageItem
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Entities;
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
public class PackageItemConfiguration : IEntityTypeConfiguration<PackageItem>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Models;
namespace LiteCharms.Features.TechShop.CartPackages.Models;
public class Package
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Models;
namespace LiteCharms.Features.TechShop.CartPackages.Models;
public class PackageItem
{
@@ -1,9 +1,10 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.CartPackages.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.CartPackages.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.Shop.CartPackages;
namespace LiteCharms.Features.TechShop.CartPackages;
public class PackageService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,9 +1,10 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Customers.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Customers.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.Shop.Customers;
namespace LiteCharms.Features.TechShop.Customers;
public class CustomerService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,9 +1,9 @@
using LiteCharms.Features.Shop.Leads.Entities;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.Quotes.Entities;
using LiteCharms.Features.Shop.ShoppingCarts.Entities;
using LiteCharms.Features.TechShop.Leads.Entities;
using LiteCharms.Features.TechShop.Orders.Entities;
using LiteCharms.Features.TechShop.Quotes.Entities;
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
namespace LiteCharms.Features.Shop.Customers.Entities;
namespace LiteCharms.Features.TechShop.Customers.Entities;
[EntityTypeConfiguration<CustomerConfiguration, Customer>]
public class Customer : Models.Customer
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Customers.Entities;
namespace LiteCharms.Features.TechShop.Customers.Entities;
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Customers.Models;
namespace LiteCharms.Features.TechShop.Customers.Models;
public class Customer
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Customers.Models;
namespace LiteCharms.Features.TechShop.Customers.Models;
public record CreateCustomer
{
@@ -1,16 +1,4 @@
namespace LiteCharms.Features.Shop;
public enum EmailStatuses : int
{
GeneralError = 0,
AuthenticationError = 1,
ProtocolError = 2,
Connected = 3,
Disconnected = 4,
TooManyConnections = 5,
ConnectionAborted = 6,
Success = 7
}
namespace LiteCharms.Features.TechShop;
public enum CorrelationIdTypes : int
{
@@ -27,13 +15,6 @@ public enum CorrelationIdTypes : int
LinkedIn = 10
}
public enum Priorities : int
{
Low = 0,
Medium = 1,
High = 2,
}
public enum NotificationPlatforms : int
{
Email = 1,
@@ -0,0 +1,22 @@
using LiteCharms.Features.TechShop.HealthChecks;
using static LiteCharms.Features.TechShop.Extensions.Postgres;
namespace LiteCharms.Features.TechShop.Extensions;
public static class HealthChecks
{
public static IServiceCollection AddShopQuartzHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<ShopQuartzHealthCheck>("ShopQuartz");
return services;
}
public static IServiceCollection AddShopPostgresHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<PostgresShopHealthCheck>(ShopDbConfigName);
return services;
}
}
@@ -1,17 +1,18 @@
using LiteCharms.Features.Shop.CartPackages.Models;
using LiteCharms.Features.Shop.Customers.Models;
using LiteCharms.Features.Shop.Leads.Models;
using LiteCharms.Features.Shop.Notifications.Models;
using LiteCharms.Features.Shop.Orders.Models;
using LiteCharms.Features.Shop.Products.Models;
using LiteCharms.Features.Shop.Quotes.Models;
using LiteCharms.Features.Shop.ShoppingCarts.Models;
using LiteCharms.Features.TechShop.CartPackages.Models;
using LiteCharms.Features.TechShop.Customers.Models;
using LiteCharms.Features.TechShop.Leads.Models;
using LiteCharms.Features.TechShop.Notifications.Models;
using LiteCharms.Features.TechShop.Orders.Models;
using LiteCharms.Features.TechShop.Products.Entities;
using LiteCharms.Features.TechShop.Products.Models;
using LiteCharms.Features.TechShop.Quotes.Models;
using LiteCharms.Features.TechShop.ShoppingCarts.Models;
namespace LiteCharms.Features.Extensions;
namespace LiteCharms.Features.TechShop.Extensions;
public static class EntityModeMappers
public static class Mappers
{
public static ShoppingCartPackage ToModel(this Features.Shop.ShoppingCarts.Entities.ShoppingCartPackage entity) =>
public static ShoppingCartPackage ToModel(this ShoppingCarts.Entities.ShoppingCartPackage entity) =>
new()
{
Id = entity.Id,
@@ -20,7 +21,7 @@ public static class EntityModeMappers
ShoppingCartId = entity.ShoppingCartId
};
public static PackageItem ToModel(this Features.Shop.CartPackages.Entities.PackageItem entity) =>
public static PackageItem ToModel(this CartPackages.Entities.PackageItem entity) =>
new()
{
Id = entity.Id,
@@ -30,7 +31,7 @@ public static class EntityModeMappers
ProductPriceId = entity.ProductPriceId
};
public static Package ToModel(this Features.Shop.CartPackages.Entities.Package entity) =>
public static Package ToModel(this CartPackages.Entities.Package entity) =>
new()
{
Id = entity.Id,
@@ -43,7 +44,7 @@ public static class EntityModeMappers
Summary = entity.Summary
};
public static ShoppingCartItem ToModel(this Features.Shop.ShoppingCarts.Entities.ShoppingCartItem entity) =>
public static ShoppingCartItem ToModel(this ShoppingCarts.Entities.ShoppingCartItem entity) =>
new()
{
Id = entity.Id,
@@ -54,7 +55,7 @@ public static class EntityModeMappers
ShoppingCartId = entity.ShoppingCartId
};
public static ShoppingCart ToModel(this Features.Shop.ShoppingCarts.Entities.ShoppingCart entity) =>
public static ShoppingCart ToModel(this ShoppingCarts.Entities.ShoppingCart entity) =>
new()
{
Id = entity.Id,
@@ -64,7 +65,7 @@ public static class EntityModeMappers
OrderId = entity.OrderId
};
public static Quote ToModel(this Features.Shop.Quotes.Entities.Quote entity) =>
public static Quote ToModel(this Quotes.Entities.Quote entity) =>
new()
{
Id = entity.Id,
@@ -79,7 +80,7 @@ public static class EntityModeMappers
OrderId = entity.OrderId
};
public static Notification ToModel(this Features.Shop.Notifications.Entities.Notification entity) =>
public static Notification ToModel(this Notifications.Entities.Notification entity) =>
new()
{
Id = entity.Id,
@@ -103,7 +104,7 @@ public static class EntityModeMappers
Errors = entity.Errors
};
public static Customer ToModel(this Features.Shop.Customers.Entities.Customer entity) =>
public static Customer ToModel(this Customers.Entities.Customer entity) =>
new()
{
Id = entity.Id,
@@ -128,7 +129,7 @@ public static class EntityModeMappers
Whatsapp = entity.Whatsapp
};
public static Lead ToModel(this Features.Shop.Leads.Entities.Lead entity) =>
public static Lead ToModel(this Leads.Entities.Lead entity) =>
new()
{
Id = entity.Id,
@@ -149,7 +150,7 @@ public static class EntityModeMappers
Status = entity.Status
};
public static Order ToModel(this Features.Shop.Orders.Entities.Order entity) =>
public static Order ToModel(this Orders.Entities.Order entity) =>
new()
{
Id = entity.Id,
@@ -163,7 +164,7 @@ public static class EntityModeMappers
InvoiceUrl = entity.InvoiceUrl
};
public static OrderRefund ToModel(this Features.Shop.Orders.Entities.OrderRefund entity) =>
public static OrderRefund ToModel(this Orders.Entities.OrderRefund entity) =>
new()
{
Id = entity.Id,
@@ -173,7 +174,7 @@ public static class EntityModeMappers
Amount = entity.Amount
};
public static Product ToModel(this Features.Shop.Products.Entities.Product entity) =>
public static Products.Models.Product ToModel(this Products.Entities.Product entity) =>
new()
{
Id = entity.Id,
@@ -187,7 +188,7 @@ public static class EntityModeMappers
Metadata = entity.Metadata,
};
public static ProductPrice ToModel(this Features.Shop.Products.Entities.ProductPrice entity) =>
public static Products.Models.ProductPrice ToModel(this Products.Entities.ProductPrice entity) =>
new()
{
Id = entity.Id,
@@ -0,0 +1,16 @@
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.TechShop.Extensions;
public static class Postgres
{
public const string ShopDbConfigName = "PostgresShop";
public static IServiceCollection AddTechShopDatabase(this IServiceCollection services, IConfiguration configuration)
{
services.AddPooledDbContextFactory<ShopDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(ShopDbConfigName)));
return services;
}
}
@@ -0,0 +1,27 @@
using LiteCharms.Features.TechShop.CartPackages;
using LiteCharms.Features.TechShop.Customers;
using LiteCharms.Features.TechShop.Leads;
using LiteCharms.Features.TechShop.Notifications;
using LiteCharms.Features.TechShop.Orders;
using LiteCharms.Features.TechShop.Products;
using LiteCharms.Features.TechShop.Quotes;
using LiteCharms.Features.TechShop.ShoppingCarts;
namespace LiteCharms.Features.TechShop.Extensions;
public static class Shop
{
public static IServiceCollection AddTechShopServices(this IServiceCollection services)
{
services.AddSingleton<PackageService>()
.AddSingleton<LeadService>()
.AddSingleton<NotificationService>()
.AddSingleton<OrderService>()
.AddSingleton<ProductService>()
.AddSingleton<QuoteService>()
.AddSingleton<ShoppingCartService>()
.AddSingleton<CustomerService>();
return services;
}
}
@@ -1,8 +1,10 @@
namespace LiteCharms.Features.HealthChecks;
using static LiteCharms.Features.TechShop.Extensions.Postgres;
public class PostgresHealthCheck(IConfiguration configuration) : IHealthCheck
namespace LiteCharms.Features.TechShop.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);
}
}
}
@@ -0,0 +1,28 @@
using static LiteCharms.Features.Extensions.Quartz;
namespace LiteCharms.Features.TechShop.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");
}
}
}
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.Customers.Entities;
using LiteCharms.Features.TechShop.Customers.Entities;
namespace LiteCharms.Features.Shop.Leads.Entities;
namespace LiteCharms.Features.TechShop.Leads.Entities;
[EntityTypeConfiguration<LeadConfiguration, Lead>]
public class Lead : Models.Lead
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Leads.Entities;
namespace LiteCharms.Features.TechShop.Leads.Entities;
public class LeadConfiguration : IEntityTypeConfiguration<Lead>
{
@@ -1,10 +1,11 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Leads.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Leads.Models;
using LiteCharms.Features.TechShop.Postgres;
using static LiteCharms.Features.Extensions.Hash;
namespace LiteCharms.Features.Shop.Leads;
namespace LiteCharms.Features.TechShop.Leads;
public class LeadService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Leads.Models;
namespace LiteCharms.Features.TechShop.Leads.Models;
public class Lead
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Leads.Models;
namespace LiteCharms.Features.TechShop.Leads.Models;
public record CreateLead
{
@@ -0,0 +1,166 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
<UserSecretsId>fbd8f4a2-0420-44e2-baff-4678d9e7eee1</UserSecretsId>
</PropertyGroup>
<!-- Nuget Package Details -->
<PropertyGroup>
<PackageId>LiteCharms.Features.TechShop</PackageId>
<Version>1.0.20</Version>
<Authors>Khwezi Mngoma</Authors>
<Company>Lite Charms (PTY) Ltd</Company>
<Description>TechShop feature components for Lite Charms applications.</Description>
<PackageProjectUrl>https://gitea.khongisa.co.za/litecharms/components</PackageProjectUrl>
<RepositoryUrl>https://gitea.khongisa.co.za/litecharms/components.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>utility;dotnet</PackageTags>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
<None Include="..\icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
<!-- Quartz Scheduler-->
<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
<PackageReference Include="Quartz" Version="3.18.1" />
<PackageReference Include="Quartz.Plugins" Version="3.18.1" />
<PackageReference Include="Quartz.Plugins.TimeZoneConverter" Version="3.18.1" />
<PackageReference Include="Quartz.Serialization.SystemTextJson" Version="3.18.1" />
<PackageReference Include="Quartz.AspNetCore" Version="3.18.1" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
<!-- Global Usings -->
<Using Include="Quartz" />
</ItemGroup>
<!-- Configuration -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
<!-- Global Usings -->
<Using Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<!-- Health Checks -->
<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Core" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Data" Version="9.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.8" />
<!-- Global Usings -->
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<Using Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
</ItemGroup>
<!-- Open Telemetry -->
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<!-- Global Usings -->
<Using Include="OpenTelemetry.Resources" />
<Using Include="OpenTelemetry.Exporter" />
<Using Include="OpenTelemetry.Logs" />
<Using Include="OpenTelemetry.Metrics" />
<Using Include="OpenTelemetry.Trace" />
</ItemGroup>
<!-- Database -->
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<!-- Global Usings -->
<Using Include="Npgsql" />
<Using Include="Microsoft.EntityFrameworkCore" />
<Using Include="Microsoft.EntityFrameworkCore.Design" />
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
</ItemGroup>
<!-- Email -->
<ItemGroup>
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<!-- Global Usings-->
<Using Include="MimeKit" />
<Using Include="MailKit.Net.Smtp" />
</ItemGroup>
<!-- CQRS -->
<ItemGroup>
<PackageReference Include="FluentResults" Version="4.0.0" />
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
<!-- Global Usings -->
<Using Include="FluentResults" />
<Using Include="Mediator" />
</ItemGroup>
<!-- Amazon S3 SDK -->
<ItemGroup>
<PackageReference Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.4.1" />
<PackageReference Include="AWSSDK.S3" Version="4.0.23.4" />
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
<!-- global Usings -->
<Using Include="Amazon.S3" />
<Using Include="Amazon.S3.Model" />
<Using Include="Amazon.Runtime" />
</ItemGroup>
<!-- Shared Usings -->
<ItemGroup>
<Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.Extensions.Hosting" />
<Using Include="System.Text" />
<Using Include="System.Text.Json" />
<Using Include="System.Threading.Channels" />
<Using Include="System.Collections.ObjectModel" />
<Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.Metrics" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="System.Security.Cryptography" />
<Using Include="Microsoft.Extensions.Options" />
<Using Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Notifications.Entities;
namespace LiteCharms.Features.TechShop.Notifications.Entities;
[EntityTypeConfiguration<NotificationConfiguration, Notification>]
public class Notification : Models.Notification;
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Notifications.Entities;
namespace LiteCharms.Features.TechShop.Notifications.Entities;
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{
@@ -1,9 +1,8 @@
using k8s.KubeConfigModels;
using LiteCharms.Features.Email;
using LiteCharms.Features.Shop.Notifications.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Email;
using LiteCharms.Features.TechShop.Notifications.Models;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.Shop.Notifications.Events.Handlers;
namespace LiteCharms.Features.TechShop.Notifications.Events.Handlers;
public class ProcessEmailNotificationsEventHandler(IDbContextFactory<ShopDbContext> contextFactory, ILogger<ProcessEmailNotificationsEvent> logger,
EmailService emailService) : INotificationHandler<ProcessEmailNotificationsEvent>
@@ -1,14 +1,12 @@
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;
namespace LiteCharms.Features.TechShop.Notifications.Events.Handlers;
public class SendShopEmailEnquiryEventHandler(NotificationService notificationService) :
INotificationHandler<SendShopEmailEnquiryEvent>
{
public async ValueTask Handle(SendShopEmailEnquiryEvent notification, CancellationToken cancellationToken) =>
await notificationService.CreateNotificationAsync(new Shop.Notifications.Models.CreateNotification
await notificationService.CreateNotificationAsync(new Models.CreateNotification
{
CorrelationId = notification.CorrelationId,
CorrelationIdType = CorrelationIdTypes.None,
@@ -1,6 +1,6 @@
using LiteCharms.Features.Abstractions;
namespace LiteCharms.Features.Shop.Notifications.Events;
namespace LiteCharms.Features.TechShop.Notifications.Events;
public class ProcessEmailNotificationsEvent : EventBase, IEvent
{
@@ -1,7 +1,6 @@
using LiteCharms.Features.Abstractions;
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email.Events;
namespace LiteCharms.Features.TechShop.Notifications.Events;
public class SendShopEmailEnquiryEvent : EventBase, IEvent
{
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Notifications.Models;
using LiteCharms.Features.TechShop;
namespace LiteCharms.Features.TechShop.Notifications.Models;
public class Notification
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Notifications.Models;
namespace LiteCharms.Features.TechShop.Notifications.Models;
public record CreateNotification
{
@@ -1,9 +1,10 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Notifications.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Notifications.Models;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.Shop.Notifications;
namespace LiteCharms.Features.TechShop.Notifications;
public class NotificationService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,8 +1,8 @@
using LiteCharms.Features.Shop.Customers.Entities;
using LiteCharms.Features.Shop.Quotes.Entities;
using LiteCharms.Features.Shop.ShoppingCarts.Entities;
using LiteCharms.Features.TechShop.Customers.Entities;
using LiteCharms.Features.TechShop.Quotes.Entities;
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
namespace LiteCharms.Features.Shop.Orders.Entities;
namespace LiteCharms.Features.TechShop.Orders.Entities;
[EntityTypeConfiguration<OrderConfiguration, Order>]
public class Order : Models.Order
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Orders.Entities;
namespace LiteCharms.Features.TechShop.Orders.Entities;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Orders.Entities;
namespace LiteCharms.Features.TechShop.Orders.Entities;
[EntityTypeConfiguration<OrderRefundConfiguration, OrderRefund>]
public class OrderRefund : Models.OrderRefund
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Orders.Entities;
namespace LiteCharms.Features.TechShop.Orders.Entities;
public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund>
{
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Orders.Models;
using LiteCharms.Features.TechShop;
namespace LiteCharms.Features.TechShop.Orders.Models;
public class Order
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Orders.Models;
namespace LiteCharms.Features.TechShop.Orders.Models;
public class OrderRefund
{
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Orders.Models;
using LiteCharms.Features.TechShop;
namespace LiteCharms.Features.TechShop.Orders.Models;
public record CreateOrder
{
@@ -1,9 +1,10 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Orders.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Orders.Models;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.Shop.Orders;
namespace LiteCharms.Features.TechShop.Orders;
public class OrderService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Postgres;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
[DbContext(typeof(ShopDbContext))]
[Migration("20260512065421_Init")]
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Postgres;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
[DbContext(typeof(ShopDbContext))]
[Migration("20260514004002_UsedStringTableNames")]
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
/// <inheritdoc />
public partial class UsedStringTableNames : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.TechShop.Postgres;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
[DbContext(typeof(ShopDbContext))]
[Migration("20260515055221_FixedLeadCustomerRelationship")]
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
/// <inheritdoc />
public partial class FixedLeadCustomerRelationship : Migration
@@ -1,7 +1,7 @@
// <auto-generated />
using System;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.Products.Models;
using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
[DbContext(typeof(ShopDbContext))]
[Migration("20260520191059_AddedProductMetadata")]
@@ -1,10 +1,10 @@
using System;
using LiteCharms.Features.Shop.Products.Models;
using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedProductMetadata : Migration
@@ -1,7 +1,7 @@
// <auto-generated />
using System;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.Products.Models;
using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Features.Shop.Postgres.Migrations
namespace LiteCharms.Features.TechShop.Postgres.Migrations
{
[DbContext(typeof(ShopDbContext))]
partial class ShopDbContextModelSnapshot : ModelSnapshot
@@ -0,0 +1,39 @@
using LiteCharms.Features.TechShop.CartPackages.Entities;
using LiteCharms.Features.TechShop.Customers.Entities;
using LiteCharms.Features.TechShop.Leads.Entities;
using LiteCharms.Features.TechShop.Notifications.Entities;
using LiteCharms.Features.TechShop.Orders.Entities;
using LiteCharms.Features.TechShop.Products.Entities;
using LiteCharms.Features.TechShop.Quotes.Entities;
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.Postgres;
public class ShopDbContext(DbContextOptions<ShopDbContext> options) : DbContext(options)
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Lead> Leads { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderRefund> OrderRefunds { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<ProductPrice> ProductPrices { get; set; }
public DbSet<Notification> Notifications { get; set; }
public DbSet<Quote> Quotes { get; set; }
public DbSet<ShoppingCart> ShoppingCarts { get; set; }
public DbSet<ShoppingCartItem> ShoppingCartItems { get; set; }
public DbSet<Package> Packages { get; set; }
public DbSet<PackageItem> PackageItems { get; set; }
public DbSet<ShoppingCartPackage> ShoppingCartPackages { get; set; }
}
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Postgres;
using static LiteCharms.Features.TechShop.Extensions.Postgres;
namespace LiteCharms.Features.TechShop.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);
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Entities;
namespace LiteCharms.Features.TechShop.Products.Entities;
[EntityTypeConfiguration<ProductConfiguration, Product>]
public class Product : Models.Product
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Entities;
namespace LiteCharms.Features.TechShop.Products.Entities;
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Entities;
namespace LiteCharms.Features.TechShop.Products.Entities;
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
public class ProductPrice : Models.ProductPrice
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Entities;
namespace LiteCharms.Features.TechShop.Products.Entities;
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
{
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace LiteCharms.Features.Shop.Products.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public class CreateProductModel
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public class Product
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public class ProductMetadata
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public class ProductPrice
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public record CreateProduct
{
@@ -1,8 +1,8 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.Products.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models;
namespace LiteCharms.Features.Shop.Products;
namespace LiteCharms.Features.TechShop.Products;
public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,8 +1,8 @@
using LiteCharms.Features.Shop.Customers.Entities;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.ShoppingCarts.Entities;
using LiteCharms.Features.TechShop.Customers.Entities;
using LiteCharms.Features.TechShop.Orders.Entities;
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
namespace LiteCharms.Features.Shop.Quotes.Entities;
namespace LiteCharms.Features.TechShop.Quotes.Entities;
[EntityTypeConfiguration<QuoteConfiguration, Quote>]
public class Quote : Models.Quote
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Quotes.Entities;
namespace LiteCharms.Features.TechShop.Quotes.Entities;
public class QuoteConfiguration : IEntityTypeConfiguration<Quote>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Quotes.Models;
namespace LiteCharms.Features.TechShop.Quotes.Models;
public class Quote
{
@@ -1,9 +1,10 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.Quotes.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Quotes.Models;
namespace LiteCharms.Features.Shop.Quotes;
namespace LiteCharms.Features.TechShop.Quotes;
public class QuoteService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -1,8 +1,8 @@
using LiteCharms.Features.Shop.Customers.Entities;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.Quotes.Entities;
using LiteCharms.Features.TechShop.Customers.Entities;
using LiteCharms.Features.TechShop.Orders.Entities;
using LiteCharms.Features.TechShop.Quotes.Entities;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
[EntityTypeConfiguration<ShoppingCartConfiguration, ShoppingCart>]
public class ShoppingCart : Models.ShoppingCart
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
public class ShoppingCartConfiguration : IEntityTypeConfiguration<ShoppingCart>
{
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.Products.Entities;
using LiteCharms.Features.TechShop.Products.Entities;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
[EntityTypeConfiguration<ShoppingCartItemConfiguration, ShoppingCartItem>]
public class ShoppingCartItem : Models.ShoppingCartItem
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
public class ShoppingCartItemConfiguration : IEntityTypeConfiguration<ShoppingCartItem>
{
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.CartPackages.Entities;
using LiteCharms.Features.TechShop.CartPackages.Entities;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
[EntityTypeConfiguration<ShoppingCartPackageConfiguration, ShoppingCartPackage>]
public class ShoppingCartPackage : Models.ShoppingCartPackage
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
public class ShoppingCartPackageConfiguration : IEntityTypeConfiguration<ShoppingCartPackage>
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Models;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Models;
public class ShoppingCart
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Models;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Models;
public class ShoppingCartItem
{
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Models;
namespace LiteCharms.Features.TechShop.ShoppingCarts.Models;
public class ShoppingCartPackage
{
@@ -1,8 +1,8 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.Shop.ShoppingCarts.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.ShoppingCarts.Models;
namespace LiteCharms.Features.Shop.ShoppingCarts;
namespace LiteCharms.Features.TechShop.ShoppingCarts;
public class ShoppingCartService(IDbContextFactory<ShopDbContext> contextFactory)
{
@@ -0,0 +1,22 @@
{
"Email": {
"Credentials": {
"Username": "shop@litecharms.co.za"
},
"Port": 465,
"Host": "mail.litecharms.co.za",
"UseSsl": true
},
"Monitoring": {
"ApiKey": "",
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
"ServiceName": "LiteCharms.LeadGenerator"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.Tests;
public class CommonFixture : IDisposable
public class Fixture : IDisposable
{
public IConfiguration Configuration { get; set; }
@@ -10,22 +10,20 @@ public class CommonFixture : IDisposable
public IMediator Mediator { get; set; }
public CommonFixture()
public Fixture()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets<CommonFixture>()
.AddUserSecrets<Fixture>()
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Services = new ServiceCollection()
.AddMediator()
.AddLogging()
.AddShopServices()
.AddLogging()
.AddEmailServiceBus()
.AddGarageS3(Configuration)
.AddShopDatabase(Configuration)
.AddEmailServices(Configuration)
.AddSingleton(Configuration)
.BuildServiceProvider();
@@ -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>
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.Tests;
public class S3ServiceFeatureTests(CommonFixture fixture, ITestOutputHelper output) : IClassFixture<CommonFixture>
public class S3ServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
{
[Fact]
public async Task BookshopS3Service_MustReturnUrl()
@@ -1,7 +1,6 @@
using LiteCharms.Features.Email.Configuration;
using LiteCharms.Features.Email.Extensions;
using LiteCharms.Features.Email.Models;
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email;
@@ -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";
}
+1 -3
View File
@@ -1,6 +1,4 @@
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email.Models;
namespace LiteCharms.Features.Email.Models;
public class Response
{
+20
View File
@@ -0,0 +1,20 @@
namespace LiteCharms.Features;
public enum EmailStatuses : int
{
GeneralError = 0,
AuthenticationError = 1,
ProtocolError = 2,
Connected = 3,
Disconnected = 4,
TooManyConnections = 5,
ConnectionAborted = 6,
Success = 7
}
public enum Priorities : int
{
Low = 0,
Medium = 1,
High = 2,
}
+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"));
@@ -1,19 +1,20 @@
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 AddMidrandShopQuartzHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<QuartzHealthCheck>("Quartz");
services.AddHealthChecks().AddCheck<MidrandShopQuartzHealthCheck>("MidrandShopQuartz");
return services;
}
public static IServiceCollection AddPostgresHealtchCheck(this IServiceCollection services)
public static IServiceCollection AddMidrandShopPostgresHealthCheck(this IServiceCollection services)
{
services.AddHealthChecks().AddCheck<PostgresHealthCheck>("PostgreSQL");
services.AddHealthChecks().AddCheck<PostgresMidrandShopHealthCheck>(MidrandShopDbConfigName);
return services;
}
+7 -4
View File
@@ -1,13 +1,16 @@
using LiteCharms.Features.Shop.Postgres;
using LiteCharms.Features.MidrandShop.Postgres;
namespace LiteCharms.Features.Extensions;
public static class Postgres
{
public static IServiceCollection AddShopDatabase(this IServiceCollection services, IConfiguration configuration)
public const string MidrandShopDbConfigName = "PostgresMidrandShop";
public const string SchedulerDbConfigName = "PostgresScheduler";
public static IServiceCollection AddMidrandShopDatabase(this IServiceCollection services, IConfiguration configuration)
{
services.AddPooledDbContextFactory<ShopDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString("PostgresShop")));
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();
-27
View File
@@ -1,27 +0,0 @@
using LiteCharms.Features.Shop.CartPackages;
using LiteCharms.Features.Shop.Customers;
using LiteCharms.Features.Shop.Leads;
using LiteCharms.Features.Shop.Notifications;
using LiteCharms.Features.Shop.Orders;
using LiteCharms.Features.Shop.Products;
using LiteCharms.Features.Shop.Quotes;
using LiteCharms.Features.Shop.ShoppingCarts;
namespace LiteCharms.Features.Extensions;
public static class Shop
{
public static IServiceCollection AddShopServices(this IServiceCollection services)
{
services.AddSingleton<PackageService>()
.AddSingleton<LeadService>()
.AddSingleton<NotificationService>()
.AddSingleton<OrderService>()
.AddSingleton<ProductService>()
.AddSingleton<QuoteService>()
.AddSingleton<ShoppingCartService>()
.AddSingleton<CustomerService>();
return services;
}
}
@@ -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,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");
}
}
}
@@ -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" />
@@ -160,8 +160,5 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Shop\Postgres\Migrations\" />
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More