Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3b079ab4b |
+23
-7
@@ -16,13 +16,30 @@ steps:
|
|||||||
NEXUS_KEY: { from_secret: nexus_api_key }
|
NEXUS_KEY: { from_secret: nexus_api_key }
|
||||||
NEXUS_URL: https://nexus.khongisa.co.za/repository/nuget-hosted/
|
NEXUS_URL: https://nexus.khongisa.co.za/repository/nuget-hosted/
|
||||||
VERSION: 1.${DRONE_BUILD_NUMBER}.0
|
VERSION: 1.${DRONE_BUILD_NUMBER}.0
|
||||||
commands:
|
commands:
|
||||||
|
# Abstractions
|
||||||
|
- dotnet pack LiteCharms.Abstractions/LiteCharms.Abstractions.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
|
- dotnet nuget push dist/LiteCharms.Abstractions.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
|
|
||||||
|
# Models
|
||||||
|
- dotnet pack LiteCharms.Models/LiteCharms.Models.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
|
- dotnet nuget push dist/LiteCharms.Models.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
|
|
||||||
|
# Infrastructure
|
||||||
|
- dotnet pack LiteCharms.Infrastructure/LiteCharms.Infrastructure.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
|
- dotnet nuget push dist/LiteCharms.Infrastructure.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
|
|
||||||
|
# Features
|
||||||
- dotnet pack LiteCharms.Features/LiteCharms.Features.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
- 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 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
|
# Extensions
|
||||||
- dotnet pack LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
- dotnet pack LiteCharms.Extensions/LiteCharms.Extensions.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
- dotnet nuget push dist/LiteCharms.Features.MidrandBooks.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
- dotnet nuget push dist/LiteCharms.Extensions.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
|
|
||||||
|
# Entities
|
||||||
|
- dotnet pack LiteCharms.Entities/LiteCharms.Entities.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
|
- dotnet nuget push dist/LiteCharms.Entities.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
|
|
||||||
- name: gitea-tag-release
|
- name: gitea-tag-release
|
||||||
image: alpine/git
|
image: alpine/git
|
||||||
@@ -32,7 +49,6 @@ steps:
|
|||||||
GITEA_PASS: { from_secret: git_password }
|
GITEA_PASS: { from_secret: git_password }
|
||||||
VERSION: 1.${DRONE_BUILD_NUMBER}.0
|
VERSION: 1.${DRONE_BUILD_NUMBER}.0
|
||||||
commands:
|
commands:
|
||||||
- echo "169.255.58.144 gitea.khongisa.co.za" >> /etc/hosts
|
|
||||||
- apk add --no-cache curl
|
- apk add --no-cache curl
|
||||||
- git remote set-url origin https://$${GITEA_USER}:$${GITEA_PASS}@gitea.khongisa.co.za/litecharms/components.git
|
- git remote set-url origin https://$${GITEA_USER}:$${GITEA_PASS}@gitea.khongisa.co.za/litecharms/components.git
|
||||||
- git tag $VERSION
|
- git tag $VERSION
|
||||||
@@ -45,7 +61,7 @@ steps:
|
|||||||
\"tag_name\": \"$VERSION\",
|
\"tag_name\": \"$VERSION\",
|
||||||
\"target_commitish\": \"${DRONE_COMMIT_SHA}\",
|
\"target_commitish\": \"${DRONE_COMMIT_SHA}\",
|
||||||
\"name\": \"Library Suite $VERSION\",
|
\"name\": \"Library Suite $VERSION\",
|
||||||
\"body\": \"### Published NuGet Packages\nAll packages versioned as **$VERSION**:\n* LiteCharms.Features\n* LiteCharms.Features.TechShop\n* LiteCharms.Features.MidrandBooks\n\n[View in Nexus](https://nexus.khongisa.co.za/repository/nuget-group/)\",
|
\"body\": \"### Published NuGet Packages\nAll packages versioned as **$VERSION**:\n* LiteCharms.Abstractions\n* LiteCharms.Models\n* LiteCharms.Infrastructure\n* LiteCharms.Features\n* LiteCharms.Extensions\n* LiteCharms.Entities\n\n[View in Nexus](https://nexus.khongisa.co.za/repository/nuget-group/)\",
|
||||||
\"draft\": false,
|
\"draft\": false,
|
||||||
\"prerelease\": false
|
\"prerelease\": false
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -3,45 +3,6 @@ root = true
|
|||||||
|
|
||||||
# C# files
|
# C# files
|
||||||
[*.cs]
|
[*.cs]
|
||||||
# IDE0250: Prefer make struct 'readonly'
|
|
||||||
dotnet_diagnostic.IDE0250.severity = warning
|
|
||||||
|
|
||||||
# IDE0060: Remove unused parameters (Good cleanup pairing)
|
|
||||||
dotnet_diagnostic.IDE0060.severity = warning
|
|
||||||
|
|
||||||
# CA1852: Seal internal types (Available in modern .NET)
|
|
||||||
dotnet_diagnostic.CA1852.severity = warning
|
|
||||||
|
|
||||||
# MA0018: Add sealed modifier to types that are never inherited
|
|
||||||
dotnet_diagnostic.MA0018.severity = warning
|
|
||||||
|
|
||||||
# Enforce that classes should be sealed
|
|
||||||
dotnet_diagnostic.MA0053.severity = warning
|
|
||||||
|
|
||||||
# CRITICAL: Force the analyzer to also flag PUBLIC classes, not just internal ones
|
|
||||||
meziantou_analyzer.MA0053.public_class_should_be_sealed = true
|
|
||||||
MA0053.public_class_should_be_sealed = true
|
|
||||||
|
|
||||||
# Keep the rule active as a warning by default
|
|
||||||
dotnet_diagnostic.MA0048.severity = warning
|
|
||||||
|
|
||||||
# Specific exclusions for Meziantou.Analyzer MA0048
|
|
||||||
# Disable the rule for enums
|
|
||||||
meziantou_analyzer.MA0048.exclude_enums = true
|
|
||||||
|
|
||||||
# Disable the rule for records
|
|
||||||
meziantou_analyzer.MA0048.exclude_records = true
|
|
||||||
|
|
||||||
#EXCLUDE specific files that are meant to hold grouped enums/records
|
|
||||||
dotnet_diagnostic.MA0048.severity = warning
|
|
||||||
|
|
||||||
# Disable the requirement to specify ConfigureAwait(false)
|
|
||||||
dotnet_diagnostic.MA0004.severity = none
|
|
||||||
|
|
||||||
# ALTERNATIVE: Exclude any file ending with 'Enums.cs' or 'Records.cs'
|
|
||||||
# (e.g., BillingEnums.cs, CustomerRecords.cs)
|
|
||||||
[**/*{Enums,Records}.cs]
|
|
||||||
dotnet_diagnostic.MA0048.severity = none
|
|
||||||
|
|
||||||
#### Core EditorConfig Options ####
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using static LiteCharms.Abstractions.Timezones;
|
||||||
|
|
||||||
|
namespace LiteCharms.Abstractions;
|
||||||
|
|
||||||
|
public abstract class EventBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.CreateVersion7();
|
||||||
|
|
||||||
|
public DateTimeOffset EnqueueAt { get; set; } = SouthAfricanTimeZone.UtcNow();
|
||||||
|
|
||||||
|
public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString();
|
||||||
|
}
|
||||||
+1
-3
@@ -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
-3
@@ -1,6 +1,4 @@
|
|||||||
using LiteCharms.Features.Abstractions;
|
namespace LiteCharms.Abstractions;
|
||||||
|
|
||||||
namespace LiteCharms.Features.ServiceBus.Abstractions;
|
|
||||||
|
|
||||||
public interface IEventBus
|
public interface IEventBus
|
||||||
{
|
{
|
||||||
+1
-3
@@ -1,6 +1,4 @@
|
|||||||
using LiteCharms.Features.Abstractions;
|
namespace LiteCharms.Abstractions;
|
||||||
|
|
||||||
namespace LiteCharms.Features.ServiceBus.Abstractions;
|
|
||||||
|
|
||||||
public interface IEventBusQueue
|
public interface IEventBusQueue
|
||||||
{
|
{
|
||||||
+1
-3
@@ -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
|
||||||
{
|
{
|
||||||
@@ -20,8 +20,8 @@ public static class Timezones
|
|||||||
? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset)
|
? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset)
|
||||||
: new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset);
|
: new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset);
|
||||||
|
|
||||||
return DateTimeOffset.Parse(localised!, CultureInfo.InvariantCulture);
|
return DateTimeOffset.Parse(localised!);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DateTime UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone).UtcDateTime;
|
public static DateTimeOffset UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone);
|
||||||
}
|
}
|
||||||
+9
-4
@@ -1,14 +1,14 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Customers.Entities;
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Customer> builder)
|
public void Configure(EntityTypeBuilder<Customer> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("Customers");
|
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).HasDefaultValueSql(null);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
-10
@@ -1,15 +1,15 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Leads.Entities;
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
public class LeadConfiguration : IEntityTypeConfiguration<Lead>
|
public class LeadConfiguration : IEntityTypeConfiguration<Lead>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Lead> builder)
|
public void Configure(EntityTypeBuilder<Lead> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("Leads");
|
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).HasDefaultValueSql(null);
|
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
|
||||||
builder.Property(f => f.CustomerId);
|
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);
|
||||||
builder.Property(f => f.WebClickId);
|
builder.Property(f => f.WebClickId);
|
||||||
@@ -22,10 +22,5 @@ public class LeadConfiguration : IEntityTypeConfiguration<Lead>
|
|||||||
builder.Property(f => f.ClickLocation);
|
builder.Property(f => f.ClickLocation);
|
||||||
builder.Property(f => f.Status).IsRequired();
|
builder.Property(f => f.Status).IsRequired();
|
||||||
builder.Property(f => f.AttributionHash).IsRequired(true);
|
builder.Property(f => f.AttributionHash).IsRequired(true);
|
||||||
|
|
||||||
builder.HasOne(f => f.Customer)
|
|
||||||
.WithMany(f => f.Leads)
|
|
||||||
.HasForeignKey(f => f.CustomerId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-6
@@ -1,22 +1,24 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Notifications.Entities;
|
using LiteCharms.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
|
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<Notification> builder)
|
public void Configure(EntityTypeBuilder<Notification> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("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).HasDefaultValueSql(null);
|
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>();
|
||||||
builder.Property(f => f.CorrelationIdType).IsRequired().HasConversion<int>();
|
builder.Property(f => f.CorrelationIdType).IsRequired().HasConversion<int>();
|
||||||
builder.Property(f => f.SenderAddress).IsRequired();
|
builder.Property(f => f.Sender).IsRequired();
|
||||||
builder.Property(f => f.Subject).IsRequired();
|
builder.Property(f => f.Subject).IsRequired();
|
||||||
builder.Property(f => f.Message).IsRequired();
|
builder.Property(f => f.Message).IsRequired();
|
||||||
builder.Property(f => f.RecipientName).IsRequired();
|
builder.Property(f => f.Recipient).IsRequired();
|
||||||
builder.Property(f => f.RecipientAddress).IsRequired();
|
builder.Property(f => f.RecipientAddress).IsRequired();
|
||||||
builder.Property(f => f.CorrelationId).IsRequired();
|
builder.Property(f => f.CorrelationId).IsRequired();
|
||||||
builder.Property(f => f.IsHtml).HasDefaultValue(false);
|
builder.Property(f => f.IsHtml).HasDefaultValue(false);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-8
@@ -1,21 +1,19 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Orders.Entities;
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund>
|
public class OrderRefundConfiguration : IEntityTypeConfiguration<OrderRefund>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<OrderRefund> builder)
|
public void Configure(EntityTypeBuilder<OrderRefund> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("OrderRefunds");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-12
@@ -1,4 +1,4 @@
|
|||||||
namespace LiteCharms.Features.TechShop.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-7
@@ -1,23 +1,22 @@
|
|||||||
namespace LiteCharms.Features.TechShop.Products.Entities;
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
||||||
{
|
{
|
||||||
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("ProductPrices");
|
builder.ToTable(nameof(ProductPrice));
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
builder.HasKey(f => f.Id);
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
|
public class ShoppingCartPackageConfiguration : IEntityTypeConfiguration<ShoppingCartPackage>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ShoppingCartPackage> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable(nameof(ShoppingCartPackage));
|
||||||
|
|
||||||
|
builder.HasKey(f => f.Id);
|
||||||
|
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
|
||||||
|
builder.Property(f => f.ShoppingCartId).IsRequired();
|
||||||
|
builder.Property(f => f.PackageId).IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(f => f.Package)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(f => f.PackageId)
|
||||||
|
.OnDelete(DeleteBehavior.NoAction);
|
||||||
|
|
||||||
|
builder.HasOne(f => f.ShoppingCart)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(f => f.ShoppingCartId)
|
||||||
|
.OnDelete(DeleteBehavior.NoAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-5
@@ -1,9 +1,6 @@
|
|||||||
using LiteCharms.Features.TechShop.Leads.Entities;
|
using LiteCharms.Entities.Configuration;
|
||||||
using LiteCharms.Features.TechShop.Orders.Entities;
|
|
||||||
using LiteCharms.Features.TechShop.Quotes.Entities;
|
|
||||||
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.TechShop.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.TechShop.Customers.Entities;
|
using LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
namespace LiteCharms.Features.TechShop.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>
|
||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
namespace LiteCharms.Features.TechShop.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;
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
namespace LiteCharms.Features.TechShop.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
|
||||||
{
|
{
|
||||||
|
|
||||||
public virtual Order? Order { get; set; }
|
public virtual Order? Order { get; set; }
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
namespace LiteCharms.Features.TechShop.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
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
namespace LiteCharms.Features.TechShop.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
|
||||||
{
|
{
|
||||||
|
|
||||||
public virtual ICollection<ProductPrice>? ProductPrices { get; set; }
|
public virtual ICollection<ProductPrice>? ProductPrices { get; set; }
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.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
|
||||||
+4
-6
@@ -1,15 +1,13 @@
|
|||||||
using LiteCharms.Features.TechShop.Customers.Entities;
|
using LiteCharms.Entities.Configuration;
|
||||||
using LiteCharms.Features.TechShop.Orders.Entities;
|
|
||||||
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.TechShop.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; }
|
||||||
}
|
}
|
||||||
+3
-5
@@ -1,8 +1,6 @@
|
|||||||
using LiteCharms.Features.TechShop.Customers.Entities;
|
using LiteCharms.Entities.Configuration;
|
||||||
using LiteCharms.Features.TechShop.Orders.Entities;
|
|
||||||
using LiteCharms.Features.TechShop.Quotes.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.TechShop.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; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace LiteCharms.Entities;
|
||||||
|
|
||||||
|
public class ShoppingCartItem : Models.ShoppingCartItem
|
||||||
|
{
|
||||||
|
public virtual ShoppingCart? ShoppingCart { get; set; }
|
||||||
|
|
||||||
|
public virtual ProductPrice? ProductPrice { get; set; }
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
using LiteCharms.Features.TechShop.CartPackages.Entities;
|
using LiteCharms.Entities.Configuration;
|
||||||
|
|
||||||
namespace LiteCharms.Features.TechShop.ShoppingCarts.Entities;
|
namespace LiteCharms.Entities;
|
||||||
|
|
||||||
[EntityTypeConfiguration<ShoppingCartPackageConfiguration, ShoppingCartPackage>]
|
[EntityTypeConfiguration<ShoppingCartPackageConfiguration, ShoppingCartPackage>]
|
||||||
public class ShoppingCartPackage : Models.ShoppingCartPackage
|
public class ShoppingCartPackage : Models.ShoppingCartPackage
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-48
@@ -1,37 +1,29 @@
|
|||||||
using LiteCharms.Features.TechShop.CartPackages.Models;
|
using LiteCharms.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.TechShop.Extensions;
|
namespace LiteCharms.Extensions;
|
||||||
|
|
||||||
public static class Mappers
|
public static class EntityModeMappers
|
||||||
{
|
{
|
||||||
public static ShoppingCartPackage ToModel(this ShoppingCarts.Entities.ShoppingCartPackage entity) =>
|
public static ShoppingCartPackage ToModel(this Entities.ShoppingCartPackage entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
CreatedAt = entity.CreatedAt,
|
CreatedAt = entity.CreatedAt,
|
||||||
PackageId = entity.PackageId,
|
PackageId = entity.PackageId,
|
||||||
ShoppingCartId = entity.ShoppingCartId
|
ShoppingCartId = entity.ShoppingCartId
|
||||||
};
|
};
|
||||||
|
|
||||||
public static PackageItem ToModel(this CartPackages.Entities.PackageItem entity) =>
|
public static PackageItem ToModel(this Entities.PackageItem entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
Active = entity.Active,
|
Active = entity.Active,
|
||||||
CreatedAt = entity.CreatedAt,
|
CreatedAt = entity.CreatedAt,
|
||||||
PackageId = entity.PackageId,
|
PackageId = entity.PackageId,
|
||||||
ProductPriceId = entity.ProductPriceId
|
ProductPriceId = entity.ProductPriceId
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Package ToModel(this CartPackages.Entities.Package entity) =>
|
public static Package ToModel(this Entities.Package entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -39,12 +31,10 @@ public static class Mappers
|
|||||||
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 ShoppingCarts.Entities.ShoppingCartItem entity) =>
|
public static ShoppingCartItem ToModel(this Entities.ShoppingCartItem entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -52,20 +42,21 @@ public static class Mappers
|
|||||||
UpdatedAt = entity.UpdatedAt,
|
UpdatedAt = entity.UpdatedAt,
|
||||||
ProductPriceId = entity.ProductPriceId,
|
ProductPriceId = entity.ProductPriceId,
|
||||||
Quantity = entity.Quantity,
|
Quantity = entity.Quantity,
|
||||||
ShoppingCartId = entity.ShoppingCartId
|
ShoppingCartId = entity.ShoppingCartId
|
||||||
};
|
};
|
||||||
|
|
||||||
public static ShoppingCart ToModel(this 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 Quotes.Entities.Quote entity) =>
|
public static Quote ToModel(this Entities.Quote entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -75,12 +66,10 @@ public static class Mappers
|
|||||||
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 Notifications.Entities.Notification entity) =>
|
public static Notification ToModel(this Entities.Notification entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -90,9 +79,9 @@ public static class Mappers
|
|||||||
CorrelationId = entity.CorrelationId,
|
CorrelationId = entity.CorrelationId,
|
||||||
CorrelationIdType = entity.CorrelationIdType,
|
CorrelationIdType = entity.CorrelationIdType,
|
||||||
IsInternal = entity.IsInternal,
|
IsInternal = entity.IsInternal,
|
||||||
SenderAddress = entity.SenderAddress,
|
Sender = entity.Sender,
|
||||||
Platform = entity.Platform,
|
Platform = entity.Platform,
|
||||||
RecipientName = entity.RecipientName,
|
Recipient = entity.Recipient,
|
||||||
Subject = entity.Subject,
|
Subject = entity.Subject,
|
||||||
Processed = entity.Processed,
|
Processed = entity.Processed,
|
||||||
SenderName = entity.SenderName,
|
SenderName = entity.SenderName,
|
||||||
@@ -104,7 +93,7 @@ public static class Mappers
|
|||||||
Errors = entity.Errors
|
Errors = entity.Errors
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Customer ToModel(this Customers.Entities.Customer entity) =>
|
public static Customer ToModel(this Entities.Customer entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -129,7 +118,7 @@ public static class Mappers
|
|||||||
Whatsapp = entity.Whatsapp
|
Whatsapp = entity.Whatsapp
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Lead ToModel(this Leads.Entities.Lead entity) =>
|
public static Lead ToModel(this Entities.Lead entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -147,10 +136,10 @@ public static class Mappers
|
|||||||
ClickId = entity.ClickId,
|
ClickId = entity.ClickId,
|
||||||
TargetId = entity.TargetId,
|
TargetId = entity.TargetId,
|
||||||
WebClickId = entity.WebClickId,
|
WebClickId = entity.WebClickId,
|
||||||
Status = entity.Status
|
Status = entity.Status
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Order ToModel(this Orders.Entities.Order entity) =>
|
public static Order ToModel(this Entities.Order entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -158,37 +147,35 @@ public static class Mappers
|
|||||||
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 Orders.Entities.OrderRefund entity) =>
|
public static OrderRefund ToModel(this Entities.OrderRefund entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
CreatedAt = entity.CreatedAt,
|
CreatedAt = entity.CreatedAt,
|
||||||
OrderId = entity.OrderId,
|
OrderId = entity.OrderId,
|
||||||
Reason = entity.Reason,
|
Reason = entity.Reason,
|
||||||
Amount = entity.Amount
|
Amount = entity.Amount
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Products.Models.Product ToModel(this Products.Entities.Product entity) =>
|
public static Product ToModel(this Entities.Product entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
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,
|
|
||||||
Metadata = entity.Metadata,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Products.Models.ProductPrice ToModel(this Products.Entities.ProductPrice entity) =>
|
public static ProductPrice ToModel(this Entities.ProductPrice entity) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -197,6 +184,6 @@ public static class Mappers
|
|||||||
Active = entity.Active,
|
Active = entity.Active,
|
||||||
CreatedAt = entity.CreatedAt,
|
CreatedAt = entity.CreatedAt,
|
||||||
Discount = entity.Discount,
|
Discount = entity.Discount,
|
||||||
UpdatedAt = entity.UpdatedAt
|
UpdatedAt = entity.UpdatedAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using LiteCharms.Infrastructure.HealthChecks;
|
||||||
|
|
||||||
|
namespace LiteCharms.Extensions;
|
||||||
|
|
||||||
|
public static class HealthChecks
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddQuartzHealtchCheck(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHealthChecks().AddCheck<QuartzHealthCheck>("Quartz");
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddPostgresHealtchCheck(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHealthChecks().AddCheck<PostgresHealthCheck>("PostgreSQL");
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddHealthChecksSupport(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddHealthChecks()
|
||||||
|
.AddCheck("Self", () => HealthCheckResult.Healthy());
|
||||||
|
|
||||||
|
//services.AddHealthChecksUI(setup =>
|
||||||
|
//{
|
||||||
|
// setup.AddHealthCheckEndpoint("Lead Generator", $"{configuration["ASPNETCORE_URLS"]}/health");
|
||||||
|
// setup.SetEvaluationTimeInSeconds(15);
|
||||||
|
//}).AddInMemoryStorage(databaseName: "healthuidb");
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
-86
@@ -6,16 +6,26 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<SignAssembly>True</SignAssembly>
|
<SignAssembly>True</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
|
||||||
<UserSecretsId>fbd8f4a2-0420-44e2-baff-4678d9e7eee1</UserSecretsId>
|
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||||
</PropertyGroup>
|
</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 -->
|
<!-- Nuget Package Details -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>LiteCharms.Features.TechShop</PackageId>
|
<PackageId>LiteCharms.Extensions</PackageId>
|
||||||
<Version>1.0.20</Version>
|
<Version>1.0.20</Version>
|
||||||
<Authors>Khwezi Mngoma</Authors>
|
<Authors>Khwezi Mngoma</Authors>
|
||||||
<Company>Lite Charms (PTY) Ltd</Company>
|
<Company>Lite Charms (PTY) Ltd</Company>
|
||||||
<Description>TechShop feature components for Lite Charms applications.</Description>
|
<Description>Extension components for Lite Charms applications.</Description>
|
||||||
<PackageProjectUrl>https://gitea.khongisa.co.za/litecharms/components</PackageProjectUrl>
|
<PackageProjectUrl>https://gitea.khongisa.co.za/litecharms/components</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://gitea.khongisa.co.za/litecharms/components.git</RepositoryUrl>
|
<RepositoryUrl>https://gitea.khongisa.co.za/litecharms/components.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -23,43 +33,12 @@
|
|||||||
<PackageTags>utility;dotnet</PackageTags>
|
<PackageTags>utility;dotnet</PackageTags>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Addresses\**" />
|
|
||||||
<EmbeddedResource Remove="Addresses\**" />
|
|
||||||
<None Remove="Addresses\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
|
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
|
||||||
<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 -->
|
<!-- Health Checks -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
|
||||||
@@ -67,9 +46,10 @@
|
|||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Core" 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.Data" Version="9.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" 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.EntityFrameworkCore.InMemory" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.8" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.18.1" />
|
||||||
|
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.18.1" />
|
||||||
|
|
||||||
<!-- Global Usings -->
|
<!-- Global Usings -->
|
||||||
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
|
||||||
@@ -95,13 +75,13 @@
|
|||||||
|
|
||||||
<!-- Database -->
|
<!-- Database -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -114,58 +94,20 @@
|
|||||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
|
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
|
||||||
</ItemGroup>
|
</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 -->
|
<!-- Shared Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="Quartz" />
|
||||||
<Using Include="Microsoft.AspNetCore.Builder" />
|
<Using Include="Microsoft.AspNetCore.Builder" />
|
||||||
<Using Include="Microsoft.Extensions.Hosting" />
|
<Using Include="Microsoft.Extensions.Configuration" />
|
||||||
<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="Microsoft.Extensions.DependencyInjection" />
|
||||||
<Using Include="System.Security.Cryptography" />
|
|
||||||
<Using Include="Microsoft.Extensions.Options" />
|
|
||||||
<Using Include="Microsoft.Extensions.Logging" />
|
<Using Include="Microsoft.Extensions.Logging" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Project References -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="appsettings.json">
|
<ProjectReference Include="..\LiteCharms.Entities\LiteCharms.Entities.csproj" />
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<ProjectReference Include="..\LiteCharms.Infrastructure\LiteCharms.Infrastructure.csproj" />
|
||||||
</None>
|
<ProjectReference Include="..\LiteCharms.Models\LiteCharms.Models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace LiteCharms.Features.Extensions;
|
namespace LiteCharms.Extensions;
|
||||||
|
|
||||||
public static class Monitoring
|
public static class Monitoring
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using LiteCharms.Infrastructure.Database;
|
||||||
|
|
||||||
|
namespace LiteCharms.Extensions;
|
||||||
|
|
||||||
|
public static class Postgres
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddShopDatabase(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddPooledDbContextFactory<ShopDbContext>(options =>
|
||||||
|
options.UseNpgsql(configuration.GetConnectionString("PostgresShop")));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
using LiteCharms.Features.Quartz;
|
using LiteCharms.Abstractions;
|
||||||
using LiteCharms.Features.Quartz.Abstractions;
|
using LiteCharms.Infrastructure.Quartz;
|
||||||
using static LiteCharms.Features.Extensions.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.Extensions;
|
namespace LiteCharms.Extensions;
|
||||||
|
|
||||||
public static class Quartz
|
public static class Quartz
|
||||||
{
|
{
|
||||||
public const string TechShopSchedulerName = "tech-shop";
|
private const string databaseConfigName = "PostgresScheduler";
|
||||||
public const string MidrandShopSchedulerName = "midrand-shop";
|
|
||||||
|
|
||||||
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
|
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
|
var connectionString = configuration.GetConnectionString(databaseConfigName);
|
||||||
|
|
||||||
services.ConfigureCommon();
|
services.ConfigureCommon();
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ public static class Quartz
|
|||||||
storage.UseClustering(cluster =>
|
storage.UseClustering(cluster =>
|
||||||
{
|
{
|
||||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||||
cluster.CheckinMisfireThreshold = TimeSpan.FromSeconds(90);
|
cluster.CheckinMisfireThreshold = TimeSpan.FromSeconds(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -46,12 +44,10 @@ public static class Quartz
|
|||||||
|
|
||||||
public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration)
|
public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var connectionString = configuration.GetConnectionString(SchedulerDbConfigName);
|
var connectionString = configuration.GetConnectionString(databaseConfigName);
|
||||||
|
|
||||||
services.ConfigureCommon();
|
services.ConfigureCommon();
|
||||||
|
|
||||||
services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
|
|
||||||
|
|
||||||
services.AddQuartz(config =>
|
services.AddQuartz(config =>
|
||||||
{
|
{
|
||||||
config.SchedulerName = schedulerName;
|
config.SchedulerName = schedulerName;
|
||||||
@@ -64,8 +60,6 @@ public static class Quartz
|
|||||||
config.UseDefaultThreadPool(options => options.MaxConcurrency = 1);
|
config.UseDefaultThreadPool(options => options.MaxConcurrency = 1);
|
||||||
config.UseTimeZoneConverter();
|
config.UseTimeZoneConverter();
|
||||||
|
|
||||||
config.SetProperty("quartz.jobStore.misfireThreshold", TimeSpan.FromMinutes(2).TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
|
|
||||||
|
|
||||||
config.UsePersistentStore(storage =>
|
config.UsePersistentStore(storage =>
|
||||||
{
|
{
|
||||||
storage.PerformSchemaValidation = false;
|
storage.PerformSchemaValidation = false;
|
||||||
@@ -78,7 +72,7 @@ public static class Quartz
|
|||||||
storage.UseClustering(cluster =>
|
storage.UseClustering(cluster =>
|
||||||
{
|
{
|
||||||
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
cluster.CheckinInterval = TimeSpan.FromSeconds(30);
|
||||||
cluster.CheckinMisfireThreshold = TimeSpan.FromSeconds(90);
|
cluster.CheckinMisfireThreshold = TimeSpan.FromSeconds(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -92,14 +86,14 @@ public static class Quartz
|
|||||||
{
|
{
|
||||||
options.Scheduling.IgnoreDuplicates = true;
|
options.Scheduling.IgnoreDuplicates = true;
|
||||||
options.Scheduling.OverWriteExistingData = true;
|
options.Scheduling.OverWriteExistingData = true;
|
||||||
|
|
||||||
options["quartz.plugin.jobHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz.Plugins";
|
options["quartz.plugin.jobHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz.Plugins";
|
||||||
options["quartz.plugin.triggerHistory.type"] = "Quartz.Plugin.History.LoggingTriggerHistoryPlugin, Quartz.Plugins";
|
options["quartz.plugin.triggerHistory.type"] = "Quartz.Plugin.History.LoggingTriggerHistoryPlugin, Quartz.Plugins";
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<RetryJobListener>();
|
services.AddTransient<RetryJobListener>();
|
||||||
services.AddTransient<IJobOrchestrator, JobOrchestrator>();
|
services.AddTransient<IJobOrchestrator, JobOrchestrator>();
|
||||||
|
services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,81 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Authors;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class AuthorServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly AuthorService authorService = fixture.Services.GetRequiredService<AuthorService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateAuthorAsync_ShouldReturn_ResultWithAuthorId()
|
|
||||||
{
|
|
||||||
var request = new CreateAuthor
|
|
||||||
{
|
|
||||||
Name = "John",
|
|
||||||
LastName = "Doe",
|
|
||||||
Company = "Solo Publishers",
|
|
||||||
Email = "solo@publishers.co.za",
|
|
||||||
PublisherType = PublisherTypes.Independent,
|
|
||||||
ImageUrl = ""
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await authorService.CreateAuthorAsync(request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateAuthorAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new UpdateAuthor
|
|
||||||
{
|
|
||||||
Name = "Jane",
|
|
||||||
LastName = "Doe",
|
|
||||||
Company = "Solo Publishers",
|
|
||||||
Email = "solo@publishers.co.za",
|
|
||||||
PublisherType = PublisherTypes.Independent,
|
|
||||||
ImageUrl = ""
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await authorService.UpdateAuthorAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetAuthors_ShouldReturn_ResultWithAuthorList()
|
|
||||||
{
|
|
||||||
var range = new DateRange
|
|
||||||
{
|
|
||||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
|
||||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
|
||||||
MaxRecords = 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await authorService.GetAuthorsAsync(range, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetAuthorAsync_ShouldReturn_ResultWithAuthor()
|
|
||||||
{
|
|
||||||
var result = await authorService.GetAuthorAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateAuthorStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await authorService.UpdateAuthorStatusAsync(1, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class BooksServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly BooksService bookService = fixture.Services.GetRequiredService<BooksService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateBookAsync_ShouldReturn_ResultWithBookId()
|
|
||||||
{
|
|
||||||
var result = await bookService.CreateBookAsync(1, 2, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetBookAsync_ShouldReturn_ResultWithBook()
|
|
||||||
{
|
|
||||||
var result = await bookService.GetBookAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetBooksByAuthorAsync_ShouldReturn_ResultWithAuthorBooks()
|
|
||||||
{
|
|
||||||
var result = await bookService.GetBooksByAuthorAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetPublishedBooksAsync_ShouldReturn_ResultWithBublishedBooks()
|
|
||||||
{
|
|
||||||
var result = await bookService.GetPublishedBooksAsync(0, 1000, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateBookStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await bookService.UpdateBookStatusAsync(1, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using LiteCharms.Features.Extensions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
public class Fixture : IDisposable
|
|
||||||
{
|
|
||||||
public IConfiguration Configuration { get; set; }
|
|
||||||
|
|
||||||
public IServiceProvider Services { get; set; }
|
|
||||||
|
|
||||||
public IMediator Mediator { get; set; }
|
|
||||||
|
|
||||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
|
||||||
|
|
||||||
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
|
||||||
|
|
||||||
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()
|
|
||||||
.AddEmailServiceBus()
|
|
||||||
.AddGarageS3(Configuration)
|
|
||||||
.AddMidrandShopDatabase(Configuration)
|
|
||||||
.AddEmailServices(Configuration)
|
|
||||||
.AddSingleton(Configuration)
|
|
||||||
.AddShopServices()
|
|
||||||
.BuildServiceProvider();
|
|
||||||
|
|
||||||
Mediator = Services.GetRequiredService<IMediator>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
public class IntegrationFactAttribute : FactAttribute
|
|
||||||
{
|
|
||||||
public IntegrationFactAttribute()
|
|
||||||
{
|
|
||||||
if(!Debugger.IsAttached)
|
|
||||||
Skip = "This test requires the debugger to be attached.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Customers;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class CustomerServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly CustomerService customerService = fixture.Services.GetRequiredService<CustomerService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateCustomerAsync_ShouldReturn_ResultWithCustomerId()
|
|
||||||
{
|
|
||||||
var request = new CreateCustomer
|
|
||||||
{
|
|
||||||
Company = "Book Lovers",
|
|
||||||
Email = "hank@booklovers.com",
|
|
||||||
Phone = "555 1245 8577",
|
|
||||||
Website = "https://www.booklovers.com"
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.CreateCustomerAsync(request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateCustomerContactAsync_ShouldReturn_ResultWithCustomerContactId()
|
|
||||||
{
|
|
||||||
var request = new CreateCustomerContact
|
|
||||||
{
|
|
||||||
Name = "Sipho",
|
|
||||||
LastName = "Madlanga",
|
|
||||||
Phone = "0710857365",
|
|
||||||
Email = "sipho@madlanga.africa",
|
|
||||||
Type = ContactTypes.Business
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.CreateCustomerContactAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddressId()
|
|
||||||
{
|
|
||||||
var request = new CreateCustomerAddress
|
|
||||||
{
|
|
||||||
Name = "Business",
|
|
||||||
BuildingType = AddressBuildingTypes.MixedUse,
|
|
||||||
Type = AddressType.Shipping,
|
|
||||||
Street = "123 Building 4, XYZ Suburb, Some Region",
|
|
||||||
City = "Johannesburg",
|
|
||||||
State = "Gauteng",
|
|
||||||
Country = "South Africa",
|
|
||||||
IsPrimary = true,
|
|
||||||
Enabled = true,
|
|
||||||
PostalCode = "12345"
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.CreateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new UpdateCustomer
|
|
||||||
{
|
|
||||||
Company = "Book Lovers",
|
|
||||||
Email = "hank@booklovers.com",
|
|
||||||
Phone = "555 1245 8578",
|
|
||||||
Website = "https://www.booklovers.com"
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.UpdateCustomerAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerContactAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new UpdateCustomerContact
|
|
||||||
{
|
|
||||||
Name = "Sipho",
|
|
||||||
LastName = "Madlanga",
|
|
||||||
Phone = "0710857366",
|
|
||||||
Email = "sipho@madlanga.africa",
|
|
||||||
Type = ContactTypes.Business
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.UpdateCustomerContactAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerAddressAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new UpdateCustomerAddress
|
|
||||||
{
|
|
||||||
Name = "Business",
|
|
||||||
BuildingType = AddressBuildingTypes.MixedUse,
|
|
||||||
Type = AddressType.Shipping,
|
|
||||||
Street = "123 Building 4, XYZ Suburb, Some Region",
|
|
||||||
City = "Johannesburg",
|
|
||||||
State = "Gauteng",
|
|
||||||
Country = "South Africa",
|
|
||||||
IsPrimary = true,
|
|
||||||
Enabled = true,
|
|
||||||
PostalCode = "12346"
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await customerService.UpdateCustomerAddressAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await customerService.UpdateCustomerStatusAsync(1, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerContactStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await customerService.UpdateCustomerContactStatusAsync(1, true, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateCustomerAddressStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await customerService.UpdateCustomerAddressStatusAsync(1, true, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomersAsync_ShouldReturn_ResultWithCustomerList()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomersAsync(fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomerContactsAsync_ShouldReturn_ResultWithCustomerContactList()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomerAddressesAsync_ShouldReturn_ResultWithCustomerAddressList()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomerAddressesAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomerAsync_ShouldReturn_ResultWithCustomer()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomerAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomerContactAsync_ShouldReturn_ResultWithCustomerContact()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomerContactsAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetCustomerAddressAsync_ShouldReturn_ResultWithCustomerAddress()
|
|
||||||
{
|
|
||||||
var result = await customerService.GetCustomerAddressAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<UserSecretsId>b205af96-ceef-44e1-851c-458c9fd1c437</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.MidrandBooks\LiteCharms.Features.MidrandBooks.csproj" />
|
|
||||||
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Using Include="System.Text.Json" />
|
|
||||||
<Using Include="System.Diagnostics" />
|
|
||||||
<Using Include="Xunit" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Orders;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class OrderServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly OrderService orderService = fixture.Services.GetRequiredService<OrderService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateOrderAsync_ShouldReturn_ResultWithOrderId()
|
|
||||||
{
|
|
||||||
var request = new CreateOrder(250, "At the intercomm, dial 1 then option 2");
|
|
||||||
|
|
||||||
var result = await orderService.CreateOrderAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task AddItemToOrderAsync_ShouldReturn_ResultWithOrderItemId()
|
|
||||||
{
|
|
||||||
var request = new CreateOrderItem(1, 1, 2);
|
|
||||||
|
|
||||||
var result = await orderService.AddItemToOrderAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task AddItemsToOrderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var requests = new List<CreateOrderItem>
|
|
||||||
{
|
|
||||||
new(1, 1, 1),
|
|
||||||
new(1, 1, 3)
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await orderService.AddItemsToOrderAsync(1, [.. requests], fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task RemoveItemFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.RemoveItemFromOrderAsync(1, 5, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task ClearOrderItemsAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.ClearOrderItemsAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CancelOrderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.CancelOrderAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetOrderAsync_ShouldReturn_ResultWithOrder()
|
|
||||||
{
|
|
||||||
var result = await orderService.GetOrderAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetOrdersByCustomerAsync_ShouldReturn_ResultWithOrderList()
|
|
||||||
{
|
|
||||||
var result = await orderService.GetOrdersByCustomerAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetOrdersAsync_ShouldReturn_ResultWithOrderList()
|
|
||||||
{
|
|
||||||
var range = new DateRange
|
|
||||||
{
|
|
||||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
|
||||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
|
||||||
MaxRecords = 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await orderService.GetOrdersAsync(range, 0, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateOrderStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.UpdateOrderStatusAsync(1, OrderStatus.Pending, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task AddShippingToOrderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new CreateShipping(1, 2);
|
|
||||||
|
|
||||||
var result = await orderService.AddShippingToOrderAsync(1, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateShippingStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.UpdateShippingStatusAsync(1, ShippingStatuses.Shipped, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetShippingByOrderIdAsync_ShouldReturn_ResultWithShipping()
|
|
||||||
{
|
|
||||||
var result = await orderService.GetShippingByOrderIdAsync(1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task RemoveShippingFromOrderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.RemoveShippingFromOrderAsync(1, 1, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateShippingTrackingNumberAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var result = await orderService.UpdateShippingTrackingNumberAsync(1, 2, "NA0009969397");
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateShippingProviderAsync_ShouldReturn_ResultWithShippingProviderId()
|
|
||||||
{
|
|
||||||
var request = new CreateShippingProvider(ShippingProviderTypes.FastWay, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
|
||||||
|
|
||||||
var result = await orderService.CreateShippingProviderAsync(request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.True(result.Value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetShippingProvidersAsync_ShouldReturn_ResultWithShippingProviderList()
|
|
||||||
{
|
|
||||||
var result = await orderService.GetShippingProvidersAsync(true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetShippingProviderAsync_ShouldReturn_ResultWithShippingProvider()
|
|
||||||
{
|
|
||||||
var result = await orderService.GetShippingProviderAsync(2, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateShippingProviderAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
var request = new UpdateShippingProvider(2,true, "FastWay Couriers", 50, "https://www.fastway.co.za/our-services/track-your-parcel");
|
|
||||||
|
|
||||||
var result = await orderService.UpdateShippingProviderAsync(request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Pages;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class PageServiceFeatureTests(Fixture fixture) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly PageService pageService = fixture.Services.GetRequiredService<PageService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreatePageAsync_ShouldReturn_ResultWithPageId()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetPagesAsync_ByBookId_ShouldReturn_ResultWithPageList()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetPageAsync_ShouldReturn_ResultWithPage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetPageByNumberAsync_ById_And_BookPageNumber_ShouldReturn_ResultWithPage()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdatePageAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task DeletePageAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task DeleteByPageTypeAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task DeleteAllAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdatePageStatusAsync_ShouldReturn_ResultWithSuccess()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Products;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Tests.Common;
|
|
||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Tests;
|
|
||||||
|
|
||||||
public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
|
||||||
{
|
|
||||||
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetProductPriceAsync_ShouldReturn_RetultOneProductPrice()
|
|
||||||
{
|
|
||||||
var result = await productService.GetProductPriceAsync(2, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
|
|
||||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetProductPricesAsync_ShouldReturn_RetultProductPriceList()
|
|
||||||
{
|
|
||||||
var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
|
|
||||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task SearchProductsAsync_ShouldReturn_RetultMatchingProducts()
|
|
||||||
{
|
|
||||||
var filter = new ProductFilter
|
|
||||||
{
|
|
||||||
Name = "system",
|
|
||||||
Manufacturer = "techwave",
|
|
||||||
SerialNumber = "2024",
|
|
||||||
MinPrice = 10,
|
|
||||||
MaxPrice = 30
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await productService.SearchProductsAsync(filter, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
|
|
||||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetProductAsync_ShouldReturn_RetultOneProduct()
|
|
||||||
{
|
|
||||||
var result = await productService.GetProductAsync(2, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotNull(result.Value);
|
|
||||||
|
|
||||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task GetProductsAsync_ShouldReturn_RetultProducts()
|
|
||||||
{
|
|
||||||
var range = new DateRange
|
|
||||||
{
|
|
||||||
From = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7)),
|
|
||||||
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
|
||||||
MaxRecords = 1000
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await productService.GetProductsAsync(0, range, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
Assert.NotEmpty(result.Value);
|
|
||||||
|
|
||||||
output.WriteLine(JsonSerializer.Serialize(result.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateProductStatusAsync_ShouldReturn_ResultTrue()
|
|
||||||
{
|
|
||||||
var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task UpdateProductPriceStatusAsync_ShouldReturn_ResultTrue()
|
|
||||||
{
|
|
||||||
var result = await productService.UpdateProductPriceStatusAsync(2, true, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateProductPriceAsync_Should_Return_NewProductPriceId()
|
|
||||||
{
|
|
||||||
var request = new CreateProductPrice
|
|
||||||
{
|
|
||||||
Amount = 29.99m,
|
|
||||||
Discount = 0.00m
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await productService.CreateProductPriceAsync(2, request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess, "Product price creation should be successful.");
|
|
||||||
Assert.True(result.Value > 0, "New ProductPriceId should be greater than 0.");
|
|
||||||
|
|
||||||
output.WriteLine($"Created ProductPriceId: {result.Value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[IntegrationFact]
|
|
||||||
public async Task CreateProductAsync_Result_Returns_ProductId()
|
|
||||||
{
|
|
||||||
var request = new CreateProduct
|
|
||||||
{
|
|
||||||
Name = "Systems Rewired",
|
|
||||||
Description = "[Design], <Code>, AND /CHAORS/ IN ***SYNC***",
|
|
||||||
Summary = "A comprehensive guide to systems thinking and design.",
|
|
||||||
ImageUrl = "https://bookshop.cdn.khongisa.co.za/design/2bf1f9a2-7b25-4fcf-9aa7-08941ea21e6c_1764838499686.webp",
|
|
||||||
Type = ProductTypes.Book,
|
|
||||||
Categories = ["Systems Thinking", "Design", "Programming"],
|
|
||||||
Metadata = new ProductMetadata
|
|
||||||
{
|
|
||||||
CopyrightInfo = "© 2024 John Doe. All rights reserved.",
|
|
||||||
ManufactureDate = "2024-06-01",
|
|
||||||
Manufacturer = "TechWave Publishing",
|
|
||||||
SerialNumber = "SR-2024-0001"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await productService.CreateProductAsync(request, fixture.CancellationToken);
|
|
||||||
|
|
||||||
Assert.True(result.IsSuccess, "Product creation should be successful.");
|
|
||||||
Assert.True(result.Value > 0, "ProductId should be greater than 0.");
|
|
||||||
|
|
||||||
output.WriteLine($"Created ProductId: {result.Value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"BookshopS3Settings": {
|
|
||||||
"ServiceUrl": "http://192.168.1.177:30900",
|
|
||||||
"Region": "garage",
|
|
||||||
"BucketName": "bookshop",
|
|
||||||
"CdnBaseUrl": "https://bookshop.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,3 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
|
|
||||||
public interface IService;
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
|
|
||||||
|
|
||||||
public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
|
||||||
{
|
|
||||||
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Books
|
|
||||||
.Where(b => b.Id == bookId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(b => b.Enabled, isEnabled)
|
|
||||||
.SetProperty(b => b.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Book with ID {bookId} not found"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateBookAsync(long authorId, long productId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Author not found.");
|
|
||||||
|
|
||||||
if (!await context.Products.AnyAsync(p => p.Id == productId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Product not found.");
|
|
||||||
|
|
||||||
var book = context.Books.Add(new Entities.AuthorBook
|
|
||||||
{
|
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
AuthorId = authorId,
|
|
||||||
ProductId = productId
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(book.Entity.Id)
|
|
||||||
: Result.Fail<long>("Failed to create book.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<AuthorBook>> GetBookAsync(long bookId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var book = await context.Books
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(b => b.Author)
|
|
||||||
.Include(b => b.Product)
|
|
||||||
.ThenInclude(b => b!.Prices)
|
|
||||||
.Include(b => b.Pages)
|
|
||||||
.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
|
||||||
|
|
||||||
return book is null
|
|
||||||
? Result.Fail<AuthorBook>(new Error($"Book with ID {bookId} not found"))
|
|
||||||
: Result.Ok(book.ToModel());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<AuthorBook>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<AuthorBook[]>> GetBooksByAuthorAsync(long authorId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if(!await context.Authors.AnyAsync(a => a.Id == authorId, cancellationToken))
|
|
||||||
return Result.Fail<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
|
||||||
|
|
||||||
var books = await context.Books
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(b => b.Author)
|
|
||||||
.Include(b => b.Product)
|
|
||||||
.ThenInclude(b => b.Prices)
|
|
||||||
.OrderByDescending(b => b.CreatedAt)
|
|
||||||
.Where(b => b.AuthorId == authorId)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return books?.Count > 0
|
|
||||||
? Result.Ok(books.Select(b => b.ToModel()).ToArray())
|
|
||||||
: Result.Fail<AuthorBook[]>(new Error($"No books found for author with ID {authorId}"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<AuthorBook[]>> GetPublishedBooksAsync(int offset, int limit, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var books = await context.Books
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(b => b.Author)
|
|
||||||
.Include(b => b.Product)
|
|
||||||
.ThenInclude(b => b!.Prices)
|
|
||||||
.Include(b => b.Pages)
|
|
||||||
.Where(b => b.Enabled && b.Product!.Enabled && b.Author!.Enabled)
|
|
||||||
.OrderByDescending(b => b.Ranking)
|
|
||||||
.ThenByDescending(b => b.Ranking)
|
|
||||||
.ThenByDescending(b => b.CreatedAt)
|
|
||||||
.ThenByDescending(b => b.UpdatedAt)
|
|
||||||
.Skip(offset).Take(limit)
|
|
||||||
.AsSplitQuery()
|
|
||||||
.ToArrayAsync(cancellationToken);
|
|
||||||
|
|
||||||
return books?.Length > 0
|
|
||||||
? Result.Ok(books.Select(b => b.ToModel()).ToArray())
|
|
||||||
: Result.Fail<AuthorBook[]>(new Error("No published books found."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<AuthorBook[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|
||||||
|
|
||||||
public class AuthorBook : Models.AuthorBook
|
|
||||||
{
|
|
||||||
public virtual Author? Author { get; set; }
|
|
||||||
|
|
||||||
public new virtual Product? Product { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<BookPage> Pages { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|
||||||
|
|
||||||
public sealed class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<AuthorBook> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Books");
|
|
||||||
|
|
||||||
builder.HasKey(f => f.AuthorId);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.AuthorId).IsRequired();
|
|
||||||
builder.Property(f => f.ProductId).IsRequired();
|
|
||||||
builder.Property(f => f.Rating).IsRequired(false);
|
|
||||||
builder.Property(f => f.Ranking).IsRequired(false);
|
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Author)
|
|
||||||
.WithMany(a => a.Books)
|
|
||||||
.HasForeignKey(f => f.AuthorId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Product)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(f => f.ProductId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
|
||||||
|
|
||||||
public class AuthorBook
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public long AuthorId { get; set; }
|
|
||||||
|
|
||||||
public long ProductId { get; set; }
|
|
||||||
|
|
||||||
public int Rating { get; set; }
|
|
||||||
|
|
||||||
public int Ranking { get; set; }
|
|
||||||
|
|
||||||
public Product? Product { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors;
|
|
||||||
|
|
||||||
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
|
||||||
{
|
|
||||||
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Authors
|
|
||||||
.Where(a => a.Id == authorId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(a => a.Enabled, isEnabled)
|
|
||||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Author with ID {authorId} not found"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Author>> GetAuthorAsync(long authorId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
|
||||||
|
|
||||||
return author is not null
|
|
||||||
? Result.Ok(author.ToModel())
|
|
||||||
: Result.Fail<Author>(new Error($"Author with ID {authorId} not found"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Author>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Author[]>> GetAuthorsAsync(DateRange range, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var authors = await context.Authors.AsNoTracking()
|
|
||||||
.OrderByDescending(o => o.CreatedAt)
|
|
||||||
.ThenByDescending(o => o.UpdatedAt)
|
|
||||||
.Where(a => a.CreatedAt >= fromDate && a.CreatedAt <= toDate)
|
|
||||||
.Take(range.MaxRecords)
|
|
||||||
.ToArrayAsync(cancellationToken);
|
|
||||||
|
|
||||||
return authors?.Length > 0
|
|
||||||
? Result.Ok(authors.Select(a => a.ToModel()).ToArray())
|
|
||||||
: Result.Fail<Author[]>(new Error("No authors found in the specified date range."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Author[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateAuthorAsync(long authorId, UpdateAuthor request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var author = await context.Authors.FirstOrDefaultAsync(a => a.Id == authorId, cancellationToken);
|
|
||||||
|
|
||||||
if (author is null)
|
|
||||||
return Result.Fail(new Error($"Author with ID {authorId} not found"));
|
|
||||||
|
|
||||||
author.UpdatedAt = DateTime.UtcNow;
|
|
||||||
author.PublisherType = request.PublisherType;
|
|
||||||
author.Company = request.Company;
|
|
||||||
author.VatNumber = request.VatNumber;
|
|
||||||
author.Name = request.Name;
|
|
||||||
author.LastName = request.LastName;
|
|
||||||
author.Biography = request.Biography;
|
|
||||||
author.Email = request.Email;
|
|
||||||
author.Website = request.Website;
|
|
||||||
author.ImageUrl = request.ImageUrl;
|
|
||||||
author.ThumbnailImageUrl = request.ThumbnailImageUrl;
|
|
||||||
author.SocialMedia = request.SocialMedia;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Failed to update author with ID {authorId}"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateAuthorAsync(CreateAuthor request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (await context.Authors.AnyAsync(a => a.Name == request.Name && a.LastName == request.LastName, cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"An author with the name {request.Name} {request.LastName} already exists"));
|
|
||||||
|
|
||||||
if (await context.Authors.AnyAsync(a => a.Email == request.Email, cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"An author with the email {request.Email} already exists"));
|
|
||||||
|
|
||||||
var newAuthor = context.Authors.Add(new Entities.Author
|
|
||||||
{
|
|
||||||
Company = request.Company,
|
|
||||||
VatNumber = request.VatNumber,
|
|
||||||
PublisherType = request.PublisherType,
|
|
||||||
Name = request.Name,
|
|
||||||
LastName = request.LastName,
|
|
||||||
Biography = request.Biography,
|
|
||||||
Email = request.Email,
|
|
||||||
Website = request.Website,
|
|
||||||
ImageUrl = request.ImageUrl,
|
|
||||||
ThumbnailImageUrl = request.ThumbnailImageUrl,
|
|
||||||
SocialMedia = request.SocialMedia
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(newAuthor.Entity.Id)
|
|
||||||
: Result.Fail<long>(new Error($"Failed to create author {request.Name} {request.LastName}"));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<AuthorConfiguration, Author>]
|
|
||||||
public sealed class Author : Models.Author
|
|
||||||
{
|
|
||||||
public ICollection<AuthorBook> Books { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
|
|
||||||
|
|
||||||
public sealed class AuthorConfiguration : IEntityTypeConfiguration<Author>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Author> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Authors");
|
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.PublisherType).IsRequired();
|
|
||||||
builder.Property(f => f.VatNumber).IsRequired(false).HasMaxLength(255);
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(255);
|
|
||||||
builder.Property(f => f.LastName).IsRequired().HasMaxLength(255);
|
|
||||||
builder.Property(f => f.Biography).IsRequired(false).HasMaxLength(2048);
|
|
||||||
builder.Property(f => f.Email).IsRequired().HasMaxLength(512);
|
|
||||||
builder.Property(f => f.Website).IsRequired(false).HasMaxLength(1024);
|
|
||||||
builder.Property(f => f.ImageUrl).IsRequired().HasMaxLength(2048);
|
|
||||||
builder.Property(f => f.ThumbnailImageUrl).IsRequired(false).HasMaxLength(2048);
|
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
|
||||||
|
|
||||||
builder.OwnsMany(f => f.SocialMedia, b => { b.ToJson(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Models;
|
|
||||||
|
|
||||||
public class Author
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public PublisherTypes PublisherType { get; set; }
|
|
||||||
|
|
||||||
public string? Company { get; set; }
|
|
||||||
|
|
||||||
public string? VatNumber { get; set; }
|
|
||||||
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public string? LastName { get; set; }
|
|
||||||
|
|
||||||
public string? Biography { get; set; }
|
|
||||||
|
|
||||||
public string? Email { get; set; }
|
|
||||||
|
|
||||||
public string? Website { get; set; }
|
|
||||||
|
|
||||||
public string? ImageUrl { get; set; }
|
|
||||||
|
|
||||||
public string? ThumbnailImageUrl { get; set; }
|
|
||||||
|
|
||||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Authors.Models;
|
|
||||||
|
|
||||||
public sealed record UpdateAuthor : CreateAuthor;
|
|
||||||
|
|
||||||
public record CreateAuthor
|
|
||||||
{
|
|
||||||
public required PublisherTypes PublisherType { get; set; }
|
|
||||||
|
|
||||||
public string? Company { get; set; }
|
|
||||||
|
|
||||||
public string? VatNumber { get; set; }
|
|
||||||
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
public required string LastName { get; set; }
|
|
||||||
|
|
||||||
public string? Biography { get; set; }
|
|
||||||
|
|
||||||
public required string Email { get; set; }
|
|
||||||
|
|
||||||
public string? Website { get; set; }
|
|
||||||
|
|
||||||
public required string ImageUrl { get; set; }
|
|
||||||
|
|
||||||
public string? ThumbnailImageUrl { get; set; }
|
|
||||||
|
|
||||||
public SocialMedia[]? SocialMedia { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,398 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Customers;
|
|
||||||
|
|
||||||
public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
|
||||||
{
|
|
||||||
public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"Customer with email '{request.Email}' already exists."));
|
|
||||||
|
|
||||||
var customer = context.Customers.Add(new Entities.Customer
|
|
||||||
{
|
|
||||||
Company = request.Company,
|
|
||||||
VatNumber = request.VatNumber,
|
|
||||||
Email = request.Email,
|
|
||||||
Website = request.Website,
|
|
||||||
Phone = request.Phone,
|
|
||||||
SocialMedia = request.SocialMedia,
|
|
||||||
Enabled = true
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(customer.Entity.Id)
|
|
||||||
: Result.Fail<long>(new Error("Failed to create customer."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateCustomerContactAsync(long customerId, CreateCustomerContact request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"Contact with email '{request.Email}' already exists for this customer."));
|
|
||||||
|
|
||||||
var contact = context.Contacts.Add(new Entities.Contact
|
|
||||||
{
|
|
||||||
CustomerId = customerId,
|
|
||||||
Name = request.Name,
|
|
||||||
Email = request.Email,
|
|
||||||
Phone = request.Phone,
|
|
||||||
LastName = request.LastName,
|
|
||||||
Type = request.Type,
|
|
||||||
Enabled = true
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(contact.Entity.Id)
|
|
||||||
: Result.Fail<long>(new Error("Failed to create customer contact."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateCustomerAddressAsync(long customerId, CreateCustomerAddress request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
var address = context.Addresses.Add(new Entities.Address
|
|
||||||
{
|
|
||||||
CustomerId = customerId,
|
|
||||||
Street = request.Street,
|
|
||||||
City = request.City,
|
|
||||||
State = request.State,
|
|
||||||
PostalCode = request.PostalCode,
|
|
||||||
Country = request.Country,
|
|
||||||
Type = request.Type,
|
|
||||||
Enabled = true,
|
|
||||||
BuildingType = request.BuildingType,
|
|
||||||
IsPrimary = request.IsPrimary,
|
|
||||||
Name = request.Name
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(address.Entity.Id)
|
|
||||||
: Result.Fail<long>(new Error("Failed to create customer address."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerAsync(long customerId, UpdateCustomer request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
|
||||||
|
|
||||||
if (customer is null)
|
|
||||||
return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
customer.UpdatedAt = DateTime.UtcNow;
|
|
||||||
customer.Company = request.Company;
|
|
||||||
customer.VatNumber = request.VatNumber;
|
|
||||||
customer.Email = request.Email;
|
|
||||||
customer.Website = request.Website;
|
|
||||||
customer.Phone = request.Phone;
|
|
||||||
customer.SocialMedia = request.SocialMedia;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error("Failed to update customer."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerContactAsync(long contactId, UpdateCustomerContact request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Contacts
|
|
||||||
.Where(cc => cc.Id == contactId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(cc => cc.Name, request.Name)
|
|
||||||
.SetProperty(cc => cc.LastName, request.LastName)
|
|
||||||
.SetProperty(cc => cc.Email, request.Email)
|
|
||||||
.SetProperty(cc => cc.Phone, request.Phone)
|
|
||||||
.SetProperty(cc => cc.Type, request.Type)
|
|
||||||
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerAddressAsync(long addressId, UpdateCustomerAddress request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Addresses
|
|
||||||
.Where(a => a.Id == addressId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(a => a.Street, request.Street)
|
|
||||||
.SetProperty(a => a.City, request.City)
|
|
||||||
.SetProperty(a => a.State, request.State)
|
|
||||||
.SetProperty(a => a.PostalCode, request.PostalCode)
|
|
||||||
.SetProperty(a => a.Country, request.Country)
|
|
||||||
.SetProperty(a => a.Type, request.Type)
|
|
||||||
.SetProperty(a => a.BuildingType, request.BuildingType)
|
|
||||||
.SetProperty(a => a.IsPrimary, request.IsPrimary)
|
|
||||||
.SetProperty(a => a.Name, request.Name)
|
|
||||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerStatusAsync(long customerId, bool enabled, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Customers
|
|
||||||
.Where(c => c.Id == customerId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(c => c.Enabled, enabled)
|
|
||||||
.SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerContactStatusAsync(long contactId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Contacts
|
|
||||||
.Where(cc => cc.Id == contactId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(cc => cc.Enabled, enabled)
|
|
||||||
.SetProperty(cc => cc.IsPrimary, isPrimary)
|
|
||||||
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateCustomerAddressStatusAsync(long addressId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Addresses
|
|
||||||
.Where(a => a.Id == addressId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(a => a.Enabled, enabled)
|
|
||||||
.SetProperty(a => a.IsPrimary, isPrimary)
|
|
||||||
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Customer[]>> GetCustomersAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var customers = await context.Customers
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(c => c.Contacts)
|
|
||||||
.Include(c => c.Addresses)
|
|
||||||
.OrderByDescending(c => c.CreatedAt)
|
|
||||||
.ThenByDescending(c => c.UpdatedAt)
|
|
||||||
.AsSplitQuery()
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return customers?.Count > 0
|
|
||||||
? Result.Ok(customers.Select(c => c.ToModel()).ToArray())
|
|
||||||
: Result.Fail<Customer[]>(new Error("No customers found."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Customer[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Contact[]>> GetCustomerContactsAsync(long customerId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<Contact[]>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
var contacts = await context.Contacts
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(cc => cc.CustomerId == customerId)
|
|
||||||
.OrderByDescending(cc => cc.CreatedAt)
|
|
||||||
.ThenByDescending(cc => cc.UpdatedAt)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return contacts?.Count > 0
|
|
||||||
? Result.Ok(contacts.Select(cc => cc.ToModel()).ToArray())
|
|
||||||
: Result.Fail<Contact[]>(new Error("No contacts found for the specified customer."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Contact[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Address[]>> GetCustomerAddressesAsync(long customerId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<Address[]>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
|
|
||||||
var addresses = await context.Addresses
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.CustomerId == customerId)
|
|
||||||
.OrderByDescending(a => a.CreatedAt)
|
|
||||||
.ThenByDescending(a => a.UpdatedAt)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return addresses?.Count > 0
|
|
||||||
? Result.Ok(addresses.Select(a => a.ToModel()).ToArray())
|
|
||||||
: Result.Fail<Address[]>(new Error($"No addresses found for customer with ID '{customerId}'."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Address[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Customer>> GetCustomerAsync(long customerId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var customer = await context.Customers
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(c => c.Contacts)
|
|
||||||
.Include(c => c.Addresses)
|
|
||||||
.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
|
||||||
|
|
||||||
return customer is not null
|
|
||||||
? Result.Ok(customer.ToModel())
|
|
||||||
: Result.Fail<Customer>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Customer>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Contact>> GetCustomerContactAsync(long contactId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var contact = await context.Contacts
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
|
||||||
|
|
||||||
return contact is not null
|
|
||||||
? Result.Ok(contact.ToModel())
|
|
||||||
: Result.Fail<Contact>(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Contact>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Address>> GetCustomerAddressAsync(long addressId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var address = await context.Addresses
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
|
||||||
|
|
||||||
return address is not null
|
|
||||||
? Result.Ok(address.ToModel())
|
|
||||||
: Result.Fail<Address>(new Error($"Address with ID '{addressId}' does not exist."));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Address>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<AddressConfiguration, Address>]
|
|
||||||
public class Address : Models.Address
|
|
||||||
{
|
|
||||||
public virtual Customer? Customer { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
public sealed class AddressConfiguration : IEntityTypeConfiguration<Address>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Address> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Addresses");
|
|
||||||
|
|
||||||
builder.HasKey(a => a.Id);
|
|
||||||
builder.Property(a => a.CustomerId).IsRequired();
|
|
||||||
builder.Property(a => a.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(a => a.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(a => a.Name).IsRequired();
|
|
||||||
builder.Property(a => a.Type).IsRequired();
|
|
||||||
builder.Property(a => a.BuildingType).IsRequired();
|
|
||||||
builder.Property(a => a.Street).IsRequired();
|
|
||||||
builder.Property(a => a.City).IsRequired();
|
|
||||||
builder.Property(a => a.State).IsRequired();
|
|
||||||
builder.Property(a => a.PostalCode).IsRequired();
|
|
||||||
builder.Property(a => a.Country).IsRequired();
|
|
||||||
builder.Property(a => a.IsPrimary).HasDefaultValue(false);
|
|
||||||
builder.Property(a => a.Enabled).HasDefaultValue(true);
|
|
||||||
|
|
||||||
builder.HasOne(a => a.Customer)
|
|
||||||
.WithMany(c => c.Addresses)
|
|
||||||
.HasForeignKey(a => a.CustomerId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<ContactConfiguration, Contact>]
|
|
||||||
public class Contact : Models.Contact
|
|
||||||
{
|
|
||||||
public virtual Customer? Customer { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
public sealed class ContactConfiguration : IEntityTypeConfiguration<Contact>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Contact> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Contacts");
|
|
||||||
|
|
||||||
builder.HasKey(c => c.Id);
|
|
||||||
builder.Property(c => c.CustomerId).IsRequired();
|
|
||||||
builder.Property(c => c.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(c => c.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(c => c.Name).IsRequired();
|
|
||||||
builder.Property(c => c.LastName).IsRequired();
|
|
||||||
builder.Property(c => c.Type).IsRequired();
|
|
||||||
builder.Property(c => c.Phone).IsRequired();
|
|
||||||
builder.Property(c => c.Email).IsRequired();
|
|
||||||
builder.Property(c => c.IsPrimary).HasDefaultValue(false);
|
|
||||||
builder.Property(c => c.Enabled).HasDefaultValue(true);
|
|
||||||
|
|
||||||
builder.HasOne(c => c.Customer)
|
|
||||||
.WithMany(c => c.Contacts)
|
|
||||||
.HasForeignKey(c => c.CustomerId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<CustomerConfiguration, Customer>]
|
|
||||||
public class Customer : Models.Customer
|
|
||||||
{
|
|
||||||
public virtual ICollection<Contact> Contacts { get; set; } = [];
|
|
||||||
|
|
||||||
public virtual ICollection<Address> Addresses { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
public sealed class CustomerConfiguration : IEntityTypeConfiguration<Customer>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Customer> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Customers");
|
|
||||||
|
|
||||||
builder.HasKey(c => c.Id);
|
|
||||||
builder.Property(c => c.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(c => c.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(c => c.Company).IsRequired(false);
|
|
||||||
builder.Property(c => c.VatNumber).IsRequired(false);
|
|
||||||
builder.Property(c => c.Email).IsRequired();
|
|
||||||
builder.Property(c => c.Phone).IsRequired();
|
|
||||||
builder.Property(c => c.Website).IsRequired();
|
|
||||||
builder.Property(c => c.Enabled).HasDefaultValue(true);
|
|
||||||
|
|
||||||
builder.OwnsMany(f => f.SocialMedia, b => { b.ToJson(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
|
|
||||||
public class Address
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public long CustomerId { get; set; }
|
|
||||||
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public AddressType Type { get; set; }
|
|
||||||
|
|
||||||
public AddressBuildingTypes BuildingType { get; set; }
|
|
||||||
|
|
||||||
public string? Street { get; set; }
|
|
||||||
|
|
||||||
public string? City { get; set; }
|
|
||||||
|
|
||||||
public string? State { get; set; }
|
|
||||||
|
|
||||||
public string? PostalCode { get; set; }
|
|
||||||
|
|
||||||
public string? Country { get; set; }
|
|
||||||
|
|
||||||
public bool IsPrimary { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
|
|
||||||
public class Contact
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public long CustomerId { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public ContactTypes Type { get; set; }
|
|
||||||
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public string? LastName { get; set; }
|
|
||||||
|
|
||||||
public string? Email { get; set; }
|
|
||||||
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
|
|
||||||
public bool IsPrimary { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
|
|
||||||
public class Customer
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public string? Company { get; set; }
|
|
||||||
|
|
||||||
public string? VatNumber { get; set; }
|
|
||||||
|
|
||||||
public string? Email { get; set; }
|
|
||||||
|
|
||||||
public string? Website { get; set; }
|
|
||||||
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
|
|
||||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
|
|
||||||
public record CreateCustomer
|
|
||||||
{
|
|
||||||
public string? Company { get; set; }
|
|
||||||
|
|
||||||
public string? VatNumber { get; set; }
|
|
||||||
|
|
||||||
public string? Email { get; set; }
|
|
||||||
|
|
||||||
public string? Website { get; set; }
|
|
||||||
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
|
|
||||||
public ICollection<SocialMedia>? SocialMedia { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record UpdateCustomer : CreateCustomer;
|
|
||||||
|
|
||||||
public record CreateCustomerContact
|
|
||||||
{
|
|
||||||
public ContactTypes Type { get; set; }
|
|
||||||
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public string? LastName { get; set; }
|
|
||||||
|
|
||||||
public string? Email { get; set; }
|
|
||||||
|
|
||||||
public string? Phone { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record UpdateCustomerContact : CreateCustomerContact;
|
|
||||||
|
|
||||||
public record CreateCustomerAddress
|
|
||||||
{
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public AddressType Type { get; set; }
|
|
||||||
|
|
||||||
public AddressBuildingTypes BuildingType { get; set; }
|
|
||||||
|
|
||||||
public string? Street { get; set; }
|
|
||||||
|
|
||||||
public string? City { get; set; }
|
|
||||||
|
|
||||||
public string? State { get; set; }
|
|
||||||
|
|
||||||
public string? PostalCode { get; set; }
|
|
||||||
|
|
||||||
public string? Country { get; set; }
|
|
||||||
|
|
||||||
public bool IsPrimary { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record UpdateCustomerAddress : CreateCustomerAddress;
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks;
|
|
||||||
|
|
||||||
public enum PublisherTypes : int
|
|
||||||
{
|
|
||||||
Individual = 0,
|
|
||||||
Company = 1,
|
|
||||||
Organization = 2,
|
|
||||||
SelfPublished = 3,
|
|
||||||
UniversityPress = 4,
|
|
||||||
GovernmentAgency = 5,
|
|
||||||
NonProfit = 6,
|
|
||||||
Independent = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BookTypes : int
|
|
||||||
{
|
|
||||||
Fiction = 0,
|
|
||||||
NonFiction = 1,
|
|
||||||
Academic = 2,
|
|
||||||
SelfHelp = 3,
|
|
||||||
Biography = 4,
|
|
||||||
Poetry = 5,
|
|
||||||
Children = 6,
|
|
||||||
YoungAdult = 7,
|
|
||||||
ScienceFiction = 8,
|
|
||||||
Fantasy = 9
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BookContentTypes : int
|
|
||||||
{
|
|
||||||
Text = 0,
|
|
||||||
Image = 1,
|
|
||||||
Video = 2,
|
|
||||||
Audio = 3,
|
|
||||||
Interactive = 4,
|
|
||||||
Markdown = 5,
|
|
||||||
Html = 6,
|
|
||||||
Json = 7,
|
|
||||||
Yaml = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BookPageTypes : int
|
|
||||||
{
|
|
||||||
Cover = 0,
|
|
||||||
Preface = 1,
|
|
||||||
Introduction = 2,
|
|
||||||
Content = 3,
|
|
||||||
Closing = 4,
|
|
||||||
Referencer = 5,
|
|
||||||
Credits = 6,
|
|
||||||
BackCover = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ProductTypes : int
|
|
||||||
{
|
|
||||||
Book = 1,
|
|
||||||
Journal = 2,
|
|
||||||
Magazine = 3,
|
|
||||||
EBook = 4,
|
|
||||||
Audiobook = 5,
|
|
||||||
Accessory = 6
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.HealthChecks;
|
|
||||||
using static LiteCharms.Features.Extensions.Postgres;
|
|
||||||
using static LiteCharms.Features.MidrandBooks.Extensions.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
|
|
||||||
public static class HealthChecks
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddMidrandShopQuartzHealthCheck(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddHealthChecks().AddCheck<MidrandShopQuartzHealthCheck>(SchedulerDbConfigName);
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMidrandShopPostgresHealthCheck(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddHealthChecks().AddCheck<PostgresMidrandShopHealthCheck>(MidrandBooksDbConfigName);
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHealthChecksSupport(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.AddHealthChecks()
|
|
||||||
.AddCheck("Self", () => HealthCheckResult.Healthy());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
|
|
||||||
public static class Mappers
|
|
||||||
{
|
|
||||||
public static ShippingProvider ToModel(this Orders.Entities.ShippingProvider entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Name = entity.Name,
|
|
||||||
Type = entity.Type,
|
|
||||||
Price = entity.Price,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
TrackingUrl = entity.TrackingUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Shipping ToModel(this Orders.Entities.Shipping entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
OrderId = entity.OrderId,
|
|
||||||
AddressId = entity.AddressId,
|
|
||||||
Status = entity.Status,
|
|
||||||
TrackingNumber = entity.TrackingNumber
|
|
||||||
};
|
|
||||||
|
|
||||||
public static OrderItem ToModel(this Orders.Entities.OrderItem entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
OrderId = entity.OrderId,
|
|
||||||
Quantity = entity.Quantity,
|
|
||||||
AuthorBookId = entity.AuthorBookId,
|
|
||||||
ProductPriceId = entity.ProductPriceId
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Order ToModel(this Orders.Entities.Order entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Status = entity.Status,
|
|
||||||
Total = entity.Total,
|
|
||||||
InvoiceUrl = entity.InvoiceUrl,
|
|
||||||
Notes = entity.Notes
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Customer ToModel(this Customers.Entities.Customer entiry) => new()
|
|
||||||
{
|
|
||||||
Id = entiry.Id,
|
|
||||||
Company = entiry.Company,
|
|
||||||
CreatedAt = entiry.CreatedAt,
|
|
||||||
Email = entiry.Email,
|
|
||||||
Enabled = entiry.Enabled,
|
|
||||||
Phone = entiry.Phone,
|
|
||||||
SocialMedia = entiry.SocialMedia,
|
|
||||||
UpdatedAt = entiry.UpdatedAt,
|
|
||||||
VatNumber = entiry.VatNumber,
|
|
||||||
Website = entiry.Website
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Address ToModel(this Customers.Entities.Address entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
BuildingType = entity.BuildingType,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
IsPrimary = entity.IsPrimary,
|
|
||||||
Name = entity.Name,
|
|
||||||
PostalCode = entity.PostalCode,
|
|
||||||
Type = entity.Type,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Street = entity.Street,
|
|
||||||
City = entity.City,
|
|
||||||
State = entity.State,
|
|
||||||
Country = entity.Country
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Contact ToModel(this Customers.Entities.Contact entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
Type = entity.Type,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
Email = entity.Email,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
LastName = entity.LastName,
|
|
||||||
Name = entity.Name,
|
|
||||||
Phone = entity.Phone
|
|
||||||
};
|
|
||||||
|
|
||||||
public static BookPage ToModel(this Pages.Entities.BookPage entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
AuthorBookId = entity.AuthorBookId,
|
|
||||||
Content = entity.Content,
|
|
||||||
ContentType = entity.ContentType,
|
|
||||||
Number = entity.Number,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
Notes = entity.Notes,
|
|
||||||
References = entity.References,
|
|
||||||
Type = entity.Type
|
|
||||||
};
|
|
||||||
|
|
||||||
public static AuthorBook ToModel(this AuthorBooks.Entities.AuthorBook entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
ProductId = entity.ProductId,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
AuthorId = entity.AuthorId,
|
|
||||||
Ranking = entity.Ranking,
|
|
||||||
Rating = entity.Rating,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
Product = entity.Product?.ToModel(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static ProductPrice ToModel(this Products.Entities.ProductPrice entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
ProductId = entity.ProductId,
|
|
||||||
Amount = entity.Amount,
|
|
||||||
Discount = entity.Discount,
|
|
||||||
Enabled = entity.Enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Product ToModel(this Products.Entities.Product entity)
|
|
||||||
{
|
|
||||||
return new Product
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Name = entity.Name,
|
|
||||||
Summary = entity.Summary,
|
|
||||||
Description = entity.Description,
|
|
||||||
Type = entity.Type,
|
|
||||||
ImageUrl = entity.ImageUrl,
|
|
||||||
ThumbnailUrls = entity.ThumbnailUrls,
|
|
||||||
Metadata = entity.Metadata,
|
|
||||||
Categories = entity.Categories,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Author ToModel(this Authors.Entities.Author entity) => new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Name = entity.Name,
|
|
||||||
LastName = entity.LastName,
|
|
||||||
Biography = entity.Biography,
|
|
||||||
Email = entity.Email,
|
|
||||||
Website = entity.Website,
|
|
||||||
ImageUrl = entity.ImageUrl,
|
|
||||||
ThumbnailImageUrl = entity.ThumbnailImageUrl,
|
|
||||||
SocialMedia = entity.SocialMedia,
|
|
||||||
Enabled = entity.Enabled,
|
|
||||||
Company = entity.Company,
|
|
||||||
PublisherType = entity.PublisherType,
|
|
||||||
VatNumber = entity.VatNumber
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
|
|
||||||
public static class Postgres
|
|
||||||
{
|
|
||||||
public const string MidrandBooksDbConfigName = "PostgresMidrandBooks";
|
|
||||||
|
|
||||||
public static IServiceCollection AddMidrandShopDatabase(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName);
|
|
||||||
|
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
|
||||||
|
|
||||||
dataSourceBuilder.ConfigureTypeLoading(options => { options.EnableTypeLoading(false); });
|
|
||||||
|
|
||||||
var dataSource = dataSourceBuilder.Build();
|
|
||||||
|
|
||||||
services.AddSingleton(dataSource);
|
|
||||||
|
|
||||||
services.AddPooledDbContextFactory<MidrandBooksDbContext>(options =>
|
|
||||||
options.UseNpgsql(dataSource));
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
|
|
||||||
public static class Shop
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddShopServices(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var serviceType = typeof(IService);
|
|
||||||
|
|
||||||
var implementations = Assembly.GetExecutingAssembly().GetTypes()
|
|
||||||
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
|
||||||
|
|
||||||
foreach (var implementation in implementations)
|
|
||||||
services.AddScoped(implementation);
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using static LiteCharms.Features.Extensions.Quartz;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
|
||||||
|
|
||||||
public sealed class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
|
|
||||||
{
|
|
||||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var scheduler = await schedulerFactory.GetScheduler(MidrandShopSchedulerName, cancellationToken);
|
|
||||||
|
|
||||||
if(scheduler == null)
|
|
||||||
return HealthCheckResult.Unhealthy($"Scheduler with name '{MidrandShopSchedulerName}' not found.");
|
|
||||||
|
|
||||||
if (!scheduler.IsStarted)
|
|
||||||
return HealthCheckResult.Unhealthy($"{MidrandShopSchedulerName} Quartz scheduler is not running");
|
|
||||||
|
|
||||||
await scheduler.CheckExists(new JobKey(Guid.NewGuid().ToString()), cancellationToken);
|
|
||||||
|
|
||||||
return HealthCheckResult.Healthy($"{MidrandShopSchedulerName} Quartz scheduler is ready");
|
|
||||||
}
|
|
||||||
catch (SchedulerException)
|
|
||||||
{
|
|
||||||
return HealthCheckResult.Unhealthy($"{MidrandShopSchedulerName} Quartz scheduler cannot connect to the store");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using static LiteCharms.Features.MidrandBooks.Extensions.Postgres;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.HealthChecks;
|
|
||||||
|
|
||||||
public sealed class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck
|
|
||||||
{
|
|
||||||
private readonly string connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName)!;
|
|
||||||
|
|
||||||
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($"{MidrandBooksDbConfigName} is responsive.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return HealthCheckResult.Unhealthy($"{MidrandBooksDbConfigName} is unreachable.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<SignAssembly>True</SignAssembly>
|
|
||||||
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
|
|
||||||
<UserSecretsId>5be62f49-3ed0-4468-884e-1b04e048b45a</UserSecretsId>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Nuget Package Details -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<PackageId>LiteCharms.Features.MidrandBooks</PackageId>
|
|
||||||
<Version>1.0.20</Version>
|
|
||||||
<Authors>Khwezi Mngoma</Authors>
|
|
||||||
<Company>Lite Charms (PTY) Ltd</Company>
|
|
||||||
<Description>MidrandBooks 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="Meziantou.Analyzer" Version="3.0.96">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<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="System.Globalization" />
|
|
||||||
<Using Include="System.Reflection" />
|
|
||||||
<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>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Payments.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<OrderConfiguration, Order>]
|
|
||||||
public class Order : Models.Order
|
|
||||||
{
|
|
||||||
public virtual Shipping? Shipping { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<OrderItem> OrderItems { get; set; } = [];
|
|
||||||
|
|
||||||
public virtual ICollection<Refund> Refunds { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
public sealed class OrderConfiguration : IEntityTypeConfiguration<Order>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Order> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Orders");
|
|
||||||
|
|
||||||
builder.HasKey(o => o.Id);
|
|
||||||
builder.Property(o => o.CustomerId).IsRequired();
|
|
||||||
builder.Property(o => o.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(o => o.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(o => o.Status).IsRequired();
|
|
||||||
builder.Property(o => o.Total).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(o => o.Notes).HasMaxLength(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<OrderItemConfiguration, OrderItem>]
|
|
||||||
public class OrderItem : Models.OrderItem
|
|
||||||
{
|
|
||||||
public virtual Order? Order { get; set; }
|
|
||||||
|
|
||||||
public virtual AuthorBook? AuthorBook { get; set; }
|
|
||||||
|
|
||||||
public virtual ProductPrice? ProductPrice { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
public sealed class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<OrderItem> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("OrderItems");
|
|
||||||
|
|
||||||
builder.HasKey(oi => oi.Id);
|
|
||||||
builder.Property(oi => oi.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(oi => oi.OrderId).IsRequired();
|
|
||||||
builder.Property(oi => oi.AuthorBookId).IsRequired();
|
|
||||||
builder.Property(oi => oi.ProductPriceId).IsRequired();
|
|
||||||
builder.Property(oi => oi.Quantity).IsRequired();
|
|
||||||
|
|
||||||
builder.HasOne(oi => oi.Order)
|
|
||||||
.WithMany(o => o.OrderItems)
|
|
||||||
.HasForeignKey(oi => oi.OrderId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
|
|
||||||
builder.HasOne(oi => oi.AuthorBook)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(oi => oi.AuthorBookId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(oi => oi.ProductPrice)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(oi => oi.ProductPriceId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<ShippingConfiguration, Shipping>]
|
|
||||||
public class Shipping : Models.Shipping
|
|
||||||
{
|
|
||||||
public virtual Order? Order { get; set; }
|
|
||||||
|
|
||||||
public virtual Address? Address { get; set; }
|
|
||||||
|
|
||||||
public virtual ShippingProvider? ShippingProvider { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
public sealed class ShippingConfiguration : IEntityTypeConfiguration<Shipping>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Shipping> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("Shippings");
|
|
||||||
|
|
||||||
builder.HasKey(s => s.Id);
|
|
||||||
builder.Property(s => s.OrderId).IsRequired();
|
|
||||||
builder.Property(s => s.AddressId).IsRequired();
|
|
||||||
builder.Property(s => s.ShippingProviderId).IsRequired();
|
|
||||||
builder.Property(s => s.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(s => s.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(s => s.Status).IsRequired();
|
|
||||||
builder.Property(s => s.TrackingNumber).HasMaxLength(255);
|
|
||||||
|
|
||||||
builder.HasOne(s => s.Order)
|
|
||||||
.WithOne(o => o.Shipping)
|
|
||||||
.HasForeignKey<Shipping>(s => s.OrderId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(s => s.Address)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(s => s.AddressId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.ShippingProvider)
|
|
||||||
.WithMany(f => f.Shippings)
|
|
||||||
.HasForeignKey(f => f.ShippingProviderId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
public class ShippingProvider : Models.ShippingProvider
|
|
||||||
{
|
|
||||||
public virtual ICollection<Shipping> Shippings { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
|
||||||
|
|
||||||
public sealed class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingProvider>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<ShippingProvider> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable("ShippingProviders");
|
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.UpdatedAt).HasDefaultValueSql("now()");
|
|
||||||
builder.Property(f => f.Type).IsRequired();
|
|
||||||
builder.Property(f => f.Name).IsRequired().HasMaxLength(100);
|
|
||||||
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
|
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
|
||||||
builder.Property(f => f.TrackingUrl).HasMaxLength(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
|
|
||||||
public class Order
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public long CustomerId { get; set; }
|
|
||||||
|
|
||||||
public OrderStatus Status { get; set; }
|
|
||||||
|
|
||||||
public decimal Total { get; set; }
|
|
||||||
|
|
||||||
public string? Notes { get; set; }
|
|
||||||
|
|
||||||
public string? InvoiceUrl { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
|
|
||||||
public class OrderItem
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public long OrderId { get; set; }
|
|
||||||
|
|
||||||
public long AuthorBookId { get; set; }
|
|
||||||
|
|
||||||
public long ProductPriceId { get; set; }
|
|
||||||
|
|
||||||
public int Quantity { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
|
|
||||||
public sealed record CreateOrder(decimal TotalPrice, string? Notes);
|
|
||||||
|
|
||||||
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
|
||||||
|
|
||||||
public sealed record CreateShipping(long AddressId, long ShippingProviderId, string? TrackingNumber = null);
|
|
||||||
|
|
||||||
public sealed record CreateShippingProvider(ShippingProviderTypes Type, string Name, decimal Price, string TrackingUrl);
|
|
||||||
|
|
||||||
public sealed record UpdateShippingProvider(long ProviderId, bool Enabled, string Name, decimal Price, string TrackingUrl);
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
|
|
||||||
public class Shipping
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public long OrderId { get; set; }
|
|
||||||
|
|
||||||
public long AddressId { get; set; }
|
|
||||||
|
|
||||||
public long ShippingProviderId { get; set; }
|
|
||||||
|
|
||||||
public string? TrackingNumber { get; set; }
|
|
||||||
|
|
||||||
public ShippingStatuses Status { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
|
|
||||||
public class ShippingProvider
|
|
||||||
{
|
|
||||||
public long Id { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime? UpdatedAt { get; set; }
|
|
||||||
|
|
||||||
public ShippingProviderTypes Type { get; set; }
|
|
||||||
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
public decimal? Price { get; set; }
|
|
||||||
|
|
||||||
public string? TrackingUrl { get; set; }
|
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
||||||
using LiteCharms.Features.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Orders;
|
|
||||||
|
|
||||||
public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
|
||||||
{
|
|
||||||
public async ValueTask<Result<long>> CreateOrderAsync(long customerId, CreateOrder request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Customer not found.");
|
|
||||||
|
|
||||||
var order = context.Orders.Add(new Entities.Order
|
|
||||||
{
|
|
||||||
CustomerId = customerId,
|
|
||||||
Status = OrderStatus.Pending,
|
|
||||||
Total = request.TotalPrice
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(order.Entity.Id)
|
|
||||||
: Result.Fail<long>("Failed to create order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> AddItemToOrderAsync(long orderId, CreateOrderItem request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Order not found.");
|
|
||||||
|
|
||||||
if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Author book not found.");
|
|
||||||
|
|
||||||
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
|
|
||||||
return Result.Fail<long>("Product price not found.");
|
|
||||||
|
|
||||||
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == request.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
|
||||||
|
|
||||||
if(existingItem is not null)
|
|
||||||
{
|
|
||||||
existingItem.Quantity += request.Quantity;
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(existingItem.Id)
|
|
||||||
: Result.Fail<long>("Update existing order item.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var orderItem = context.OrderItems.Add(new Entities.OrderItem
|
|
||||||
{
|
|
||||||
OrderId = orderId,
|
|
||||||
AuthorBookId = request.AuthorBookId,
|
|
||||||
ProductPriceId = request.ProductPriceId,
|
|
||||||
Quantity = request.Quantity
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(orderItem.Entity.Id)
|
|
||||||
: Result.Fail<long>("Failed to add item to order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> AddItemsToOrderAsync(long orderId, CreateOrderItem[] items, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(items.Length == 0)
|
|
||||||
return Result.Fail("No items to add.");
|
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
|
||||||
return Result.Fail("Order not found.");
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
if (!await context.Books.AnyAsync(ab => ab.Id == item.AuthorBookId, cancellationToken))
|
|
||||||
return Result.Fail($"Author book with ID {item.AuthorBookId} not found.");
|
|
||||||
|
|
||||||
if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken))
|
|
||||||
return Result.Fail($"Product price with ID {item.ProductPriceId} not found.");
|
|
||||||
|
|
||||||
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == item.ProductPriceId && i.OrderId == orderId, cancellationToken);
|
|
||||||
|
|
||||||
if (existingItem is not null)
|
|
||||||
existingItem.Quantity += item.Quantity;
|
|
||||||
else
|
|
||||||
context.OrderItems.Add(new Entities.OrderItem
|
|
||||||
{
|
|
||||||
OrderId = orderId,
|
|
||||||
AuthorBookId = item.AuthorBookId,
|
|
||||||
ProductPriceId = item.ProductPriceId,
|
|
||||||
Quantity = item.Quantity
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.Ok();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> RemoveItemFromOrderAsync(long orderId, long orderItemId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsDeleted = await context.OrderItems
|
|
||||||
.Where(oi => oi.Id == orderItemId && oi.OrderId == orderId)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Order item not found or failed to remove.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> ClearOrderItemsAsync(long orderId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var deletedItems = await context.OrderItems.Where(oi => oi.OrderId == orderId)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Failed to clear order items.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> CancelOrderAsync(long orderId, CancellationToken cancellationToken = default) =>
|
|
||||||
await UpdateOrderStatusAsync(orderId, OrderStatus.Cancelled, cancellationToken);
|
|
||||||
|
|
||||||
public async ValueTask<Result<Order>> GetOrderAsync(long orderId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var order = await context.Orders.AsNoTracking().FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
|
||||||
|
|
||||||
return order is not null
|
|
||||||
? Result.Ok(order.ToModel())
|
|
||||||
: Result.Fail<Order>("Order not found.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Order>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Order[]>> GetOrdersByCustomerAsync(long customerId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if(!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
||||||
return Result.Fail<Order[]>("Customer not found.");
|
|
||||||
|
|
||||||
var orders = await context.Orders
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(o => o.CustomerId == customerId)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Order[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<Order[]>> GetOrdersAsync(DateRange range, int index, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fromDate = range.From.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
|
|
||||||
var toDate = range.To.ToDateTime(TimeOnly.MaxValue, DateTimeKind.Utc);
|
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var orders = await context.Orders
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate)
|
|
||||||
.Skip(index).Take(range.MaxRecords)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Result.Ok(orders.Select(o => o.ToModel()).ToArray());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Order[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateOrderStatusAsync(long orderId, OrderStatus newStatus, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Orders
|
|
||||||
.Where(o => o.Id == orderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(o => o.Status, newStatus)
|
|
||||||
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Order not found or status update failed.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
|
||||||
return Result.Fail("Order not found.");
|
|
||||||
|
|
||||||
if(!await context.Addresses.AnyAsync(a => a.Id == request.AddressId, cancellationToken))
|
|
||||||
return Result.Fail("Address not found.");
|
|
||||||
|
|
||||||
if(!await context.ShippingProviders.AnyAsync(sp => sp.Id == request.ShippingProviderId && sp.Enabled, cancellationToken))
|
|
||||||
return Result.Fail("Shipping provider not found or disabled.");
|
|
||||||
|
|
||||||
if(await context.Shippings.AnyAsync(s => s.OrderId == orderId, cancellationToken))
|
|
||||||
return Result.Fail("Shipping already exists for this order.");
|
|
||||||
|
|
||||||
var shipping = context.Shippings.Add(new Entities.Shipping
|
|
||||||
{
|
|
||||||
OrderId = orderId,
|
|
||||||
AddressId = request.AddressId,
|
|
||||||
ShippingProviderId = request.ShippingProviderId,
|
|
||||||
Status = ShippingStatuses.Pending,
|
|
||||||
TrackingNumber = request.TrackingNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Failed to add shipping to order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateShippingStatusAsync(long orderId, ShippingStatuses newStatus, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Shippings
|
|
||||||
.Where(s => s.OrderId == orderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(s => s.Status, newStatus)
|
|
||||||
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow),
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Shipping not found for this order or status update failed.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result<Shipping>> GetShippingByOrderIdAsync(long orderId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var shipping = await context.Shippings
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken);
|
|
||||||
|
|
||||||
return shipping is not null
|
|
||||||
? Result.Ok(shipping.ToModel())
|
|
||||||
: Result.Fail<Shipping>("Shipping not found for this order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<Shipping>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result> RemoveShippingFromOrderAsync(long orderId, long shippingId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsDeleted = await context.Shippings
|
|
||||||
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
|
||||||
.ExecuteDeleteAsync(cancellationToken);
|
|
||||||
|
|
||||||
return rowsDeleted > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Shipping record not found for this order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateShippingTrackingNumberAsync(long orderId, long shippingId, string trackingNumber, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.Shippings
|
|
||||||
.Where(s => s.Id == shippingId && s.OrderId == orderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(s => s.TrackingNumber, trackingNumber)
|
|
||||||
.SetProperty(s => s.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Shipping record not found for this order.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<long>> CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
if(await context.ShippingProviders.AnyAsync(sp => sp.Type == request.Type, cancellationToken))
|
|
||||||
return Result.Fail("Shipping provider with the same type already exists.");
|
|
||||||
|
|
||||||
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
|
|
||||||
{
|
|
||||||
CreatedAt = DateTime.UtcNow,
|
|
||||||
Name = request.Name,
|
|
||||||
Type = request.Type,
|
|
||||||
Price = request.Price,
|
|
||||||
TrackingUrl = request.TrackingUrl
|
|
||||||
});
|
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
||||||
? Result.Ok(shippingProvider.Entity.Id)
|
|
||||||
: Result.Fail("Failed to create shipping provider.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<ShippingProvider[]>> GetShippingProvidersAsync(bool isEnabled, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var providers = await context.ShippingProviders.AsNoTracking().Where(sp => sp.Enabled == isEnabled)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return Result.Ok(providers.Select(sp => sp.ToModel()).ToArray());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<ShippingProvider[]>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result<ShippingProvider>> GetShippingProviderAsync(long providerId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var provider = await context.ShippingProviders.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(sp => sp.Id == providerId, cancellationToken);
|
|
||||||
|
|
||||||
return provider is not null
|
|
||||||
? Result.Ok(provider.ToModel())
|
|
||||||
: Result.Fail<ShippingProvider>("Shipping provider not found.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<ShippingProvider>(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateShippingProviderAsync(UpdateShippingProvider request, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
||||||
|
|
||||||
var rowsUpdated = await context.ShippingProviders
|
|
||||||
.Where(sp => sp.Id == request.ProviderId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(sp => sp.Name, request.Name)
|
|
||||||
.SetProperty(sp => sp.Price, request.Price)
|
|
||||||
.SetProperty(sp => sp.TrackingUrl, request.TrackingUrl)
|
|
||||||
.SetProperty(sp => sp.Enabled, request.Enabled)
|
|
||||||
.SetProperty(sp => sp.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
||||||
|
|
||||||
return rowsUpdated > 0
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail("Shipping provider not found.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Pages.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<BookPageConfiguration, BookPage>]
|
|
||||||
public class BookPage : Models.BookPage
|
|
||||||
{
|
|
||||||
public virtual AuthorBook Book { get; set; } = new();
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user