Compare commits

..
16 Commits
Author SHA1 Message Date
khwezi c3b079ab4b Merge commit 'cecd9f90e9812a2bd84b2bb8026dbc9cce79741f' 2026-05-10 14:51:22 +00: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
230 changed files with 1453 additions and 1669 deletions
@@ -1,9 +1,13 @@
namespace LiteCharms.Features.ServiceBus; namespace LiteCharms.Abstractions;
public static class Constants public static class Constants
{ {
public const int QueueBounds = 100000; public const int QueueBounds = 100000;
public const string ShopSchedulerName = "shop";
public const string ShopEmailFromName = "Khongisa Shop";
public const string ShopEmailFromAddress = "shop@litecharms.co.za";
public const string EmailServiceBus = nameof(EmailServiceBus); public const string EmailServiceBus = nameof(EmailServiceBus);
public const string GeneralServiceBus = nameof(GeneralServiceBus); public const string GeneralServiceBus = nameof(GeneralServiceBus);
public const string SalesServiceBus = nameof(SalesServiceBus); public const string SalesServiceBus = nameof(SalesServiceBus);
@@ -1,7 +1,6 @@
using LiteCharms.Features.Extensions; using static LiteCharms.Abstractions.Timezones;
using static LiteCharms.Features.Extensions.Timezones;
namespace LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
public abstract class EventBase public abstract class EventBase
{ {
@@ -1,6 +1,4 @@
using LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
namespace LiteCharms.Features.ServiceBus.Abstractions;
public abstract class EventBusQueueBase public abstract class EventBusQueueBase
{ {
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
public interface IEvent : INotification public interface IEvent : INotification
{ {
@@ -1,6 +1,4 @@
using LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
namespace LiteCharms.Features.ServiceBus.Abstractions;
public interface IEventBus public interface IEventBus
{ {
@@ -1,6 +1,4 @@
using LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
namespace LiteCharms.Features.ServiceBus.Abstractions;
public interface IEventBusQueue public interface IEventBusQueue
{ {
@@ -1,6 +1,4 @@
using LiteCharms.Features.Abstractions; namespace LiteCharms.Abstractions;
namespace LiteCharms.Features.Quartz.Abstractions;
public interface IJobOrchestrator public interface IJobOrchestrator
{ {
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<!-- Nuget Package Details -->
<PropertyGroup>
<PackageId>LiteCharms.Abstractions</PackageId>
<Version>1.0.20</Version>
<Authors>Khwezi Mngoma</Authors>
<Company>Lite Charms (PTY) Ltd</Company>
<Description>Shared abstractions 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>
<ItemGroup>
<PackageReference Include="FluentResults" Version="4.0.0" />
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
<Using Include="Mediator" />
<Using Include="FluentResults" />
<Using Include="System.Threading.Channels" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Abstractions;
public static class Timezones public static class Timezones
{ {
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Customers.Entities; namespace LiteCharms.Entities.Configuration;
public class CustomerConfiguration : IEntityTypeConfiguration<Customer> public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{ {
@@ -7,8 +7,8 @@ public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
builder.ToTable(nameof(Customer)); builder.ToTable(nameof(Customer));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false); builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.Company); builder.Property(f => f.Company);
builder.Property(f => f.Name).IsRequired(); builder.Property(f => f.Name).IsRequired();
builder.Property(f => f.LastName).IsRequired(); builder.Property(f => f.LastName).IsRequired();
@@ -26,5 +26,10 @@ public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
builder.Property(f => f.Country); builder.Property(f => f.Country);
builder.Property(f => f.PostalCode); builder.Property(f => f.PostalCode);
builder.Property(f => f.Active).HasDefaultValue(true); builder.Property(f => f.Active).HasDefaultValue(true);
builder.HasMany(f => f.Leads)
.WithOne(f => f.Customer)
.HasForeignKey(f => f.CustomerId)
.OnDelete(DeleteBehavior.NoAction);
} }
} }
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Leads.Entities; namespace LiteCharms.Entities.Configuration;
public class LeadConfiguration : IEntityTypeConfiguration<Lead> public class LeadConfiguration : IEntityTypeConfiguration<Lead>
{ {
@@ -7,8 +7,8 @@ public class LeadConfiguration : IEntityTypeConfiguration<Lead>
builder.ToTable(nameof(Lead)); builder.ToTable(nameof(Lead));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false); builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.CustomerId).IsRequired(false); builder.Property(f => f.CustomerId).IsRequired(false);
builder.Property(f => f.Source); builder.Property(f => f.Source);
builder.Property(f => f.ClickId); builder.Property(f => f.ClickId);
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Notifications.Entities; using LiteCharms.Models;
namespace LiteCharms.Entities.Configuration;
public class NotificationConfiguration : IEntityTypeConfiguration<Notification> public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{ {
@@ -7,8 +9,8 @@ public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
builder.ToTable(nameof(Notification)); builder.ToTable(nameof(Notification));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false); builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.Direction).IsRequired().HasConversion<int>(); builder.Property(f => f.Direction).IsRequired().HasConversion<int>();
builder.Property(f => f.Platform).IsRequired().HasConversion<int>(); builder.Property(f => f.Platform).IsRequired().HasConversion<int>();
builder.Property(f => f.Priority).IsRequired().HasConversion<int>(); builder.Property(f => f.Priority).IsRequired().HasConversion<int>();
@@ -0,0 +1,32 @@
namespace LiteCharms.Entities.Configuration;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable(nameof(Order));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.CustomerId).IsRequired();
builder.Property(f => f.QuoteId).IsRequired(false);
builder.Property(f => f.RefundId).IsRequired(false);
builder.Property(f => f.ShoppingCartId).IsRequired();
builder.Property(f => f.Status).HasConversion<int>().IsRequired();
builder.Property(f => f.Requirements).HasColumnType("jsonb").IsRequired(false);
builder.Property(f => f.Notes).HasColumnType("jsonb").IsRequired(false);
builder.Property(f => f.Terms).HasColumnType("jsonb").IsRequired(false);
builder.Property(f => f.DepositRequired);
builder.HasOne(f => f.Quote)
.WithOne(f => f.Order)
.HasForeignKey<Order>(f => f.QuoteId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Customer)
.WithMany(f => f.Orders)
.HasForeignKey(f => f.CustomerId)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Orders.Entities; namespace LiteCharms.Entities.Configuration;
public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund> public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund>
{ {
@@ -7,15 +7,13 @@ public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund>
builder.ToTable(nameof(OrderRefund)); builder.ToTable(nameof(OrderRefund));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.OrderId).IsRequired(); builder.Property(f => f.OrderId).IsRequired();
builder.Property(f => f.Reason).IsRequired(); builder.Property(f => f.Reason).IsRequired();
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2); builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.HasOne(r => r.Order) builder.HasOne(f => f.Order)
.WithMany(o => o.Refunds) .WithOne(o => o.Refund)
.HasForeignKey(r => r.OrderId) .HasForeignKey<OrderRefund>(o => o.OrderId);
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
} }
} }
@@ -0,0 +1,16 @@
namespace LiteCharms.Entities.Configuration;
public class PackageConfirguration : IEntityTypeConfiguration<Package>
{
public void Configure(EntityTypeBuilder<Package> builder)
{
builder.ToTable(nameof(Package));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.Name).IsRequired();
builder.Property(f => f.Description).IsRequired();
builder.Property(f => f.Active);
}
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.CartPackages.Entities; namespace LiteCharms.Entities.Configuration;
public class PackageItemConfiguration : IEntityTypeConfiguration<PackageItem> public class PackageItemConfiguration : IEntityTypeConfiguration<PackageItem>
{ {
@@ -7,21 +7,14 @@ public class PackageItemConfiguration : IEntityTypeConfiguration<PackageItem>
builder.ToTable(nameof(PackageItem)); builder.ToTable(nameof(PackageItem));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.PackageId).IsRequired(); builder.Property(f => f.PackageId).IsRequired();
builder.Property(f => f.ProductPriceId).IsRequired(); builder.Property(f => f.ProductPriceId).IsRequired();
builder.Property(f => f.Active); builder.Property(f => f.Active);
builder.HasOne(f => f.Package) builder.HasOne(f => f.Package)
.WithMany(f => f.PackageItems) .WithMany()
.HasForeignKey(pi => pi.PackageId) .HasForeignKey(f => f.PackageId)
.IsRequired() .OnDelete(DeleteBehavior.NoAction);
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(f => f.ProductPrice)
.WithMany()
.HasForeignKey(pi => pi.ProductPriceId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
} }
} }
@@ -0,0 +1,14 @@
namespace LiteCharms.Entities.Configuration;
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable(nameof(Product));
builder.HasKey(f => f.Id);
builder.Property(f => f.Name).IsRequired();
builder.Property(f => f.Description).IsRequired();
builder.Property(f => f.Active).HasDefaultValue(true);
}
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.Products.Entities; namespace LiteCharms.Entities.Configuration;
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice> public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
{ {
@@ -7,17 +7,16 @@ public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
builder.ToTable(nameof(ProductPrice)); builder.ToTable(nameof(ProductPrice));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false); builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.ProductId).IsRequired(); builder.Property(f => f.ProductId).IsRequired();
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2); builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Discount).HasPrecision(18, 2); builder.Property(f => f.Discount).HasPrecision(18, 2);
builder.Property(f => f.Active); builder.Property(f => f.Active);
builder.HasOne(f => f.Product) builder.HasOne(f => f.Product)
.WithMany(f => f.ProductPrices) .WithMany(p => p.ProductPrices)
.HasForeignKey(pp => pp.ProductId) .HasForeignKey(f => f.ProductId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
} }
} }
@@ -0,0 +1,23 @@
namespace LiteCharms.Entities.Configuration;
public class QuoteConfiguration : IEntityTypeConfiguration<Quote>
{
public void Configure(EntityTypeBuilder<Quote> builder)
{
builder.ToTable(nameof(Quote));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.ExpiredAt).IsRequired(false);
builder.Property(f => f.CustomerId).IsRequired();
builder.Property(f => f.Status).IsRequired().HasConversion<int>();
builder.Property(f => f.ShoppingCartId).IsRequired();
builder.Property(f => f.Reason).IsRequired(false);
builder.HasOne(f => f.Customer)
.WithMany()
.HasForeignKey(f => f.CustomerId)
.OnDelete(DeleteBehavior.Cascade);
}
}
@@ -0,0 +1,31 @@
namespace LiteCharms.Entities.Configuration;
public class ShoppingCartConfiguration : IEntityTypeConfiguration<ShoppingCart>
{
public void Configure(EntityTypeBuilder<ShoppingCart> builder)
{
builder.ToTable(nameof(ShoppingCart));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.CustomerId).IsRequired(false);
builder.Property(f => f.OrderId).IsRequired(false);
builder.Property(f => f.QuoteId).IsRequired(false);
builder.HasOne(f => f.Customer)
.WithMany(c => c.ShoppingCarts)
.HasForeignKey(f => f.CustomerId)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Order)
.WithOne(o => o.ShoppingCart)
.HasForeignKey<Order>(o => o.ShoppingCartId)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Quote)
.WithOne(o => o.ShoppingCart)
.HasForeignKey<Quote>(o => o.ShoppingCartId)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -0,0 +1,25 @@
namespace LiteCharms.Entities.Configuration;
public class ShoppingCartItemConfiguration : IEntityTypeConfiguration<ShoppingCartItem>
{
public void Configure(EntityTypeBuilder<ShoppingCartItem> builder)
{
builder.ToTable(nameof(ShoppingCartItem));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.Quantity).IsRequired().HasDefaultValue(1);
builder.Property(f => f.ProductPriceId).IsRequired();
builder.HasOne(f => f.ProductPrice)
.WithMany()
.HasForeignKey(f => f.ProductPriceId)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.ShoppingCart)
.WithMany(f => f.ShoppingCartItems)
.HasForeignKey(f => f.ShoppingCartId)
.OnDelete(DeleteBehavior.NoAction);
}
}
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities; namespace LiteCharms.Entities.Configuration;
public class ShoppingCartPackageConfiguration : IEntityTypeConfiguration<ShoppingCartPackage> public class ShoppingCartPackageConfiguration : IEntityTypeConfiguration<ShoppingCartPackage>
{ {
@@ -7,20 +7,18 @@ public class ShoppingCartPackageConfiguration : IEntityTypeConfiguration<Shoppin
builder.ToTable(nameof(ShoppingCartPackage)); builder.ToTable(nameof(ShoppingCartPackage));
builder.HasKey(f => f.Id); builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.ShoppingCartId).IsRequired(); builder.Property(f => f.ShoppingCartId).IsRequired();
builder.Property(f => f.PackageId).IsRequired(); builder.Property(f => f.PackageId).IsRequired();
builder.HasOne(f => f.ShoppingCart)
.WithMany(s => s.ShoppingCartPackages)
.HasForeignKey(scp => scp.ShoppingCartId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(f => f.Package) builder.HasOne(f => f.Package)
.WithMany() .WithMany()
.HasForeignKey(scp => scp.PackageId) .HasForeignKey(f => f.PackageId)
.IsRequired() .OnDelete(DeleteBehavior.NoAction);
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.ShoppingCart)
.WithMany()
.HasForeignKey(f => f.ShoppingCartId)
.OnDelete(DeleteBehavior.NoAction);
} }
} }
@@ -1,9 +1,6 @@
using LiteCharms.Features.Shop.Leads.Entities; using LiteCharms.Entities.Configuration;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.Quotes.Entities;
using LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.Shop.Customers.Entities; namespace LiteCharms.Entities;
[EntityTypeConfiguration<CustomerConfiguration, Customer>] [EntityTypeConfiguration<CustomerConfiguration, Customer>]
public class Customer : Models.Customer public class Customer : Models.Customer
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.Customers.Models; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Features.Shop.Leads.Entities; namespace LiteCharms.Entities;
[EntityTypeConfiguration<LeadConfiguration, Lead>] [EntityTypeConfiguration<LeadConfiguration, Lead>]
public class Lead : Models.Lead public class Lead : Models.Lead
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<!-- Nuget Package Details -->
<PropertyGroup>
<PackageId>LiteCharms.Entities</PackageId>
<Version>1.0.20</Version>
<Authors>Khwezi Mngoma</Authors>
<Company>Lite Charms (PTY) Ltd</Company>
<Description>Shared entities 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>
<!-- Database -->
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
<!-- Global Usings -->
<Using Include="Microsoft.EntityFrameworkCore" />
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LiteCharms.Models\LiteCharms.Models.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Notifications.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<NotificationConfiguration, Notification>] [EntityTypeConfiguration<NotificationConfiguration, Notification>]
public class Notification : Models.Notification; public class Notification : Models.Notification;
+15
View File
@@ -0,0 +1,15 @@
using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<OrderConfiguration, Order>]
public class Order : Models.Order
{
public virtual OrderRefund? Refund { get; set; }
public virtual Customer? Customer { get; set; }
public virtual Quote? Quote { get; set; }
public virtual ShoppingCart? ShoppingCart { get; set; }
}
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Orders.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<OrderRefundConfiguration, OrderRefund>] [EntityTypeConfiguration<OrderRefundConfiguration, OrderRefund>]
public class OrderRefund : Models.OrderRefund public class OrderRefund : Models.OrderRefund
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.CartPackages.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<PackageConfirguration, Package>] [EntityTypeConfiguration<PackageConfirguration, Package>]
public class Package : Models.Package public class Package : Models.Package
+9
View File
@@ -0,0 +1,9 @@
using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<PackageItemConfiguration, PackageItem>]
public class PackageItem : Models.PackageItem
{
public virtual Package? Package { get; set; }
}
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Products.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<ProductConfiguration, Product>] [EntityTypeConfiguration<ProductConfiguration, Product>]
public class Product : Models.Product public class Product : Models.Product
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.Shop.Products.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>] [EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
public class ProductPrice : Models.ProductPrice public class ProductPrice : Models.ProductPrice
@@ -1,15 +1,13 @@
using LiteCharms.Features.Shop.Customers.Entities; using LiteCharms.Entities.Configuration;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.ShoppingCarts.Entities;
namespace LiteCharms.Features.Shop.Quotes.Entities; namespace LiteCharms.Entities;
[EntityTypeConfiguration<QuoteConfiguration, Quote>] [EntityTypeConfiguration<QuoteConfiguration, Quote>]
public class Quote : Models.Quote public class Quote : Models.Quote
{ {
public virtual Customer? Customer { get; set; } public virtual Customer? Customer { get; set; }
public virtual Order? Order { get; set; }
public virtual ShoppingCart? ShoppingCart { get; set; } public virtual ShoppingCart? ShoppingCart { get; set; }
public virtual Order? Order { get; set; }
} }
@@ -1,8 +1,6 @@
using LiteCharms.Features.Shop.Customers.Entities; using LiteCharms.Entities.Configuration;
using LiteCharms.Features.Shop.Orders.Entities;
using LiteCharms.Features.Shop.Quotes.Entities;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities; namespace LiteCharms.Entities;
[EntityTypeConfiguration<ShoppingCartConfiguration, ShoppingCart>] [EntityTypeConfiguration<ShoppingCartConfiguration, ShoppingCart>]
public class ShoppingCart : Models.ShoppingCart public class ShoppingCart : Models.ShoppingCart
@@ -15,5 +13,5 @@ public class ShoppingCart : Models.ShoppingCart
public virtual ICollection<ShoppingCartItem>? ShoppingCartItems { get; set; } public virtual ICollection<ShoppingCartItem>? ShoppingCartItems { get; set; }
public virtual ICollection<ShoppingCartPackage>? ShoppingCartPackages { get; set; } public virtual ICollection<Package>? Packages { get; set; }
} }
@@ -1,6 +1,4 @@
using LiteCharms.Features.Shop.Products.Entities; namespace LiteCharms.Entities;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities;
public class ShoppingCartItem : Models.ShoppingCartItem public class ShoppingCartItem : Models.ShoppingCartItem
{ {
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.CartPackages.Entities; using LiteCharms.Entities.Configuration;
namespace LiteCharms.Features.Shop.ShoppingCarts.Entities; namespace LiteCharms.Entities;
[EntityTypeConfiguration<ShoppingCartPackageConfiguration, ShoppingCartPackage>] [EntityTypeConfiguration<ShoppingCartPackageConfiguration, ShoppingCartPackage>]
public class ShoppingCartPackage : Models.ShoppingCartPackage public class ShoppingCartPackage : Models.ShoppingCartPackage
+13
View File
@@ -0,0 +1,13 @@
using LiteCharms.Models.Configuraton.Email;
namespace LiteCharms.Extensions;
public static class Email
{
public static IServiceCollection AddEmailServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<SmtpSettings>(configuration.GetSection("Email"));
return services;
}
}
@@ -1,17 +1,10 @@
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.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;
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class EntityModeMappers public static class EntityModeMappers
{ {
public static ShoppingCartPackage ToModel(this Shop.ShoppingCarts.Entities.ShoppingCartPackage entity) => public static ShoppingCartPackage ToModel(this Entities.ShoppingCartPackage entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -20,7 +13,7 @@ public static class EntityModeMappers
ShoppingCartId = entity.ShoppingCartId ShoppingCartId = entity.ShoppingCartId
}; };
public static PackageItem ToModel(this Shop.CartPackages.Entities.PackageItem entity) => public static PackageItem ToModel(this Entities.PackageItem entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -30,7 +23,7 @@ public static class EntityModeMappers
ProductPriceId = entity.ProductPriceId ProductPriceId = entity.ProductPriceId
}; };
public static Package ToModel(this Shop.CartPackages.Entities.Package entity) => public static Package ToModel(this Entities.Package entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -38,12 +31,10 @@ public static class EntityModeMappers
Active = entity.Active, Active = entity.Active,
Description = entity.Description, Description = entity.Description,
Name = entity.Name, Name = entity.Name,
UpdatedAt = entity.UpdatedAt, UpdatedAt = entity.UpdatedAt
ImageUrl = entity.ImageUrl,
Summary = entity.Summary
}; };
public static ShoppingCartItem ToModel(this Shop.ShoppingCarts.Entities.ShoppingCartItem entity) => public static ShoppingCartItem ToModel(this Entities.ShoppingCartItem entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -54,17 +45,18 @@ public static class EntityModeMappers
ShoppingCartId = entity.ShoppingCartId ShoppingCartId = entity.ShoppingCartId
}; };
public static ShoppingCart ToModel(this Shop.ShoppingCarts.Entities.ShoppingCart entity) => public static ShoppingCart ToModel(this Entities.ShoppingCart entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
UpdatedAt = entity.UpdatedAt, UpdatedAt = entity.UpdatedAt,
CustomerId = entity.CustomerId, CustomerId = entity.CustomerId,
OrderId = entity.OrderId OrderId = entity.OrderId,
QuoteId = entity.QuoteId
}; };
public static Quote ToModel(this Shop.Quotes.Entities.Quote entity) => public static Quote ToModel(this Entities.Quote entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -74,12 +66,10 @@ public static class EntityModeMappers
ExpiredAt = entity.ExpiredAt, ExpiredAt = entity.ExpiredAt,
Reason = entity.Reason, Reason = entity.Reason,
ShoppingCartId = entity.ShoppingCartId, ShoppingCartId = entity.ShoppingCartId,
Status = entity.Status, Status = entity.Status
InvoiceUrl = entity.InvoiceUrl,
OrderId = entity.OrderId
}; };
public static Notification ToModel(this Shop.Notifications.Entities.Notification entity) => public static Notification ToModel(this Entities.Notification entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -103,7 +93,7 @@ public static class EntityModeMappers
Errors = entity.Errors Errors = entity.Errors
}; };
public static Customer ToModel(this Shop.Customers.Entities.Customer entity) => public static Customer ToModel(this Entities.Customer entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -128,7 +118,7 @@ public static class EntityModeMappers
Whatsapp = entity.Whatsapp Whatsapp = entity.Whatsapp
}; };
public static Lead ToModel(this Shop.Leads.Entities.Lead entity) => public static Lead ToModel(this Entities.Lead entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -149,7 +139,7 @@ public static class EntityModeMappers
Status = entity.Status Status = entity.Status
}; };
public static Order ToModel(this Shop.Orders.Entities.Order entity) => public static Order ToModel(this Entities.Order entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -157,13 +147,16 @@ public static class EntityModeMappers
UpdatedAt = entity.UpdatedAt, UpdatedAt = entity.UpdatedAt,
CustomerId = entity.CustomerId, CustomerId = entity.CustomerId,
Notes = entity.Notes, Notes = entity.Notes,
RefundId = entity.RefundId,
QuoteId = entity.QuoteId,
Status = entity.Status, Status = entity.Status,
ShoppingCartId = entity.ShoppingCartId,
DepositRequired = entity.DepositRequired,
Requirements = entity.Requirements, Requirements = entity.Requirements,
Terms = entity.Terms, Terms = entity.Terms
InvoiceUrl = entity.InvoiceUrl
}; };
public static OrderRefund ToModel(this Shop.Orders.Entities.OrderRefund entity) => public static OrderRefund ToModel(this Entities.OrderRefund entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -173,19 +166,16 @@ public static class EntityModeMappers
Amount = entity.Amount Amount = entity.Amount
}; };
public static Product ToModel(this Shop.Products.Entities.Product entity) => public static Product ToModel(this Entities.Product entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
Name = entity.Name, Name = entity.Name,
Description = entity.Description, Description = entity.Description,
Active = entity.Active, Active = entity.Active
Summary = entity.Summary,
ImageUrl = entity.ImageUrl,
Thumbnails = entity.Thumbnails
}; };
public static ProductPrice ToModel(this Shop.Products.Entities.ProductPrice entity) => public static ProductPrice ToModel(this Entities.ProductPrice entity) =>
new() new()
{ {
Id = entity.Id, Id = entity.Id,
@@ -1,6 +1,6 @@
using LiteCharms.Features.HealthChecks; using LiteCharms.Infrastructure.HealthChecks;
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class HealthChecks public static class HealthChecks
{ {
@@ -0,0 +1,113 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<!-- Warnings And Exclusions -->
<PropertyGroup>
<NoWarn>$(NoWarn);MA0004</NoWarn>
<!-- https://github.com/dotnet/aspnetcore/issues/50836 -->
<NoWarn>$(NoWarn);AD0001</NoWarn>
<PublishTrimmed>true</PublishTrimmed>
<NoWarn>$(NoWarn);IL2080;IL2065;IL2075;IL2087;IL2057;IL2060;IL2070;IL2067;IL2072;IL2026;IL2104</NoWarn>
<NoWarn>$(NoWarn);IL2110;IL2111</NoWarn>
</PropertyGroup>
<!-- Nuget Package Details -->
<PropertyGroup>
<PackageId>LiteCharms.Extensions</PackageId>
<Version>1.0.20</Version>
<Authors>Khwezi Mngoma</Authors>
<Company>Lite Charms (PTY) Ltd</Company>
<Description>Extension 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>
<!-- 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.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageReference Include="Quartz.AspNetCore" Version="3.18.1" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
<!-- 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.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7">
<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>
<!-- Shared Usings -->
<ItemGroup>
<Using Include="Quartz" />
<Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.Extensions.Configuration" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<!-- Project References -->
<ItemGroup>
<ProjectReference Include="..\LiteCharms.Entities\LiteCharms.Entities.csproj" />
<ProjectReference Include="..\LiteCharms.Infrastructure\LiteCharms.Infrastructure.csproj" />
<ProjectReference Include="..\LiteCharms.Models\LiteCharms.Models.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class Monitoring public static class Monitoring
{ {
@@ -1,6 +1,6 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class Postgres public static class Postgres
{ {
@@ -1,7 +1,7 @@
using LiteCharms.Features.Quartz; using LiteCharms.Abstractions;
using LiteCharms.Features.Quartz.Abstractions; using LiteCharms.Infrastructure.Quartz;
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class Quartz public static class Quartz
{ {
@@ -1,9 +1,9 @@
using LiteCharms.Features.ServiceBus; using LiteCharms.Abstractions;
using LiteCharms.Features.ServiceBus.Abstractions; using LiteCharms.Infrastructure.ServiceBus;
using LiteCharms.Features.ServiceBus.Exchanges; using LiteCharms.Infrastructure.ServiceBus.Exchanges;
using LiteCharms.Features.ServiceBus.Queues; using LiteCharms.Infrastructure.ServiceBus.Queues;
namespace LiteCharms.Features.Extensions; namespace LiteCharms.Extensions;
public static class ServiceBus public static class ServiceBus
{ {
@@ -1,34 +0,0 @@
using LiteCharms.Features.Extensions;
namespace LiteCharms.Features.Tests;
public class CommonFixture : IDisposable
{
public IConfiguration Configuration { get; set; }
public IServiceProvider Services { get; set; }
public IMediator Mediator { get; set; }
public CommonFixture()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddUserSecrets<CommonFixture>()
.AddEnvironmentVariables()
.Build();
Services = new ServiceCollection()
.AddMediator()
.AddLogging()
.AddEmailServiceBus()
.AddShopDatabase(Configuration)
.AddEmailServices(Configuration)
.BuildServiceProvider();
Mediator = Services.GetRequiredService<IMediator>();
}
public void Dispose() { }
}
@@ -1,50 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<UserSecretsId>62fa604a-1340-4edb-9ddd-3305fcf46fca</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<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\LiteCharms.Features.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -1,19 +0,0 @@
using LiteCharms.Features.Notifications.Commands;
namespace LiteCharms.Features.Tests;
public class NotificationsFeatureTests(CommonFixture fixture) : IClassFixture<CommonFixture>
{
[Fact]
public async Task CreateNotificationCommand_ShouldSucceed()
{
var command = CreateNotification.Create(Models.NotificationDirection.Outgoing, "UnitTest", "khwezi@mngoma.co.za",
"CreateNotificationCommand_ShouldSucceed Test", "Test Message", Models.NotificationPlatforms.Email, Models.Priorities.Medium,
"Khngisa Shop - Test", "shop@litecharms.co.za", Guid.NewGuid().ToString(), Models.CorrelationIdTypes.None,
true, false);
var result = await fixture.Mediator.Send(command);
Assert.True(result.IsSuccess);
}
}
@@ -1,22 +0,0 @@
{
"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 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.CartPackages.Commands.Handlers; namespace LiteCharms.Features.CartPackages.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries; namespace LiteCharms.Features.CartPackages.Queries;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries; namespace LiteCharms.Features.CartPackages.Queries;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries; namespace LiteCharms.Features.CartPackages.Queries;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries.Handlers; namespace LiteCharms.Features.CartPackages.Queries.Handlers;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries.Handlers; namespace LiteCharms.Features.CartPackages.Queries.Handlers;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.CartPackages.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.CartPackages.Queries.Handlers; namespace LiteCharms.Features.CartPackages.Queries.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.Customers.Commands.Handlers; namespace LiteCharms.Features.Customers.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.Customers.Commands.Handlers; namespace LiteCharms.Features.Customers.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Customers.Models; using LiteCharms.Models;
namespace LiteCharms.Features.Customers.Queries; namespace LiteCharms.Features.Customers.Queries;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Customers.Models; using LiteCharms.Models;
namespace LiteCharms.Features.Customers.Queries; namespace LiteCharms.Features.Customers.Queries;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.Customers.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.Customers.Queries.Handlers; namespace LiteCharms.Features.Customers.Queries.Handlers;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.Customers.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.Customers.Queries.Handlers; namespace LiteCharms.Features.Customers.Queries.Handlers;
@@ -0,0 +1,61 @@
using LiteCharms.Features.Email.Commands;
using LiteCharms.Models.Configuraton.Email;
namespace LiteCharms.Features.Email.Commands.Handlers;
public class SendEmailCommandHandler(IOptions<SmtpSettings> smtpOptions) : IRequestHandler<SendEmailCommand, Result>
{
public async ValueTask<Result> Handle(SendEmailCommand request, CancellationToken cancellationToken)
{
try
{
var settings = smtpOptions.Value;
if(settings == null)
return Result.Fail(new Error("SMTP settings are not configured."));
if(settings.Credentials == null)
return Result.Fail(new Error("SMTP credentials are not configured."));
if(string.IsNullOrWhiteSpace(settings?.Credentials.Username) || string.IsNullOrWhiteSpace(settings.Credentials.Password))
return Result.Fail(new Error("SMTP credentials are incomplete."));
if(string.IsNullOrWhiteSpace(settings.Host) || settings.Port == 0)
return Result.Fail(new Error("SMTP host and port must be configured."));
var message = new MimeMessage();
message.From.Add(new MailboxAddress(request.SenderName, request.From!));
message.To.Add(new MailboxAddress(request.RecipientName, request.To!));
message.Subject = request.Subject!;
var bodyBuilder = new BodyBuilder();
if(request.Attachment?.Length > 0 && !string.IsNullOrEmpty(request.AttachmentFileName))
bodyBuilder.Attachments.Add(request.AttachmentFileName!, request.Attachment!, cancellationToken);
if (!request.IsHtml) bodyBuilder.TextBody = request.Message;
if (request.IsHtml) bodyBuilder.HtmlBody = request.Message;
message.Body = bodyBuilder.ToMessageBody();
using var client = new SmtpClient();
await client.ConnectAsync(settings.Host!, settings.Port, settings.UseSsl, cancellationToken);
await client.AuthenticateAsync(settings.Credentials!.Username!, settings.Credentials.Password!, cancellationToken);
var response = await client.SendAsync(message, cancellationToken);
bool emailSent = response.Contains("OK", StringComparison.InvariantCultureIgnoreCase);
await client.DisconnectAsync(true, cancellationToken);
return emailSent
? Result.Ok()
: Result.Fail(new Error("Failed to send email. SMTP response: " + response));
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -0,0 +1,79 @@
namespace LiteCharms.Features.Email.Commands;
public class SendEmailCommand : IRequest<Result>
{
public string? From { get; set; }
public string? SenderName { get; set; }
public string? To { get; set; }
public string? RecipientName { get; set; }
public string? Subject { get; set; }
public string? Message { get; set; }
public bool IsHtml { get; set; }
public Stream? Attachment { get; set; }
public string? AttachmentFileName { get; set; }
private SendEmailCommand(string from, string senderName, string to, string recipientName, string subject, string message, bool isHtml = false, Stream? attachment = null, string? attachmentFileName = null)
{
From = from;
To = to;
Subject = subject;
Message = message;
IsHtml = isHtml;
Attachment = attachment;
AttachmentFileName = attachmentFileName;
SenderName = senderName;
RecipientName = recipientName;
}
public static SendEmailCommand Create(string from, string senderName, string to, string recipientName, string subject, string message, bool isHtml = false, Stream? attachment = null, string? attachmentFileName = null)
{
if (string.IsNullOrWhiteSpace(from))
throw new ArgumentException("From address is required.");
if (string.IsNullOrWhiteSpace(senderName))
throw new ArgumentException("Sender name is required.");
if (!string.IsNullOrWhiteSpace(senderName) && senderName?.Length > 255)
throw new ArgumentException("Sender name cannot exceed 255 characters.");
if (string.IsNullOrWhiteSpace(to))
throw new ArgumentException("To address is required.");
if (string.IsNullOrWhiteSpace(recipientName))
throw new ArgumentException("Recipient name is required.");
if (!string.IsNullOrWhiteSpace(recipientName) && recipientName?.Length > 255)
throw new ArgumentException("Recipient name cannot exceed 255 characters.");
if (string.IsNullOrWhiteSpace(subject))
throw new ArgumentException("Subject is required.");
if (!string.IsNullOrWhiteSpace(subject) && subject?.Length > 2048)
throw new ArgumentException("Subject cannot exceed 2048 characters.");
if (string.IsNullOrWhiteSpace(message))
throw new ArgumentException("Message is required.");
if (message.Length > 10485760)
throw new ArgumentException("Message cannot exceed 10 MB.");
if (attachment != null && string.IsNullOrWhiteSpace(attachmentFileName))
throw new ArgumentException("Attachment file name must be provided when an attachment is included.");
if (attachment is not null && attachment.Length > 10485760)
throw new ArgumentException("Attachment cannot exceed 10 MB.");
if (!string.IsNullOrWhiteSpace(attachmentFileName) && attachmentFileName.Length > 255)
throw new ArgumentException("Attachment file name cannot exceed 255 characters.");
return new(from, senderName!, to, recipientName!, subject!, message, isHtml, attachment, attachmentFileName);
}
}
-192
View File
@@ -1,192 +0,0 @@
using LiteCharms.Features.Email.Configuration;
using LiteCharms.Features.Email.Extensions;
using LiteCharms.Features.Email.Models;
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email;
public class EmailService(IOptions<SmtpSettings> options) : IEmailService
{
private readonly SmtpSettings settings = options.Value;
private readonly SmtpClient client = new();
private readonly int sendMaxCount = 10;
private int sendCount = 0;
public EmailStatuses Status { get; private set; } = EmailStatuses.Disconnected;
public async Task<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default)
{
using var activity = EmailTelemetry.Source.StartActivity("Email Send");
activity?.SetTag("email.recipient", message.Recipient?.Address);
try
{
if (Status != EmailStatuses.Connected)
{
activity?.SetStatus(ActivityStatusCode.Error, "Disconnected");
return Result.Fail<Response>("Smtp service is disconnected.");
}
var email = new MimeMessage();
email.From.Add(new MailboxAddress(message.Sender!.Name, message.Sender.Address!));
email.To.Add(new MailboxAddress(message.Recipient!.Name, message.Recipient!.Address!));
email.Subject = message.Subject!;
var bodyBuilder = new BodyBuilder();
foreach (var attachment in message.Body?.Attachments!)
bodyBuilder.Attachments.Add(attachment.Name!, attachment.FileStream!, cancellationToken);
if (!message.Body.Properties.IsHtml) bodyBuilder.TextBody = message.Body.Message;
if (message.Body.Properties.IsHtml) bodyBuilder.HtmlBody = message.Body.Message;
email.Body = bodyBuilder.ToMessageBody();
var response = await client.SendAsync(email, cancellationToken);
bool emailSent = response.Contains("OK", StringComparison.InvariantCultureIgnoreCase);
message.Dispose();
Interlocked.Increment(ref sendCount);
if (sendCount % sendMaxCount == 0)
{
using var delayActivity = EmailTelemetry.Source.StartActivity("Rate Limit Pause");
sendCount = 0;
await Task.Delay(1000, cancellationToken);
}
if (emailSent)
{
EmailTelemetry.EmailsSent.Add(1, new TagList { { "host", settings.Host } });
return Result.Ok(Response.Create(EmailStatuses.Success));
}
await DisconnectAsync(cancellationToken);
if (response.Contains("421"))
{
Status = EmailStatuses.TooManyConnections;
return Result.Fail<Response>(response);
}
if (response.Contains("451"))
{
Status = EmailStatuses.ConnectionAborted;
return Result.Fail<Response>(response);
}
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "error_message", response } });
Status = EmailStatuses.Disconnected;
return Result.Fail<Response>("General error, disconnected");
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "exception", ex.GetType().Name } });
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async Task<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default)
{
using var activity = EmailTelemetry.Source.StartActivity("Email Connect");
activity?.SetTag("email.smtp.connect", settings.Host);
try
{
if (Status is EmailStatuses.Connected) return Result.Ok(Response.Create(Status));
await client.ConnectAsync(settings.Host!, settings.Port, settings.UseSsl, cancellationToken);
await client.AuthenticateAsync(settings.Credentials!.Username!, settings.Credentials.Password!, cancellationToken);
Status = EmailStatuses.Connected;
activity?.SetStatus(ActivityStatusCode.Ok, "Connected");
return Result.Ok(Response.Create(Status));
}
catch (MailKit.ProtocolException ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "exception", ex.GetType().Name } });
Status = EmailStatuses.ProtocolError;
return Result.Fail<Response>(new Error(ex.Message).CausedBy(ex));
}
catch (Exception ex) when (ex is MailKit.Security.SslHandshakeException || ex is MailKit.Security.AuthenticationException)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "exception", ex.GetType().Name } });
Status = EmailStatuses.AuthenticationError;
return Result.Fail<Response>(new Error(ex.Message).CausedBy(ex));
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "exception", ex.GetType().Name } });
Status = EmailStatuses.GeneralError;
return Result.Fail<Response>(new Error(ex.Message).CausedBy(ex));
}
}
public async Task<Result> DisconnectAsync(CancellationToken cancellationToken = default)
{
using var activity = EmailTelemetry.Source.StartActivity("Email Disconnect");
activity?.SetTag("email.smtp.disconnect", settings.Host);
try
{
if (Status is EmailStatuses.Disconnected) return Result.Ok();
await client.DisconnectAsync(true, cancellationToken);
activity?.SetStatus(ActivityStatusCode.Ok, "Disconnected");
Status = EmailStatuses.Disconnected;
return Result.Ok();
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.AddException(ex);
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "exception", ex.GetType().Name } });
Status = EmailStatuses.GeneralError;
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public void Dispose()
{
client.Dispose();
GC.SuppressFinalize(this);
}
}
@@ -1,22 +1,18 @@
using LiteCharms.Features.Notifications.Commands; using LiteCharms.Features.Notifications.Commands;
using LiteCharms.Features.Shop; using static LiteCharms.Abstractions.Constants;
using static LiteCharms.Features.Email.Extensions.Constants;
namespace LiteCharms.Features.Email.Events.Handlers; namespace LiteCharms.Features.Email.Events.Handlers;
// TODO: Inject the INotificationService
public class SendShopEmailEnquiryEventHandler(ISender mediator) : public class SendShopEmailEnquiryEventHandler(ISender mediator) :
INotificationHandler<SendShopEmailEnquiryEvent> INotificationHandler<SendShopEmailEnquiryEvent>
{ {
public async ValueTask Handle(SendShopEmailEnquiryEvent notification, CancellationToken cancellationToken) public async ValueTask Handle(SendShopEmailEnquiryEvent notification, CancellationToken cancellationToken)
{ {
// TODO: Refactor this to use the NotificationService var command = CreateNotificationCommand.Create(Models.NotificationDirection.Outgoing, notification.SenderName!,
var command = CreateNotification.Create(NotificationDirection.Outgoing, notification.SenderName!, notification.SenderAddress!, notification.Subject!, notification.Message!, Models.NotificationPlatforms.Email,
notification.SenderAddress!, notification.Subject!, notification.Message!, NotificationPlatforms.Email,
notification.Priority, ShopEmailFromName, ShopEmailFromAddress, Guid.CreateVersion7().ToString(), notification.Priority, ShopEmailFromName, ShopEmailFromAddress, Guid.CreateVersion7().ToString(),
CorrelationIdTypes.None, isInternal: true, isHtml: false); Models.CorrelationIdTypes.None, isInternal: true, isHtml: false);
// TODO: Remove, deprecated
await mediator.Send(command, cancellationToken); await mediator.Send(command, cancellationToken);
} }
} }
@@ -1,5 +1,5 @@
using LiteCharms.Features.Abstractions; using LiteCharms.Abstractions;
using LiteCharms.Features.Shop; using LiteCharms.Models;
namespace LiteCharms.Features.Email.Events; namespace LiteCharms.Features.Email.Events;
@@ -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,9 +0,0 @@
namespace LiteCharms.Features.Email.Extensions;
public static class EmailTelemetry
{
public static readonly ActivitySource Source = new("LiteCharms.EmailService");
public static readonly Meter Meter = new("LiteCharms.EmailService");
public static readonly Counter<long> EmailsSent = Meter.CreateCounter<long>("emails_sent_total", "count", "Total successful emails sent");
public static readonly Counter<long> EmailsFailed = Meter.CreateCounter<long>("emails_failed_total", "count", "Total failed email attempts");
}
@@ -1,19 +0,0 @@
using LiteCharms.Features.Email.Configuration;
namespace LiteCharms.Features.Email.Extensions;
public static class Setup
{
public static IServiceCollection AddEmailServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<SmtpSettings>(configuration.GetSection("Email"));
services.AddSingleton<IEmailService, EmailService>();
services.AddOpenTelemetry()
.WithTracing(tracing => tracing.AddSource("LiteCharms.EmailService"))
.WithMetrics(metrics => metrics.AddMeter("LiteCharms.EmailService"));
return services;
}
}
@@ -1,15 +0,0 @@
using LiteCharms.Features.Email.Models;
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email;
public interface IEmailService : IDisposable
{
EmailStatuses Status { get; }
Task<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default);
Task<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default);
Task<Result> DisconnectAsync(CancellationToken cancellationToken = default);
}
@@ -1,8 +0,0 @@
namespace LiteCharms.Features.Email.Models;
public class Attachment
{
public string? Name { get; set; }
public Stream? FileStream { get; set; }
}
-26
View File
@@ -1,26 +0,0 @@
namespace LiteCharms.Features.Email.Models;
public class Body : IDisposable
{
public string? Message { get; set; }
public ReadOnlyCollection<Attachment>? Attachments { get; set; }
public BodyProperties Properties { get; set; } = new();
public void Dispose()
{
if (Attachments is null) return;
foreach (var attachment in Attachments!)
{
if (attachment is not null)
{
attachment.FileStream!.Close();
attachment.FileStream!.Dispose();
}
}
GC.SuppressFinalize(this);
}
}
@@ -1,8 +0,0 @@
namespace LiteCharms.Features.Email.Models;
public class BodyProperties
{
public bool IsHtml { get; set; }
public bool HasAttachments { get; set; }
}
@@ -1,19 +0,0 @@
namespace LiteCharms.Features.Email.Models;
public class Message : IDisposable
{
public Party? Sender { get; set; }
public Party? Recipient { get; set; }
public string? Subject { get; set; }
public Body? Body { get; set; }
public void Dispose()
{
Body?.Dispose();
GC.SuppressFinalize(this);
}
}
@@ -1,8 +0,0 @@
namespace LiteCharms.Features.Email.Models;
public class Party
{
public string? Name { get; set; }
public string? Address { get; set; }
}
@@ -1,22 +0,0 @@
using LiteCharms.Features.Shop;
namespace LiteCharms.Features.Email.Models;
public class Response
{
public int Code { get; set; }
public string? Error { get; set; }
public EmailStatuses Status { get; set; }
private Response(EmailStatuses status, int code = 0, string? error = null)
{
Status = status;
Code = code;
Error = error;
}
public static Response Create(EmailStatuses status, int code = 0, string? error = null) =>
new(status, code, error);
}
-7
View File
@@ -1,7 +0,0 @@
namespace LiteCharms.Features.Extensions;
public static class Hash
{
public static Func<string?, string?> GenerateSha256HashString = (input) =>
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
}
@@ -1,5 +1,5 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Features.Utilities.Hash.Commands;
using LiteCharms.Features.Utilities.Hash.Commands; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.Leads.Commands.Handlers; namespace LiteCharms.Features.Leads.Commands.Handlers;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Infrastructure.Database;
namespace LiteCharms.Features.Leads.Commands.Handlers; namespace LiteCharms.Features.Leads.Commands.Handlers;
@@ -1,5 +1,4 @@
using LiteCharms.Features.Shop; using LiteCharms.Models;
using LiteCharms.Models;
namespace LiteCharms.Features.Leads.Commands; namespace LiteCharms.Features.Leads.Commands;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Leads.Models; using LiteCharms.Models;
namespace LiteCharms.Features.Leads.Queries; namespace LiteCharms.Features.Leads.Queries;
@@ -1,4 +1,4 @@
using LiteCharms.Features.Shop.Leads.Models; using LiteCharms.Models;
namespace LiteCharms.Features.Leads.Queries; namespace LiteCharms.Features.Leads.Queries;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.Leads.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.Leads.Queries.Handlers; namespace LiteCharms.Features.Leads.Queries.Handlers;
@@ -1,6 +1,6 @@
using LiteCharms.Extensions; using LiteCharms.Extensions;
using LiteCharms.Features.Shop.Leads.Models; using LiteCharms.Infrastructure.Database;
using LiteCharms.Features.Shop.Postgres; using LiteCharms.Models;
namespace LiteCharms.Features.Leads.Queries.Handlers; namespace LiteCharms.Features.Leads.Queries.Handlers;
+6 -88
View File
@@ -28,86 +28,6 @@
<None Include="..\icon.png" Pack="true" PackagePath="\" /> <None Include="..\icon.png" Pack="true" PackagePath="\" />
</ItemGroup> </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 --> <!-- Email -->
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="4.16.0" /> <PackageReference Include="MailKit" Version="4.16.0" />
@@ -130,18 +50,16 @@
<!-- Shared Usings --> <!-- Shared Usings -->
<ItemGroup> <ItemGroup>
<Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.Extensions.Hosting" />
<Using Include="System.Text" /> <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="System.Security.Cryptography" />
<Using Include="Microsoft.EntityFrameworkCore" />
<Using Include="Microsoft.Extensions.Options" /> <Using Include="Microsoft.Extensions.Options" />
<Using Include="Microsoft.Extensions.Logging" /> <Using Include="Microsoft.Extensions.Logging" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LiteCharms.Extensions\LiteCharms.Extensions.csproj" />
<ProjectReference Include="..\LiteCharms.Infrastructure\LiteCharms.Infrastructure.csproj" />
</ItemGroup>
</Project> </Project>
@@ -1,29 +0,0 @@
namespace LiteCharms.Features.Mediator;
public sealed class LoggingPipelineBehavior<TRequest, TResponse>(ILogger<LoggingPipelineBehavior<TRequest, TResponse>> logger) :
IPipelineBehavior<TRequest, TResponse?>
where TRequest : IRequest<TResponse>
where TResponse : ResultBase, new()
{
public async ValueTask<TResponse?> Handle(TRequest message, MessageHandlerDelegate<TRequest, TResponse?> next, CancellationToken cancellationToken)
{
TResponse? response = await next(message, cancellationToken);
if (response is null)
logger.LogCritical("{Request} {TypeName} was returned as null", typeof(TRequest).Name, typeof(TRequest).Name);
if(response?.IsFailed == true || response?.Errors?.Any() == true)
{
foreach (var error in response.Errors)
{
if (!string.IsNullOrWhiteSpace(error.Message))
logger.LogWarning("{Request} {Error}", typeof(TRequest).Name, error.Message);
if (error?.Reasons?.Count > 0)
error.Reasons.ForEach(r => logger.LogError("{Request} {Reason}", typeof(TRequest).Name, r.ToString()));
}
}
return response;
}
}

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