Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c2ce74032 | |||
| 902942eee6 | |||
| 70860efcfb | |||
| 20b747e89c | |||
| 7136e4fc70 | |||
| 4a85d01d1a | |||
| d55bf4f82f | |||
| f5efdde37c | |||
| 87da491ed6 | |||
| 1592d5dc8f | |||
| 08a64d1578 | |||
| f67f5eaf53 | |||
| 50a8a59d92 | |||
| 70c6e0bfbc | |||
| b70d9559b0 | |||
| 032b9e1818 | |||
| 424c1c6f8c | |||
| 81d5e8f07c | |||
| 7d5e9a18d8 | |||
| 20a53942b5 | |||
| 3656223b5f | |||
| 9edb2aa4aa | |||
| ccf30ac36b | |||
| 6ed023f2cf | |||
| d6fdf1b9c8 | |||
| 2c9f5a846c | |||
| 89a343a85f | |||
| 41f7c05be3 | |||
| 52d204e286 | |||
| 1a03355e84 | |||
| f245bc94e1 | |||
| 7743c3178e | |||
| da141311ff | |||
| ab3d8e6e9a | |||
| 97bde73777 | |||
| db4c348288 | |||
| a65e926a53 | |||
| 6683234642 | |||
| 1471d9e597 | |||
| 6ddbb9479a | |||
| e978aa17f8 | |||
| 6c7349a0f8 | |||
| a31f75c5ef | |||
| e97fd6cd3f | |||
| 7f4246ac63 | |||
| 184c7c252a | |||
| dfc62c8fe1 | |||
| bfe8c458d6 | |||
| 0f91f102e5 | |||
| e6e0475db1 | |||
| be9c83c8a3 | |||
| 65687d231e | |||
| 5090c60797 | |||
| 4523ef6151 | |||
| 9432252e15 | |||
| 36b3656886 | |||
| 47111a1a3a | |||
| f606b8fd3c | |||
| 2610275bef | |||
| 134d8429c0 | |||
| 42001998d6 | |||
| a42c51d7b2 | |||
| 6eb3d50375 | |||
| 26075cd9a7 | |||
| 4deb732804 | |||
| cecd9f90e9 | |||
| 73ba41beaf | |||
| e8e9a85c57 | |||
| 394429677e | |||
| 20d9387d0b | |||
| 32d1019eb5 |
+6
-22
@@ -17,29 +17,12 @@ steps:
|
|||||||
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/
|
||||||
# Extensions
|
- dotnet nuget push dist/LiteCharms.Features.TechShop.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
||||||
- dotnet pack LiteCharms.Extensions/LiteCharms.Extensions.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
- dotnet pack LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj -c Release -p:PackageVersion=$VERSION -o dist/
|
||||||
- dotnet nuget push dist/LiteCharms.Extensions.$VERSION.nupkg --api-key $NEXUS_KEY --source $NEXUS_URL
|
- dotnet nuget push dist/LiteCharms.Features.MidrandBooks.$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
|
||||||
@@ -49,6 +32,7 @@ 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
|
||||||
@@ -61,7 +45,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.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/)\",
|
\"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/)\",
|
||||||
\"draft\": false,
|
\"draft\": false,
|
||||||
\"prerelease\": false
|
\"prerelease\": false
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -3,6 +3,45 @@ 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,40 +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>
|
|
||||||
</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,22 +0,0 @@
|
|||||||
namespace LiteCharms.Entities.Configuration;
|
|
||||||
|
|
||||||
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Notification> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Notification));
|
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
|
||||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
|
|
||||||
builder.Property(f => f.Direction).IsRequired();
|
|
||||||
builder.Property(f => f.Author).IsRequired();
|
|
||||||
builder.Property(f => f.Title).IsRequired();
|
|
||||||
builder.Property(f => f.Description).IsRequired();
|
|
||||||
builder.Property(f => f.Platform).IsRequired();
|
|
||||||
builder.Property(f => f.PlatformAddress).IsRequired();
|
|
||||||
builder.Property(f => f.CorrelationId).IsRequired();
|
|
||||||
builder.Property(f => f.CorrelationIdType).IsRequired();
|
|
||||||
builder.Property(f => f.IsInternal).HasDefaultValue(true);
|
|
||||||
builder.Property(f => f.Processed).HasDefaultValue(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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.Notes).HasColumnType("jsonb").IsRequired(false);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Quote)
|
|
||||||
.WithOne(f => f.Order)
|
|
||||||
.HasForeignKey<Order>(f => f.QuoteId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.Customer)
|
|
||||||
.WithMany(f => f.Orders)
|
|
||||||
.HasForeignKey(f => f.CustomerId)
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace LiteCharms.Entities.Configuration;
|
|
||||||
|
|
||||||
public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<Product> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(Product));
|
|
||||||
|
|
||||||
builder.HasKey(f => f.Id);
|
|
||||||
builder.Property(f => f.Name).IsRequired();
|
|
||||||
builder.Property(f => f.Description).IsRequired();
|
|
||||||
builder.Property(f => f.Active).HasDefaultValue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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().ValueGeneratedOnAddOrUpdate();
|
|
||||||
builder.Property(f => f.ExpiredAt).IsRequired(false);
|
|
||||||
builder.Property(f => f.CustomerId).IsRequired();
|
|
||||||
builder.Property(f => f.Status).IsRequired();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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().ValueGeneratedOnAddOrUpdate();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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().ValueGeneratedOnAddOrUpdate();
|
|
||||||
builder.Property(f => f.Quantity).IsRequired().HasDefaultValue(1);
|
|
||||||
builder.Property(f => f.ProductPriceId).IsRequired();
|
|
||||||
|
|
||||||
builder.HasOne(f => f.ProductPrice)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(f => f.ProductPriceId)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
|
|
||||||
builder.HasOne(f => f.ShoppingCart)
|
|
||||||
.WithMany(f => f.ShoppingCartItems)
|
|
||||||
.HasForeignKey(f => f.ShoppingCartId)
|
|
||||||
.OnDelete(DeleteBehavior.NoAction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +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>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Nuget Package Details -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<PackageId>LiteCharms.Entities</PackageId>
|
|
||||||
<Version>1.0.20</Version>
|
|
||||||
<Authors>Khwezi Mngoma</Authors>
|
|
||||||
<Company>Lite Charms (PTY) Ltd</Company>
|
|
||||||
<Description>Shared entities for Lite Charms applications.</Description>
|
|
||||||
<PackageProjectUrl>https://gitea.khongisa.co.za/litecharms/components</PackageProjectUrl>
|
|
||||||
<RepositoryUrl>https://gitea.khongisa.co.za/litecharms/components.git</RepositoryUrl>
|
|
||||||
<RepositoryType>git</RepositoryType>
|
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
|
||||||
<PackageTags>utility;dotnet</PackageTags>
|
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="..\LICENSE" Pack="true" PackagePath="\"/>
|
|
||||||
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!-- Database -->
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
|
|
||||||
|
|
||||||
<!-- Global Usings -->
|
|
||||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
|
||||||
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\LiteCharms.Models\LiteCharms.Models.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using LiteCharms.Entities.Configuration;
|
|
||||||
|
|
||||||
namespace LiteCharms.Entities;
|
|
||||||
|
|
||||||
[EntityTypeConfiguration<OrderConfiguration, Order>]
|
|
||||||
public class Order : Models.Order
|
|
||||||
{
|
|
||||||
public virtual OrderRefund? Refund { get; set; }
|
|
||||||
|
|
||||||
public virtual Customer? Customer { get; set; }
|
|
||||||
|
|
||||||
public virtual Quote? Quote { get; set; }
|
|
||||||
|
|
||||||
public virtual ShoppingCart? ShoppingCart { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace LiteCharms.Entities;
|
|
||||||
|
|
||||||
public class ShoppingCartItem : Models.ShoppingCartItem
|
|
||||||
{
|
|
||||||
public virtual ShoppingCart? ShoppingCart { get; set; }
|
|
||||||
|
|
||||||
public virtual ProductPrice? ProductPrice { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using LiteCharms.Models.Configuraton.Email;
|
|
||||||
|
|
||||||
namespace LiteCharms.Extensions;
|
|
||||||
|
|
||||||
public static class Email
|
|
||||||
{
|
|
||||||
public static IServiceCollection AddEmailServices(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.Configure<SmtpSettings>(configuration.GetSection("Email"));
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
using LiteCharms.Models;
|
|
||||||
|
|
||||||
namespace LiteCharms.Extensions;
|
|
||||||
|
|
||||||
public static class EntityModeMappers
|
|
||||||
{
|
|
||||||
public static ShoppingCartItem ToModel(this Entities.ShoppingCartItem entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
ProductPriceId = entity.ProductPriceId,
|
|
||||||
Quantity = entity.Quantity,
|
|
||||||
ShoppingCartId = entity.ShoppingCartId
|
|
||||||
};
|
|
||||||
|
|
||||||
public static ShoppingCart ToModel(this Entities.ShoppingCart entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
OrderId = entity.OrderId,
|
|
||||||
QuoteId = entity.QuoteId
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Quote ToModel(this Entities.Quote entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
ExpiredAt = entity.ExpiredAt,
|
|
||||||
Reason = entity.Reason,
|
|
||||||
ShoppingCartId = entity.ShoppingCartId,
|
|
||||||
Status = entity.Status
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Notification ToModel(this Entities.Notification entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
Description = entity.Description,
|
|
||||||
Direction = entity.Direction,
|
|
||||||
CorrelationId = entity.CorrelationId,
|
|
||||||
CorrelationIdType = entity.CorrelationIdType,
|
|
||||||
IsInternal = entity.IsInternal,
|
|
||||||
Author = entity.Author,
|
|
||||||
Platform = entity.Platform,
|
|
||||||
PlatformAddress = entity.PlatformAddress,
|
|
||||||
Title = entity.Title,
|
|
||||||
Processed = entity.Processed
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Customer ToModel(this Entities.Customer entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
Active = entity.Active,
|
|
||||||
Address = entity.Address,
|
|
||||||
City = entity.City,
|
|
||||||
Company = entity.Company,
|
|
||||||
Country = entity.Country,
|
|
||||||
Discord = entity.Discord,
|
|
||||||
Email = entity.Email,
|
|
||||||
LastName = entity.LastName,
|
|
||||||
LinkedIn = entity.LinkedIn,
|
|
||||||
Name = entity.Name,
|
|
||||||
Phone = entity.Phone,
|
|
||||||
PostalCode = entity.PostalCode,
|
|
||||||
Region = entity.Region,
|
|
||||||
Slack = entity.Slack,
|
|
||||||
Tax = entity.Tax,
|
|
||||||
Website = entity.Website,
|
|
||||||
Whatsapp = entity.Whatsapp
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Lead ToModel(this Entities.Lead entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
AdGroupId = entity.AdGroupId,
|
|
||||||
AdName = entity.AdName,
|
|
||||||
AppClickId = entity.AppClickId,
|
|
||||||
AttributionHash = entity.AttributionHash,
|
|
||||||
CampaignId = entity.CampaignId,
|
|
||||||
ClickLocation = entity.ClickLocation,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
FeedItemId = entity.FeedItemId,
|
|
||||||
Source = entity.Source,
|
|
||||||
ClickId = entity.ClickId,
|
|
||||||
TargetId = entity.TargetId,
|
|
||||||
WebClickId = entity.WebClickId,
|
|
||||||
Status = entity.Status
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Order ToModel(this Entities.Order entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
UpdatedAt = entity.UpdatedAt,
|
|
||||||
CustomerId = entity.CustomerId,
|
|
||||||
Notes = entity.Notes,
|
|
||||||
RefundId = entity.RefundId,
|
|
||||||
QuoteId = entity.QuoteId,
|
|
||||||
Status = entity.Status,
|
|
||||||
ShoppingCartId = entity.ShoppingCartId
|
|
||||||
};
|
|
||||||
|
|
||||||
public static OrderRefund ToModel(this Entities.OrderRefund entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
OrderId = entity.OrderId,
|
|
||||||
Reason = entity.Reason,
|
|
||||||
Amount = entity.Amount
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Product ToModel(this Entities.Product entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
Name = entity.Name,
|
|
||||||
Description = entity.Description,
|
|
||||||
Active = entity.Active
|
|
||||||
};
|
|
||||||
|
|
||||||
public static ProductPrice ToModel(this Entities.ProductPrice entity) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Id = entity.Id,
|
|
||||||
ProductId = entity.ProductId,
|
|
||||||
Price = entity.Price,
|
|
||||||
Active = entity.Active,
|
|
||||||
CreatedAt = entity.CreatedAt,
|
|
||||||
Discount = entity.Discount,
|
|
||||||
UpdatedAt = entity.UpdatedAt
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using LiteCharms.Features.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Tests;
|
||||||
|
|
||||||
|
public class Fixture : IDisposable
|
||||||
|
{
|
||||||
|
public IConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
|
public IServiceProvider Services { get; set; }
|
||||||
|
|
||||||
|
public IMediator Mediator { get; set; }
|
||||||
|
|
||||||
|
public Fixture()
|
||||||
|
{
|
||||||
|
Configuration = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddUserSecrets<Fixture>()
|
||||||
|
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Services = new ServiceCollection()
|
||||||
|
.AddMediator()
|
||||||
|
.AddLogging()
|
||||||
|
//.AddMidrandShopServices()
|
||||||
|
.AddEmailServiceBus()
|
||||||
|
.AddGarageS3(Configuration)
|
||||||
|
.AddMidrandShopDatabase(Configuration)
|
||||||
|
.AddEmailServices(Configuration)
|
||||||
|
.AddSingleton(Configuration)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
Mediator = Services.GetRequiredService<IMediator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<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="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"BookshopS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop",
|
||||||
|
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
|
"BookshopQuotesS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop.quotes",
|
||||||
|
"CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
|
"Email": {
|
||||||
|
"Credentials": {
|
||||||
|
"Username": "shop@litecharms.co.za"
|
||||||
|
},
|
||||||
|
"Port": 465,
|
||||||
|
"Host": "mail.litecharms.co.za",
|
||||||
|
"UseSsl": true
|
||||||
|
},
|
||||||
|
"Monitoring": {
|
||||||
|
"ApiKey": "",
|
||||||
|
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
|
||||||
|
"ServiceName": "LiteCharms.LeadGenerator"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
|
public interface IService;
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var book = await context.Books.FirstOrDefaultAsync(b => b.Id == bookId, cancellationToken);
|
||||||
|
|
||||||
|
if (book is null)
|
||||||
|
return Result.Fail(new Error($"Book with ID {bookId} not found"));
|
||||||
|
|
||||||
|
book.UpdatedAt = DateTime.UtcNow;
|
||||||
|
book.Enabled = isEnabled;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error($"Failed to change status of book with ID {bookId}"));
|
||||||
|
}
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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!.Price)
|
||||||
|
.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
|
||||||
|
{
|
||||||
|
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!.Price)
|
||||||
|
.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!.Price)
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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; } = new();
|
||||||
|
|
||||||
|
public new virtual Product? Product { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<BookPage> Pages { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
||||||
|
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<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
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<AuthorBook[]>(new Error($"Author with ID {authorId} not found"));
|
||||||
|
|
||||||
|
var books = await context.Books
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(b => b.Author)
|
||||||
|
.Include(b => b.Product!.Price)
|
||||||
|
.OrderByDescending(b => b.CreatedAt)
|
||||||
|
.Where(p => p.AuthorId == authorId)
|
||||||
|
.AsSplitQuery()
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return books?.Length > 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> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
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.Enabled = isEnabled;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error($"Failed to change status of author with ID {authorId}"));
|
||||||
|
}
|
||||||
|
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[]>> GetAuthors(DateRange range, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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(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(new Error($"An author with the email {request.Email} already exists"));
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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.SocialMedia).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(f => f.Enabled).HasDefaultValue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 SocialMedia[]? SocialMedia { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
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 => c.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), 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 && cc.Email!.Equals(request.Email, StringComparison.OrdinalIgnoreCase), 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 contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||||
|
|
||||||
|
if (contact is null)
|
||||||
|
return Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||||
|
|
||||||
|
contact.UpdatedAt = DateTime.UtcNow;
|
||||||
|
contact.Name = request.Name;
|
||||||
|
contact.LastName = request.LastName;
|
||||||
|
contact.Email = request.Email;
|
||||||
|
contact.Phone = request.Phone;
|
||||||
|
contact.Type = request.Type;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error("Failed to update customer contact."));
|
||||||
|
}
|
||||||
|
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 address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||||
|
|
||||||
|
if (address is null)
|
||||||
|
return Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||||
|
|
||||||
|
address.UpdatedAt = DateTime.UtcNow;
|
||||||
|
address.Street = request.Street;
|
||||||
|
address.City = request.City;
|
||||||
|
address.State = request.State;
|
||||||
|
address.PostalCode = request.PostalCode;
|
||||||
|
address.Country = request.Country;
|
||||||
|
address.Type = request.Type;
|
||||||
|
address.BuildingType = request.BuildingType;
|
||||||
|
address.IsPrimary = request.IsPrimary;
|
||||||
|
address.Name = request.Name;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error("Failed to update customer address."));
|
||||||
|
}
|
||||||
|
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 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.Enabled = enabled;
|
||||||
|
customer.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error("Failed to update customer status."));
|
||||||
|
}
|
||||||
|
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 contact = await context.Contacts.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
||||||
|
|
||||||
|
if (contact is null)
|
||||||
|
return Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
||||||
|
|
||||||
|
contact.Enabled = enabled;
|
||||||
|
contact.IsPrimary = isPrimary;
|
||||||
|
contact.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error("Failed to update customer contact status."));
|
||||||
|
}
|
||||||
|
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 address = await context.Addresses.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
||||||
|
|
||||||
|
if (address is null)
|
||||||
|
return Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
||||||
|
|
||||||
|
address.Enabled = enabled;
|
||||||
|
address.IsPrimary = isPrimary;
|
||||||
|
address.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error("Failed to update customer address status."));
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<AddressConfiguration, Address>]
|
||||||
|
public class Address : Models.Address
|
||||||
|
{
|
||||||
|
public virtual Customer? Customer { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<ContactConfiguration, Contact>]
|
||||||
|
public class Contact : Models.Contact
|
||||||
|
{
|
||||||
|
public virtual Customer? Customer { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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.SocialMedia).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(c => c.Enabled).HasDefaultValue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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 SocialMedia[]? SocialMedia { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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 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;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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)
|
||||||
|
{
|
||||||
|
services.AddPooledDbContextFactory<MidrandBooksDbContext>(options =>
|
||||||
|
options.UseNpgsql(configuration.GetConnectionString(MidrandBooksDbConfigName)));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
|
||||||
|
public static class Shop
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddShopServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime)
|
||||||
|
{
|
||||||
|
var serviceType = typeof(IService);
|
||||||
|
|
||||||
|
var implementations = assembly.GetTypes()
|
||||||
|
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
||||||
|
|
||||||
|
foreach (var implementation in implementations)
|
||||||
|
{
|
||||||
|
var descriptor = new ServiceDescriptor(serviceType, implementation, serviceLifetime);
|
||||||
|
|
||||||
|
services.Add(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<SignAssembly>True</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
|
||||||
|
<UserSecretsId>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>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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().HasColumnType("decimal(18,2)");
|
||||||
|
builder.Property(o => o.Notes).HasMaxLength(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
public class ShippingProvider : Models.ShippingProvider
|
||||||
|
{
|
||||||
|
public virtual ICollection<Shipping> Shippings { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
|
|
||||||
|
public sealed record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes);
|
||||||
|
|
||||||
|
public sealed record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity);
|
||||||
|
|
||||||
|
public sealed record CreateShipping(long OrderId, long AddressId, long ShippingProviderId, string? TrackingNumber);
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
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 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.");
|
||||||
|
|
||||||
|
var existingItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||||
|
context.OrderItems.RemoveRange(existingItems);
|
||||||
|
|
||||||
|
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.");
|
||||||
|
|
||||||
|
context.OrderItems.Add(new Entities.OrderItem
|
||||||
|
{
|
||||||
|
OrderId = orderId,
|
||||||
|
AuthorBookId = item.AuthorBookId,
|
||||||
|
ProductPriceId = item.ProductPriceId,
|
||||||
|
Quantity = item.Quantity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to add items to order.");
|
||||||
|
}
|
||||||
|
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 orderItem = await context.OrderItems.FirstOrDefaultAsync(oi => oi.Id == orderItemId && oi.OrderId == orderId, cancellationToken);
|
||||||
|
|
||||||
|
if (orderItem is null)
|
||||||
|
return Result.Fail("Order item not found.");
|
||||||
|
|
||||||
|
context.OrderItems.Remove(orderItem);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to remove item from order.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> ClearOrderItemasAsync(long orderId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var orderItems = context.OrderItems.Where(oi => oi.OrderId == orderId);
|
||||||
|
|
||||||
|
context.OrderItems.RemoveRange(orderItems);
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var orders = await context.Orders
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(o => o.CreatedAt >= range.From.ToDateTime(TimeOnly.MinValue) && o.CreatedAt <= range.To.ToDateTime(TimeOnly.MaxValue))
|
||||||
|
.Skip(index * 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 order = await context.Orders.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
|
||||||
|
|
||||||
|
if (order is null)
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
order.UpdatedAt = DateTime.UtcNow;
|
||||||
|
order.Status = newStatus;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update order status.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> 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 shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken);
|
||||||
|
|
||||||
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
shipping.UpdatedAt = DateTime.UtcNow;
|
||||||
|
shipping.Status = newStatus;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update shipping status.");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
|
||||||
|
return Result.Fail("Order not found.");
|
||||||
|
|
||||||
|
var shipping = await context.Shippings.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken);
|
||||||
|
|
||||||
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
context.Shippings.Remove(shipping);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to remove shipping from 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 shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken);
|
||||||
|
|
||||||
|
if (shipping is null)
|
||||||
|
return Result.Fail("Shipping not found for this order.");
|
||||||
|
|
||||||
|
shipping.UpdatedAt = DateTime.UtcNow;
|
||||||
|
shipping.TrackingNumber = trackingNumber;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update shipping tracking number.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> 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
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
Type = request.Type,
|
||||||
|
Price = request.Price,
|
||||||
|
TrackingUrl = request.TrackingUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: 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 provider = await context.ShippingProviders.FirstOrDefaultAsync(sp => sp.Id == request.ProviderId, cancellationToken);
|
||||||
|
|
||||||
|
if (provider is null)
|
||||||
|
return Result.Fail("Shipping provider not found.");
|
||||||
|
|
||||||
|
provider.UpdatedAt = DateTime.UtcNow;
|
||||||
|
provider.Enabled = request.Enabled;
|
||||||
|
provider.Name = request.Name;
|
||||||
|
provider.Price = request.Price;
|
||||||
|
provider.TrackingUrl = request.TrackingUrl;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update shipping provider status.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages.Entities;
|
||||||
|
|
||||||
|
public sealed class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<BookPage> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("BookPages");
|
||||||
|
|
||||||
|
builder.HasKey(bp => bp.Id);
|
||||||
|
builder.Property(bp => bp.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(bp => bp.UpdatedAt).HasDefaultValueSql("now()");
|
||||||
|
builder.Property(bp => bp.Number).IsRequired().HasDefaultValue(0);
|
||||||
|
builder.Property(bp => bp.AuthorBookId).IsRequired();
|
||||||
|
builder.Property(bp => bp.Content).IsRequired();
|
||||||
|
builder.Property(bp => bp.Type).IsRequired();
|
||||||
|
builder.Property(bp => bp.ContentType).IsRequired();
|
||||||
|
builder.Property(bp => bp.Notes).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(bp => bp.References).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(bp => bp.Enabled).HasDefaultValue(true);
|
||||||
|
|
||||||
|
builder.HasOne(f =>f.Book)
|
||||||
|
.WithMany(b => b.Pages)
|
||||||
|
.HasForeignKey(f => f.AuthorBookId)
|
||||||
|
.OnDelete(DeleteBehavior.NoAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
|
|
||||||
|
public class BookPage
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public long AuthorBookId { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public BookPageTypes Type { get; set; }
|
||||||
|
|
||||||
|
public BookContentTypes ContentType { get; set; }
|
||||||
|
|
||||||
|
public int Number { get; set; }
|
||||||
|
|
||||||
|
public byte[]? Content { get; set; }
|
||||||
|
|
||||||
|
public string[]? Notes { get; set; }
|
||||||
|
|
||||||
|
public PageReference[]? References { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
|
|
||||||
|
public class CreateBookPage
|
||||||
|
{
|
||||||
|
public BookPageTypes Type { get; set; }
|
||||||
|
|
||||||
|
public BookContentTypes ContentType { get; set; }
|
||||||
|
|
||||||
|
public int Number { get; set; }
|
||||||
|
|
||||||
|
public byte[]? Content { get; set; }
|
||||||
|
|
||||||
|
public string[]? Notes { get; set; }
|
||||||
|
|
||||||
|
public PageReference[]? References { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
|
|
||||||
|
public sealed class UpdateBookPage : CreateBookPage;
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Pages;
|
||||||
|
|
||||||
|
public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> DeleteAllAsync(long authorBookId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
||||||
|
return Result.Fail("Book not found");
|
||||||
|
|
||||||
|
var pages = await context.Pages.Where(p => p.AuthorBookId == authorBookId).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (pages.Count == 0)
|
||||||
|
return Result.Fail("No pages found for the specified book");
|
||||||
|
|
||||||
|
context.Pages.RemoveRange(pages);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> DeleteByPageTypeAsync(long authorBookId, int pageNumber, BookPageTypes pageType, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.AuthorBookId == authorBookId && p.Number == pageNumber && p.Type == pageType, cancellationToken);
|
||||||
|
|
||||||
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
context.Pages.Remove(page);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to delete page");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdatePageStatusAsync(long bookPageId, bool enabled, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
|
|
||||||
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
page.UpdatedAt = DateTime.UtcNow;
|
||||||
|
page.Enabled = enabled;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update page status");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> DeletePageAsync(long bookPageId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
|
|
||||||
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
context.Pages.Remove(page);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to delete page");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdatePageAsync(long bookPageId, UpdateBookPage request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == bookPageId, cancellationToken);
|
||||||
|
|
||||||
|
if (page is null)
|
||||||
|
return Result.Fail("Page not found");
|
||||||
|
|
||||||
|
page.UpdatedAt = DateTime.UtcNow;
|
||||||
|
page.Type = request.Type;
|
||||||
|
page.ContentType = request.ContentType;
|
||||||
|
page.Number = request.Number;
|
||||||
|
page.Content = request.Content;
|
||||||
|
page.Notes = request.Notes;
|
||||||
|
page.References = request.References;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail("Failed to update page");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> CreatePageAsync(long authorBookId, CreateBookPage request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
||||||
|
return Result.Fail<long>("Book not found");
|
||||||
|
|
||||||
|
if (await context.Pages.AnyAsync(p => p.AuthorBookId == authorBookId && p.Number == request.Number && p.Type == request.Type, cancellationToken))
|
||||||
|
return Result.Fail<long>("A page with the same number already exists for this book");
|
||||||
|
|
||||||
|
var page = context.Pages.Add(new Entities.BookPage
|
||||||
|
{
|
||||||
|
AuthorBookId = authorBookId,
|
||||||
|
Type = request.Type,
|
||||||
|
ContentType = request.ContentType,
|
||||||
|
Number = request.Number,
|
||||||
|
Content = request.Content,
|
||||||
|
Notes = request.Notes,
|
||||||
|
References = request.References,
|
||||||
|
Enabled = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(page.Entity.Id)
|
||||||
|
: Result.Fail<long>("Failed to create page");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<BookPage[]>> GetPagesAsync(long authorBookId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Books.AnyAsync(b => b.Id == authorBookId, cancellationToken))
|
||||||
|
return Result.Fail<BookPage[]>("Book not found");
|
||||||
|
|
||||||
|
var pages = await context.Pages.AsNoTracking()
|
||||||
|
.Where(p => p.AuthorBookId == authorBookId).ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return pages?.Length > 0
|
||||||
|
? Result.Ok(pages.Select(p => p.ToModel()).ToArray())
|
||||||
|
: Result.Fail<BookPage[]>("No pages found for the specified book");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<BookPage[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<BookPage>> GetPageByNumberAsync(long pageId, int number, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == pageId && p.Number == number, cancellationToken);
|
||||||
|
|
||||||
|
return page is not null
|
||||||
|
? page.ToModel()
|
||||||
|
: Result.Fail<BookPage>("Page not found");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<BookPage>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<BookPage>> GetPageAsync(long pageId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var page = await context.Pages.FirstOrDefaultAsync(p => p.Id == pageId, cancellationToken);
|
||||||
|
|
||||||
|
return page is not null
|
||||||
|
? page.ToModel()
|
||||||
|
: Result.Fail<BookPage>("Page not found");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<BookPage>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<RefundConfiguration, Refund>]
|
||||||
|
public class Refund : Models.Refund
|
||||||
|
{
|
||||||
|
public virtual Order? Order { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
|
||||||
|
public sealed class RefundConfiguration : IEntityTypeConfiguration<Refund>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Refund> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Refunds");
|
||||||
|
|
||||||
|
builder.HasKey(r => r.Id);
|
||||||
|
builder.Property(r => r.OrderId).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(r => r.Amount).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(r => r.Reason).HasMaxLength(1000);
|
||||||
|
|
||||||
|
builder.HasOne(r => r.Order)
|
||||||
|
.WithMany(o => o.Refunds)
|
||||||
|
.HasForeignKey(r => r.OrderId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments.Models;
|
||||||
|
|
||||||
|
public class Refund
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
|
||||||
|
public RefundTypes Type { get; set; }
|
||||||
|
|
||||||
|
public RefundStatus Status { get; set; }
|
||||||
|
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Payments;
|
||||||
|
|
||||||
|
public sealed class PaymentService : IService
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Payments.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
|
||||||
|
public sealed class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> options) : DbContext(options)
|
||||||
|
{
|
||||||
|
public DbSet<Author> Authors => Set<Author>();
|
||||||
|
|
||||||
|
public DbSet<Product> Products => Set<Product>();
|
||||||
|
|
||||||
|
public DbSet<ProductPrice> Prices => Set<ProductPrice>();
|
||||||
|
|
||||||
|
public DbSet<AuthorBook> Books => Set<AuthorBook>();
|
||||||
|
|
||||||
|
public DbSet<BookPage> Pages => Set<BookPage>();
|
||||||
|
|
||||||
|
public DbSet<Contact> Contacts => Set<Contact>();
|
||||||
|
|
||||||
|
public DbSet<Address> Addresses => Set<Address>();
|
||||||
|
|
||||||
|
public DbSet<Customer> Customers => Set<Customer>();
|
||||||
|
|
||||||
|
public DbSet<Order> Orders => Set<Order>();
|
||||||
|
|
||||||
|
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
|
||||||
|
|
||||||
|
public DbSet<Refund> Refunds => Set<Refund>();
|
||||||
|
|
||||||
|
public DbSet<Shipping> Shippings => Set<Shipping>();
|
||||||
|
|
||||||
|
public DbSet<ShippingProvider> ShippingProviders => Set<ShippingProvider>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using static LiteCharms.Features.MidrandBooks.Extensions.Postgres;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
|
||||||
|
public sealed class MidrandBooksDbContextFactory : IDesignTimeDbContextFactory<MidrandBooksDbContext>
|
||||||
|
{
|
||||||
|
public MidrandBooksDbContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddUserSecrets(typeof(MidrandBooksDbContext).Assembly)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var optionsBuilder = new DbContextOptionsBuilder<MidrandBooksDbContext>();
|
||||||
|
optionsBuilder.UseNpgsql(configuration.GetConnectionString(MidrandBooksDbConfigName));
|
||||||
|
|
||||||
|
return new MidrandBooksDbContext(optionsBuilder.Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
+875
@@ -0,0 +1,875 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MidrandBooksDbContext))]
|
||||||
|
[Migration("20260527070840_Init")]
|
||||||
|
partial class Init
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("PublisherType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BuildingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("City")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Country")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PostalCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Street")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Addresses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Contacts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Customers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<decimal>("Total")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ProductPriceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductPriceId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AddressId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ShippingProviderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ShippingProviderId");
|
||||||
|
|
||||||
|
b.ToTable("Shippings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ShippingProviders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("Notes")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<PageReference[]>("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.ToTable("BookPages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("Refunds", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("Categories")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<ProductMetadata>("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Products", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Prices", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
|
.WithMany("Books")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Contacts")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", "ProductPrice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductPriceId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AuthorBook");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithOne("Shipping")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", "ShippingProvider")
|
||||||
|
.WithMany("Shippings")
|
||||||
|
.HasForeignKey("ShippingProviderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Address");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ShippingProvider");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "Book")
|
||||||
|
.WithMany("Pages")
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("Refunds")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany("Prices")
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", null)
|
||||||
|
.WithOne("Price")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", "ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Pages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("Contacts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderItems");
|
||||||
|
|
||||||
|
b.Navigation("Refunds");
|
||||||
|
|
||||||
|
b.Navigation("Shipping");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Shippings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Price");
|
||||||
|
|
||||||
|
b.Navigation("Prices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
using System;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Init : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Authors",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
PublisherType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Company = table.Column<string>(type: "text", nullable: true),
|
||||||
|
VatNumber = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||||
|
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
LastName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
Biography = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||||
|
Email = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
|
Website = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
ThumbnailImageUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||||
|
SocialMedia = table.Column<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Authors", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Customers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Company = table.Column<string>(type: "text", nullable: true),
|
||||||
|
VatNumber = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Website = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Phone = table.Column<string>(type: "text", nullable: false),
|
||||||
|
SocialMedia = table.Column<SocialMedia[]>(type: "jsonb", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Customers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Orders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Total = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
|
||||||
|
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||||
|
InvoiceUrl = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Orders", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Products",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||||
|
Summary = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
|
Description = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ImageUrl = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ThumbnailUrls = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
|
Categories = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
|
Metadata = table.Column<ProductMetadata>(type: "jsonb", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Products", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ShippingProviders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Price = table.Column<decimal>(type: "numeric", nullable: true),
|
||||||
|
TrackingUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ShippingProviders", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Addresses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
BuildingType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Street = table.Column<string>(type: "text", nullable: false),
|
||||||
|
City = table.Column<string>(type: "text", nullable: false),
|
||||||
|
State = table.Column<string>(type: "text", nullable: false),
|
||||||
|
PostalCode = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Country = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Addresses", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Addresses_Customers_CustomerId",
|
||||||
|
column: x => x.CustomerId,
|
||||||
|
principalTable: "Customers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Contacts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CustomerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
LastName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Phone = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Contacts", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Contacts_Customers_CustomerId",
|
||||||
|
column: x => x.CustomerId,
|
||||||
|
principalTable: "Customers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Refunds",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Reason = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Refunds", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Refunds_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Books",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
AuthorId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Rating = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Ranking = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Books", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Books_Authors_AuthorId",
|
||||||
|
column: x => x.AuthorId,
|
||||||
|
principalTable: "Authors",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Books_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Prices",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
|
Discount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Prices", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Prices_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ProductPrice",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Discount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ProductPrice", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ProductPrice_Products_ProductId",
|
||||||
|
column: x => x.ProductId,
|
||||||
|
principalTable: "Products",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Shippings",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
AddressId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ShippingProviderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
TrackingNumber = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Shippings", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_Addresses_AddressId",
|
||||||
|
column: x => x.AddressId,
|
||||||
|
principalTable: "Addresses",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Shippings_ShippingProviders_ShippingProviderId",
|
||||||
|
column: x => x.ShippingProviderId,
|
||||||
|
principalTable: "ShippingProviders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BookPages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
AuthorBookId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, defaultValueSql: "now()"),
|
||||||
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ContentType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Number = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||||
|
Content = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
Notes = table.Column<string>(type: "jsonb", nullable: true),
|
||||||
|
References = table.Column<PageReference[]>(type: "jsonb", nullable: true),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BookPages", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BookPages_Books_AuthorBookId",
|
||||||
|
column: x => x.AuthorBookId,
|
||||||
|
principalTable: "Books",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OrderItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
|
||||||
|
OrderId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
AuthorBookId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ProductPriceId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Quantity = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OrderItems", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Books_AuthorBookId",
|
||||||
|
column: x => x.AuthorBookId,
|
||||||
|
principalTable: "Books",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Orders_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "Orders",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_OrderItems_Prices_ProductPriceId",
|
||||||
|
column: x => x.ProductPriceId,
|
||||||
|
principalTable: "Prices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Addresses_CustomerId",
|
||||||
|
table: "Addresses",
|
||||||
|
column: "CustomerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BookPages_AuthorBookId",
|
||||||
|
table: "BookPages",
|
||||||
|
column: "AuthorBookId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Books_AuthorId",
|
||||||
|
table: "Books",
|
||||||
|
column: "AuthorId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Books_ProductId",
|
||||||
|
table: "Books",
|
||||||
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Contacts_CustomerId",
|
||||||
|
table: "Contacts",
|
||||||
|
column: "CustomerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_AuthorBookId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "AuthorBookId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_OrderId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "OrderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OrderItems_ProductPriceId",
|
||||||
|
table: "OrderItems",
|
||||||
|
column: "ProductPriceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Prices_ProductId",
|
||||||
|
table: "Prices",
|
||||||
|
column: "ProductId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ProductPrice_ProductId",
|
||||||
|
table: "ProductPrice",
|
||||||
|
column: "ProductId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Refunds_OrderId",
|
||||||
|
table: "Refunds",
|
||||||
|
column: "OrderId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_AddressId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "AddressId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_OrderId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "OrderId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Shippings_ShippingProviderId",
|
||||||
|
table: "Shippings",
|
||||||
|
column: "ShippingProviderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BookPages");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Contacts");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OrderItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ProductPrice");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Refunds");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Shippings");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Books");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Prices");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Addresses");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Orders");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ShippingProviders");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Authors");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Products");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Customers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+872
@@ -0,0 +1,872 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MidrandBooksDbContext))]
|
||||||
|
partial class MidrandBooksDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("PublisherType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BuildingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("City")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Country")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PostalCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Street")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Addresses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Contacts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<SocialMedia[]>("SocialMedia")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Customers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<decimal>("Total")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ProductPriceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductPriceId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AddressId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ShippingProviderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ShippingProviderId");
|
||||||
|
|
||||||
|
b.ToTable("Shippings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ShippingProviders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("Notes")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<PageReference[]>("References")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.ToTable("BookPages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("Refunds", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("Categories")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<ProductMetadata>("Metadata")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("ThumbnailUrls")
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Products", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Prices", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", "Author")
|
||||||
|
.WithMany("Books")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", "Customer")
|
||||||
|
.WithMany("Contacts")
|
||||||
|
.HasForeignKey("CustomerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "AuthorBook")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("OrderItems")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", "ProductPrice")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProductPriceId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AuthorBook");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ProductPrice");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", "Address")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddressId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithOne("Shipping")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", "ShippingProvider")
|
||||||
|
.WithMany("Shippings")
|
||||||
|
.HasForeignKey("ShippingProviderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Address");
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
|
||||||
|
b.Navigation("ShippingProvider");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", "Book")
|
||||||
|
.WithMany("Pages")
|
||||||
|
.HasForeignKey("AuthorBookId")
|
||||||
|
.OnDelete(DeleteBehavior.NoAction)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", "Order")
|
||||||
|
.WithMany("Refunds")
|
||||||
|
.HasForeignKey("OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
|
.WithMany("Prices")
|
||||||
|
.HasForeignKey("ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Product");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", null)
|
||||||
|
.WithOne("Price")
|
||||||
|
.HasForeignKey("LiteCharms.Features.MidrandBooks.Products.Models.ProductPrice", "ProductId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Pages");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("Contacts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderItems");
|
||||||
|
|
||||||
|
b.Navigation("Refunds");
|
||||||
|
|
||||||
|
b.Navigation("Shipping");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Shippings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Price");
|
||||||
|
|
||||||
|
b.Navigation("Prices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<ProductConfiguration, Product>]
|
||||||
|
public class Product : Models.Product
|
||||||
|
{
|
||||||
|
public virtual ICollection<ProductPrice> Prices { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Product> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Products");
|
||||||
|
|
||||||
|
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(255);
|
||||||
|
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
||||||
|
builder.Property(f => f.Description).HasMaxLength(1024);
|
||||||
|
builder.Property(f => f.ImageUrl).HasMaxLength(1024);
|
||||||
|
builder.Property(f => f.ThumbnailUrls).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(f => f.Metadata).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(f => f.Categories).IsRequired(false).HasColumnType("jsonb");
|
||||||
|
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
|
||||||
|
public class ProductPrice : Models.ProductPrice
|
||||||
|
{
|
||||||
|
public virtual Product Product { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
public sealed class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Prices");
|
||||||
|
|
||||||
|
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.ProductId).IsRequired();
|
||||||
|
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(f => f.Discount).IsRequired().HasPrecision(18, 2);
|
||||||
|
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
||||||
|
|
||||||
|
builder.HasOne(f => f.Product)
|
||||||
|
.WithMany(p => p.Prices)
|
||||||
|
.HasForeignKey(f => f.ProductId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
public sealed class CreateProductPrice
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
public decimal Discount { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
public class Product
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public ProductTypes Type { get; set; }
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
public string[]? ThumbnailUrls { get; set; }
|
||||||
|
|
||||||
|
public string[]? Categories { get; set; }
|
||||||
|
|
||||||
|
public ProductMetadata? Metadata { get; set; }
|
||||||
|
|
||||||
|
public ProductPrice? Price { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
public class ProductPrice
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
public decimal Discount { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
|
||||||
|
public sealed record CreateProduct
|
||||||
|
{
|
||||||
|
public required ProductTypes Type { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public required string Summary { get; set; }
|
||||||
|
|
||||||
|
public required string Description { get; set; }
|
||||||
|
|
||||||
|
public required string ImageUrl { get; set; }
|
||||||
|
|
||||||
|
public string[]? ThumbnailUrls { get; set; }
|
||||||
|
|
||||||
|
public string[]? Categories { get; set; }
|
||||||
|
|
||||||
|
public ProductMetadata? Metadata { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Products;
|
||||||
|
|
||||||
|
public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var productPrice = await context.Prices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||||
|
|
||||||
|
if (productPrice is null)
|
||||||
|
return Result.Fail(new Error($"Product price with ID {productPriceId} not found"));
|
||||||
|
|
||||||
|
productPrice.UpdatedAt = DateTime.UtcNow;
|
||||||
|
productPrice.Enabled = isEnabled;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error($"Failed to change status of product price with ID {productPriceId}"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateProductStatusAsync(long productId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||||
|
|
||||||
|
if (product is null)
|
||||||
|
return Result.Fail(new Error($"Product with ID {productId} not found"));
|
||||||
|
|
||||||
|
product.UpdatedAt = DateTime.UtcNow;
|
||||||
|
product.Enabled = isEnabled;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error($"Failed to change status of product with ID {productId}"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Product[]>> SearchProductsAsync(ProductFilter filter, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var query = context.Products.AsQueryable();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Title))
|
||||||
|
query = query.Where(p => p.Name!.Contains(filter.Title));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Category))
|
||||||
|
query = query.Where(p => p.Categories!.Any(c => c == filter.Category));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
||||||
|
query = query.Where(p => p.Metadata!.Manufacturer == filter.Manufacturer);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter.SerialNumber))
|
||||||
|
query = query.Where(p => p.Metadata!.SerialNumber == filter.SerialNumber);
|
||||||
|
|
||||||
|
if (filter.MinPrice > 0)
|
||||||
|
query = query.Where(p => p.Prices!.Any(pr => pr.Amount >= filter.MinPrice && pr.Amount <= filter.MaxPrice));
|
||||||
|
|
||||||
|
var products = await query.AsNoTracking().Where(p => p.Enabled).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return products?.Count > 0
|
||||||
|
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
||||||
|
: Result.Fail<Product[]>("No products found.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Product[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> CreateProductPriceAsync(long productId, CreateProductPrice request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Products.AnyAsync(p => p.Id == productId, cancellationToken))
|
||||||
|
return Result.Fail<long>($"Product with ID {productId} does not exist.");
|
||||||
|
|
||||||
|
var existingPrices = await context.Prices.Where(p => p.ProductId == productId).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (existingPrices.Count > 0)
|
||||||
|
foreach (var existingPrice in existingPrices)
|
||||||
|
{
|
||||||
|
existingPrice.Enabled = false;
|
||||||
|
existingPrice.UpdatedAt = DateTime.UtcNow;
|
||||||
|
context.Prices.Update(existingPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
var price = context.Prices.Add(new Entities.ProductPrice
|
||||||
|
{
|
||||||
|
ProductId = productId,
|
||||||
|
Amount = request.Amount,
|
||||||
|
Discount = request.Discount,
|
||||||
|
Enabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(price.Entity.Id)
|
||||||
|
: Result.Fail<long>("Failed to create product price.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<long>> CreateProductAsync(CreateProduct request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (await context.Products.AnyAsync(p => p.Name == request.Name, cancellationToken))
|
||||||
|
return Result.Fail<long>("A product with the same name already exists.");
|
||||||
|
|
||||||
|
if (request.Metadata is not null)
|
||||||
|
if (await context.Products.AnyAsync(p => p.Metadata!.SerialNumber == request.Metadata.SerialNumber, cancellationToken))
|
||||||
|
return Result.Fail<long>("A product with the same metadata already exists.");
|
||||||
|
|
||||||
|
var product = context.Products.Add(new Entities.Product
|
||||||
|
{
|
||||||
|
UpdatedAt = DateTime.UtcNow,
|
||||||
|
Type = request.Type,
|
||||||
|
Name = request.Name,
|
||||||
|
Summary = request.Summary,
|
||||||
|
Description = request.Description,
|
||||||
|
ImageUrl = request.ImageUrl,
|
||||||
|
ThumbnailUrls = request.ThumbnailUrls,
|
||||||
|
Metadata = request.Metadata,
|
||||||
|
Categories = request.Categories,
|
||||||
|
Enabled = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(product.Entity.Id)
|
||||||
|
: Result.Fail<long>("Failed to create product.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<ProductPrice[]>> GetProductPriceAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var product = await context.Prices
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
|
.ThenBy(p => p.UpdatedAt)
|
||||||
|
.FirstOrDefaultAsync(p => p.ProductId == productId, cancellationToken);
|
||||||
|
|
||||||
|
return product is not null
|
||||||
|
? Result.Ok(new[] { product.ToModel() })
|
||||||
|
: Result.Fail<ProductPrice[]>(new Error($"No price found for product with ID {productId}"));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<ProductPrice[]>> GetProductPricesAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var prices = await context.Prices.Where(p => p.ProductId == productId).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return prices?.Count > 0
|
||||||
|
? prices.Select(p => p.ToModel()).ToArray()
|
||||||
|
: Result.Fail<ProductPrice[]>(new Error($"No prices found for product with ID {productId}"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Product>> GetProductAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var product = await context.Products.AsNoTracking().FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||||
|
|
||||||
|
return product is null
|
||||||
|
? Result.Fail<Product>(new Error($"Product with ID {productId} not found."))
|
||||||
|
: Result.Ok(product.ToModel());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Product>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Product[]>> GetProductsAsync(int offset, DateRange range, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var products = await context.Products
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(p => p.Prices)
|
||||||
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
|
.ThenByDescending(p => p.UpdatedAt)
|
||||||
|
.Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate)
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(range.MaxRecords)
|
||||||
|
.AsSplitQuery()
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(products.Select(p => p.ToModel()).ToArray())
|
||||||
|
: Result.Fail<Product[]>(new Error("Failed to retrieve products."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Product[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using LiteCharms.Features.Extensions;
|
||||||
|
using LiteCharms.Features.TechShop.Extensions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.Tests;
|
||||||
|
|
||||||
|
public class Fixture : IDisposable
|
||||||
|
{
|
||||||
|
public IConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
|
public IServiceProvider Services { get; set; }
|
||||||
|
|
||||||
|
public IMediator Mediator { get; set; }
|
||||||
|
|
||||||
|
public Fixture()
|
||||||
|
{
|
||||||
|
Configuration = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddUserSecrets<Fixture>()
|
||||||
|
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Services = new ServiceCollection()
|
||||||
|
.AddMediator()
|
||||||
|
.AddLogging()
|
||||||
|
.AddTechShopServices()
|
||||||
|
.AddEmailServiceBus()
|
||||||
|
.AddGarageS3(Configuration)
|
||||||
|
.AddTechShopDatabase(Configuration)
|
||||||
|
.AddEmailServices(Configuration)
|
||||||
|
.AddSingleton(Configuration)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
Mediator = Services.GetRequiredService<IMediator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<UserSecretsId>fa06c1a6-2ba8-4c47-b19b-11e0f78e7b92</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="10.0.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Global Usings -->
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Mediator" />
|
||||||
|
<Using Include="Xunit.Abstractions" />
|
||||||
|
<Using Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
|
<Using Include="Microsoft.Extensions.Configuration" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LiteCharms.Features.TechShop\LiteCharms.Features.TechShop.csproj" />
|
||||||
|
<ProjectReference Include="..\LiteCharms.Features\LiteCharms.Features.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using LiteCharms.Features.TechShop.Notifications;
|
||||||
|
using LiteCharms.Features.TechShop.Notifications.Events;
|
||||||
|
using static LiteCharms.Features.Extensions.Email;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.Tests;
|
||||||
|
|
||||||
|
public class NotificationsFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
private readonly NotificationService notificationService = fixture.Services.GetRequiredService<NotificationService>();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateNotificationCommand_ShouldSucceed()
|
||||||
|
{
|
||||||
|
Notifications.Models.CreateNotification request = new()
|
||||||
|
{
|
||||||
|
CorrelationId = Guid.CreateVersion7().ToString(),
|
||||||
|
CorrelationIdType = CorrelationIdTypes.None,
|
||||||
|
Direction = NotificationDirection.Outgoing,
|
||||||
|
Platform = NotificationPlatforms.Email,
|
||||||
|
Priority = Priorities.Medium,
|
||||||
|
Sender = "xUnit Test",
|
||||||
|
SenderAddress = "khwezi@mngoma.africa",
|
||||||
|
Recipient = $"{ShopEmailFromName} [Test]",
|
||||||
|
RecipientAddress = ShopEmailFromAddress,
|
||||||
|
Subject = "Test Message",
|
||||||
|
Message = "This is an automation test",
|
||||||
|
IsHtml = false,
|
||||||
|
IsInternal = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var createResult = await notificationService.CreateNotificationAsync(request);
|
||||||
|
|
||||||
|
Assert.True(createResult.IsSuccess);
|
||||||
|
|
||||||
|
foreach (var error in createResult.Errors) output.WriteLine(error.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetNotifications_ShouldReturn_AllNotifications()
|
||||||
|
{
|
||||||
|
DateRange range = new()
|
||||||
|
{
|
||||||
|
From = DateOnly.FromDateTime(new DateTime(2026, 04, 01, 0, 0, 0, DateTimeKind.Utc)),
|
||||||
|
To = DateOnly.FromDateTime(DateTime.UtcNow),
|
||||||
|
MaxRecords = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
var getResult = await notificationService.GetNotificationsAsync(range);
|
||||||
|
|
||||||
|
Assert.True(getResult.IsSuccess);
|
||||||
|
|
||||||
|
foreach (var error in getResult.Errors) output.WriteLine(error.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ProcessEmailNotificationsEvent_ShouldSucceed()
|
||||||
|
{
|
||||||
|
var notification = ProcessEmailNotificationsEvent.Create();
|
||||||
|
|
||||||
|
await fixture.Mediator.Publish(notification);
|
||||||
|
|
||||||
|
Assert.True(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using LiteCharms.Features.TechShop.Products;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.Tests;
|
||||||
|
|
||||||
|
public class ProductsFeatureTests(Fixture fixture, ITestOutputHelper output) : IClassFixture<Fixture>
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task GetProductsAsync_ReturnsProducts()
|
||||||
|
{
|
||||||
|
var productService = fixture.Services.GetRequiredService<ProductService>();
|
||||||
|
|
||||||
|
var result = await productService.GetProductsAsync();
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine($"Retrieved {result.Value.Length} products.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"BookshopS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop",
|
||||||
|
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
|
"BookshopQuotesS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop.quotes",
|
||||||
|
"CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
|
"Email": {
|
||||||
|
"Credentials": {
|
||||||
|
"Username": "shop@litecharms.co.za"
|
||||||
|
},
|
||||||
|
"Port": 465,
|
||||||
|
"Host": "mail.litecharms.co.za",
|
||||||
|
"UseSsl": true
|
||||||
|
},
|
||||||
|
"Monitoring": {
|
||||||
|
"ApiKey": "",
|
||||||
|
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
|
||||||
|
"ServiceName": "LiteCharms.LeadGenerator"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<PackageConfirguration, Package>]
|
||||||
|
public class Package : Models.Package
|
||||||
|
{
|
||||||
|
public virtual ICollection<PackageItem>? PackageItems { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
|
||||||
|
|
||||||
|
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().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(f => f.UpdatedAt).IsRequired(false).HasDefaultValueSql(null);
|
||||||
|
builder.Property(f => f.Name).IsRequired();
|
||||||
|
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
||||||
|
builder.Property(f => f.Description).IsRequired().HasMaxLength(2048);
|
||||||
|
builder.Property(f => f.ImageUrl).IsRequired(false).HasMaxLength(2048);
|
||||||
|
builder.Property(f => f.Active);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using LiteCharms.Features.TechShop.Products.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<PackageItemConfiguration, PackageItem>]
|
||||||
|
public class PackageItem : Models.PackageItem
|
||||||
|
{
|
||||||
|
public virtual Package? Package { get; set; }
|
||||||
|
|
||||||
|
public virtual ProductPrice? ProductPrice { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Entities;
|
||||||
|
|
||||||
|
public class PackageItemConfiguration : IEntityTypeConfiguration<PackageItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PackageItem> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable(nameof(PackageItem));
|
||||||
|
|
||||||
|
builder.HasKey(f => f.Id);
|
||||||
|
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||||
|
builder.Property(f => f.PackageId).IsRequired();
|
||||||
|
builder.Property(f => f.ProductPriceId).IsRequired();
|
||||||
|
builder.Property(f => f.Active);
|
||||||
|
|
||||||
|
builder.HasOne(f => f.Package)
|
||||||
|
.WithMany(f => f.PackageItems)
|
||||||
|
.HasForeignKey(pi => pi.PackageId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasOne(f => f.ProductPrice)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(pi => pi.ProductPriceId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Models;
|
||||||
|
|
||||||
|
public class Package
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
public bool Active { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace LiteCharms.Features.TechShop.CartPackages.Models;
|
||||||
|
|
||||||
|
public class PackageItem
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public Guid PackageId { get; set; }
|
||||||
|
|
||||||
|
public Guid ProductPriceId { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
|
||||||
|
public bool Active { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
using LiteCharms.Features.Extensions;
|
||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using LiteCharms.Features.TechShop.CartPackages.Models;
|
||||||
|
using LiteCharms.Features.TechShop.Extensions;
|
||||||
|
using LiteCharms.Features.TechShop.Postgres;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.CartPackages;
|
||||||
|
|
||||||
|
public class PackageService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||||
|
{
|
||||||
|
public async ValueTask<Result<Guid>> AddPackageItemAsync(Guid packageId, Guid productPriceId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken))
|
||||||
|
return Result.Fail($"Could not find package by ID {packageId}");
|
||||||
|
|
||||||
|
if (!await context.ProductPrices.AnyAsync(p => p.Id == productPriceId && p.Active == true, cancellationToken))
|
||||||
|
return Result.Fail($"Could not find an active product price by ID {productPriceId}");
|
||||||
|
|
||||||
|
if (await context.PackageItems.AnyAsync(p => p.ProductPriceId == productPriceId && p.PackageId == packageId, cancellationToken))
|
||||||
|
return Result.Fail<Guid>($"Product price {productPriceId} is already added to this package {packageId}");
|
||||||
|
|
||||||
|
var newPackageItem = context.PackageItems.Add(new Entities.PackageItem
|
||||||
|
{
|
||||||
|
PackageId = packageId,
|
||||||
|
ProductPriceId = productPriceId,
|
||||||
|
Active = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(newPackageItem.Entity.Id)
|
||||||
|
: Result.Fail<Guid>($"Failed to add new package item by ID {productPriceId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Guid>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Guid>> CreatePackageAsync(string? name, string? summary, string? description, string? ImageUrl, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (await context.Packages.AnyAsync(p => p.Name == name, cancellationToken))
|
||||||
|
return Result.Fail($"A package by the same name already exists: {name}");
|
||||||
|
|
||||||
|
var newPackage = context.Packages.Add(new Entities.Package
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Summary = summary,
|
||||||
|
Description = description,
|
||||||
|
ImageUrl = ImageUrl,
|
||||||
|
Active = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(newPackage.Entity.Id)
|
||||||
|
: Result.Fail($"Failed to create a new package by the name: {name}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Guid>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> DeletePackageItemAsync(Guid packageId, Guid packageItemId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken))
|
||||||
|
return Result.Fail($"Could not find package by ID {packageId}");
|
||||||
|
|
||||||
|
var item = await context.PackageItems.FirstOrDefaultAsync(p => p.Id == packageItemId && p.PackageId == packageId, cancellationToken);
|
||||||
|
|
||||||
|
if (item is null)
|
||||||
|
return Result.Fail($"Product item {packageItemId} is already added to this package {packageId}");
|
||||||
|
|
||||||
|
context.PackageItems.Remove(item);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail($"Failed to delete package item by id {packageItemId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> DeletePackageItemsAsync(Guid packageId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken))
|
||||||
|
return Result.Fail($"Could not find package by ID {packageId}");
|
||||||
|
|
||||||
|
var items = await context.PackageItems.Where(i => i.PackageId == packageId).ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
context.PackageItems.RemoveRange(items);
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail($"Failed to delete package {packageId} items");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Package>> GetPackageAsync(Guid packageId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken);
|
||||||
|
|
||||||
|
return package is not null
|
||||||
|
? Result.Ok(package.ToModel())
|
||||||
|
: Result.Fail($"Failed to find package by ID {packageId}");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Package>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<PackageItem[]>> GetPackageItemsAsync(Guid packageId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Packages.AnyAsync(p => p.Id == packageId, cancellationToken))
|
||||||
|
return Result.Fail<PackageItem[]>($"Package could not be found with ID {packageId}");
|
||||||
|
|
||||||
|
var items = await context.PackageItems.AsNoTracking()
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.Where(p => p.PackageId == packageId)
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return items?.Length > 0
|
||||||
|
? Result.Ok(items.Select(i => i.ToModel()).ToArray())
|
||||||
|
: Result.Fail<PackageItem[]>($"Could not find package items by package ID {packageId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<PackageItem[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Package[]>> GetPackagesAsync(Guid packageId, DateRange range, bool active, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var packages = await context.Packages
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.Where(p => p.CreatedAt >= fromDate && p.CreatedAt <= toDate)
|
||||||
|
.Where(p => p.Active == active)
|
||||||
|
.Take(range.MaxRecords)
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return packages?.Length > 0
|
||||||
|
? Result.Ok(packages.Select(o => o.ToModel()).ToArray())
|
||||||
|
: Result.Fail<Package[]>(new Error($"No packages found for the specified date range {range.From} - {range.To}."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Package[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Guid>> UpdatePackageAsync(Guid packageId, string? name, string? summary, string? description, string? ImageUrl, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (await context.Packages.AnyAsync(p => p.Name == name, cancellationToken))
|
||||||
|
return Result.Fail($"A package by the same name already exists: {name}");
|
||||||
|
|
||||||
|
var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken);
|
||||||
|
|
||||||
|
if (package is null)
|
||||||
|
return Result.Fail($"Could not find package by id {packageId}");
|
||||||
|
|
||||||
|
package.Name = name;
|
||||||
|
package.Summary = summary;
|
||||||
|
package.Description = description;
|
||||||
|
package.ImageUrl = ImageUrl;
|
||||||
|
package.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail($"Failed to update package with id {packageId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Guid>> UpdatePackageStatusAsync(Guid packageId, bool active, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var package = await context.Packages.FirstOrDefaultAsync(p => p.Id == packageId, cancellationToken);
|
||||||
|
|
||||||
|
if (package is null)
|
||||||
|
return Result.Fail($"Could not find package by id {packageId}");
|
||||||
|
|
||||||
|
package.Active = active;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail($"Failed to update package with id {packageId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using LiteCharms.Features.Models;
|
||||||
|
using LiteCharms.Features.TechShop.Customers.Models;
|
||||||
|
using LiteCharms.Features.TechShop.Extensions;
|
||||||
|
using LiteCharms.Features.TechShop.Postgres;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.TechShop.Customers;
|
||||||
|
|
||||||
|
public class CustomerService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||||
|
{
|
||||||
|
public async ValueTask<Result<Guid>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var customerEmail = request.Email.ToLower().Trim();
|
||||||
|
|
||||||
|
if (await context.Customers.AnyAsync(c => c.Email == customerEmail, cancellationToken))
|
||||||
|
return Result.Fail<Guid>(new Error($"A customer with the email {customerEmail} already exists"));
|
||||||
|
|
||||||
|
var newCustomer = context.Customers.Add(new Entities.Customer
|
||||||
|
{
|
||||||
|
Company = request.Company,
|
||||||
|
Name = request.Name,
|
||||||
|
LastName = request.LastName,
|
||||||
|
Tax = request.Tax,
|
||||||
|
Email = customerEmail,
|
||||||
|
Discord = request.Discord,
|
||||||
|
Slack = request.Slack,
|
||||||
|
LinkedIn = request.LinkedIn,
|
||||||
|
Whatsapp = request.Whatsapp,
|
||||||
|
Website = request.Website,
|
||||||
|
Phone = request.Phone,
|
||||||
|
Address = request.Address,
|
||||||
|
City = request.City,
|
||||||
|
Region = request.Region,
|
||||||
|
Country = request.Country,
|
||||||
|
PostalCode = request.PostalCode,
|
||||||
|
Active = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok(newCustomer.Entity.Id)
|
||||||
|
: Result.Fail<Guid>(new Error($"Failed to create customer {customerEmail}"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Guid>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Customer>> GetCustomerAsync(Guid customerId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
||||||
|
|
||||||
|
return customer is not null
|
||||||
|
? Result.Ok(customer.ToModel())
|
||||||
|
: Result.Fail<Customer>($"Customer not found with id {customerId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Customer>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Customer[]>> GetCustomersAsync(DateRange range, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
|
||||||
|
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
|
||||||
|
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var customers = await context.Customers.AsNoTracking()
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.Where(c => c.CreatedAt >= fromDate && c.CreatedAt <= toDate)
|
||||||
|
.Take(range.MaxRecords)
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
return customers?.Length > 0
|
||||||
|
? Result.Ok(customers.Select(c => c.ToModel()).ToArray())
|
||||||
|
: Result.Fail<Customer[]>(new Error("No customers found in the specified date range."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Customer[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateCustomerAsync(UpdateCustomer request, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == request.CustomerId, cancellationToken);
|
||||||
|
|
||||||
|
if (customer is null)
|
||||||
|
return Result.Fail(new Error($"Customer with ID {request.CustomerId} not found."));
|
||||||
|
|
||||||
|
customer.Name = request.Name;
|
||||||
|
customer.LastName = request.LastName;
|
||||||
|
customer.Email = request.Email;
|
||||||
|
customer.Company = request.Company;
|
||||||
|
customer.Address = request.Address;
|
||||||
|
customer.City = request.City;
|
||||||
|
customer.Region = request.Region;
|
||||||
|
customer.Country = request.Country;
|
||||||
|
customer.PostalCode = request.PostalCode;
|
||||||
|
customer.Phone = request.Phone;
|
||||||
|
customer.Tax = request.Tax;
|
||||||
|
customer.City = request.City;
|
||||||
|
customer.Discord = request.Discord;
|
||||||
|
customer.Slack = request.Slack;
|
||||||
|
customer.LinkedIn = request.LinkedIn;
|
||||||
|
customer.Whatsapp = request.Whatsapp;
|
||||||
|
|
||||||
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
? Result.Ok()
|
||||||
|
: Result.Fail(new Error($"Failed to update the customer {request.CustomerId}."));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
using LiteCharms.Entities.Configuration;
|
using LiteCharms.Features.TechShop.Leads.Entities;
|
||||||
|
using LiteCharms.Features.TechShop.Orders.Entities;
|
||||||
|
using LiteCharms.Features.TechShop.Quotes.Entities;
|
||||||
|
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
|
||||||
|
|
||||||
namespace LiteCharms.Entities;
|
namespace LiteCharms.Features.TechShop.Customers.Entities;
|
||||||
|
|
||||||
[EntityTypeConfiguration<CustomerConfiguration, Customer>]
|
[EntityTypeConfiguration<CustomerConfiguration, Customer>]
|
||||||
public class Customer : Models.Customer
|
public class Customer : Models.Customer
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user