Completed initial database design
continuous-integration/drone/pr Build is passing

Sealed qualifying public classes
Migrated database changes
This commit is contained in:
Khwezi Mngoma
2026-05-27 09:12:04 +02:00
parent 70860efcfb
commit 902942eee6
86 changed files with 2883 additions and 140 deletions
+39
View File
@@ -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 ####
@@ -5,7 +5,7 @@ using LiteCharms.Features.MidrandBooks.Postgres;
namespace LiteCharms.Features.MidrandBooks.AuthorBooks; namespace LiteCharms.Features.MidrandBooks.AuthorBooks;
public class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken) public async ValueTask<Result> UpdateBookStatusAsync(long bookId, bool isEnabled, CancellationToken cancellationToken)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities; namespace LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
public class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook> public sealed class AuthorBookConfiguration : IEntityTypeConfiguration<AuthorBook>
{ {
public void Configure(EntityTypeBuilder<AuthorBook> builder) public void Configure(EntityTypeBuilder<AuthorBook> builder)
{ {
@@ -7,7 +7,7 @@ using LiteCharms.Features.Models;
namespace LiteCharms.Features.MidrandBooks.Authors; namespace LiteCharms.Features.MidrandBooks.Authors;
public class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken) public async ValueTask<Result<AuthorBook[]>> GetAuthorBooksAsync(long authorId, CancellationToken cancellationToken)
{ {
@@ -3,7 +3,7 @@
namespace LiteCharms.Features.MidrandBooks.Authors.Entities; namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
[EntityTypeConfiguration<AuthorConfiguration, Author>] [EntityTypeConfiguration<AuthorConfiguration, Author>]
public class Author : Models.Author public sealed class Author : Models.Author
{ {
public ICollection<AuthorBook> Books { get; set; } = []; public ICollection<AuthorBook> Books { get; set; } = [];
} }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Authors.Entities; namespace LiteCharms.Features.MidrandBooks.Authors.Entities;
public class AuthorConfiguration : IEntityTypeConfiguration<Author> public sealed class AuthorConfiguration : IEntityTypeConfiguration<Author>
{ {
public void Configure(EntityTypeBuilder<Author> builder) public void Configure(EntityTypeBuilder<Author> builder)
{ {
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.MidrandBooks.Authors.Models; namespace LiteCharms.Features.MidrandBooks.Authors.Models;
public record UpdateAuthor : CreateAuthor; public sealed record UpdateAuthor : CreateAuthor;
public record CreateAuthor public record CreateAuthor
{ {
@@ -5,7 +5,7 @@ using LiteCharms.Features.MidrandBooks.Postgres;
namespace LiteCharms.Features.MidrandBooks.Customers; namespace LiteCharms.Features.MidrandBooks.Customers;
public class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default) public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Customers.Entities; namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
public class AddressConfiguration : IEntityTypeConfiguration<Address> public sealed class AddressConfiguration : IEntityTypeConfiguration<Address>
{ {
public void Configure(EntityTypeBuilder<Address> builder) public void Configure(EntityTypeBuilder<Address> builder)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Customers.Entities; namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
public class ContactConfiguration : IEntityTypeConfiguration<Contact> public sealed class ContactConfiguration : IEntityTypeConfiguration<Contact>
{ {
public void Configure(EntityTypeBuilder<Contact> builder) public void Configure(EntityTypeBuilder<Contact> builder)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Customers.Entities; namespace LiteCharms.Features.MidrandBooks.Customers.Entities;
public class CustomerConfiguration : IEntityTypeConfiguration<Customer> public sealed class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{ {
public void Configure(EntityTypeBuilder<Customer> builder) public void Configure(EntityTypeBuilder<Customer> builder)
{ {
@@ -17,7 +17,7 @@ public record CreateCustomer
public SocialMedia[]? SocialMedia { get; set; } public SocialMedia[]? SocialMedia { get; set; }
} }
public record UpdateCustomer : CreateCustomer; public sealed record UpdateCustomer : CreateCustomer;
public record CreateCustomerContact public record CreateCustomerContact
{ {
@@ -32,7 +32,7 @@ public record CreateCustomerContact
public string? Phone { get; set; } public string? Phone { get; set; }
} }
public record UpdateCustomerContact : CreateCustomerContact; public sealed record UpdateCustomerContact : CreateCustomerContact;
public record CreateCustomerAddress public record CreateCustomerAddress
{ {
@@ -57,4 +57,4 @@ public record CreateCustomerAddress
public bool Enabled { get; set; } public bool Enabled { get; set; }
} }
public record UpdateCustomerAddress : CreateCustomerAddress; public sealed record UpdateCustomerAddress : CreateCustomerAddress;
@@ -1,6 +1,7 @@
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models; using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
using LiteCharms.Features.MidrandBooks.Authors.Models; using LiteCharms.Features.MidrandBooks.Authors.Models;
using LiteCharms.Features.MidrandBooks.Customers.Models; using LiteCharms.Features.MidrandBooks.Customers.Models;
using LiteCharms.Features.MidrandBooks.Orders.Models;
using LiteCharms.Features.MidrandBooks.Pages.Models; using LiteCharms.Features.MidrandBooks.Pages.Models;
using LiteCharms.Features.MidrandBooks.Products.Models; using LiteCharms.Features.MidrandBooks.Products.Models;
@@ -8,6 +9,50 @@ namespace LiteCharms.Features.MidrandBooks.Extensions;
public static class Mappers 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() public static Customer ToModel(this Customers.Entities.Customer entiry) => new()
{ {
Id = entiry.Id, Id = entiry.Id,
@@ -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;
}
}
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.MidrandBooks.HealthChecks; namespace LiteCharms.Features.MidrandBooks.HealthChecks;
public class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck public sealed class MidrandShopQuartzHealthCheck(ISchedulerFactory schedulerFactory) : IHealthCheck
{ {
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{ {
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.MidrandBooks.HealthChecks; namespace LiteCharms.Features.MidrandBooks.HealthChecks;
public class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck public sealed class PostgresMidrandShopHealthCheck(IConfiguration configuration) : IHealthCheck
{ {
private readonly string connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName)!; private readonly string connectionString = configuration.GetConnectionString(MidrandBooksDbConfigName)!;
@@ -31,6 +31,10 @@
<!-- Quartz Scheduler--> <!-- Quartz Scheduler-->
<ItemGroup> <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="OpenTelemetry" Version="1.15.3" />
<PackageReference Include="Quartz" Version="3.18.1" /> <PackageReference Include="Quartz" Version="3.18.1" />
<PackageReference Include="Quartz.Plugins" Version="3.18.1" /> <PackageReference Include="Quartz.Plugins" Version="3.18.1" />
@@ -143,6 +147,8 @@
<!-- Shared Usings --> <!-- Shared Usings -->
<ItemGroup> <ItemGroup>
<Using Include="System.Globalization" />
<Using Include="System.Reflection" />
<Using Include="Microsoft.AspNetCore.Builder" /> <Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.Extensions.Hosting" /> <Using Include="Microsoft.Extensions.Hosting" />
<Using Include="System.Text" /> <Using Include="System.Text" />
@@ -157,10 +163,4 @@
<Using Include="Microsoft.Extensions.Logging" /> <Using Include="Microsoft.Extensions.Logging" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; using LiteCharms.Features.MidrandBooks.Payments.Entities;
namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
[EntityTypeConfiguration<OrderConfiguration, Order>] [EntityTypeConfiguration<OrderConfiguration, Order>]
public class Order : Models.Order public class Order : Models.Order
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
public class OrderConfiguration : IEntityTypeConfiguration<Order> public sealed class OrderConfiguration : IEntityTypeConfiguration<Order>
{ {
public void Configure(EntityTypeBuilder<Order> builder) public void Configure(EntityTypeBuilder<Order> builder)
{ {
@@ -1,13 +1,13 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
public class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem> public sealed class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
{ {
public void Configure(EntityTypeBuilder<OrderItem> builder) public void Configure(EntityTypeBuilder<OrderItem> builder)
{ {
builder.ToTable("OrderItems"); builder.ToTable("OrderItems");
builder.HasKey(oi => oi.Id); builder.HasKey(oi => oi.Id);
builder.Property(oi => oi.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("new()"); builder.Property(oi => oi.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
builder.Property(oi => oi.OrderId).IsRequired(); builder.Property(oi => oi.OrderId).IsRequired();
builder.Property(oi => oi.AuthorBookId).IsRequired(); builder.Property(oi => oi.AuthorBookId).IsRequired();
builder.Property(oi => oi.ProductPriceId).IsRequired(); builder.Property(oi => oi.ProductPriceId).IsRequired();
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
public class ShippingConfiguration : IEntityTypeConfiguration<Shipping> public sealed class ShippingConfiguration : IEntityTypeConfiguration<Shipping>
{ {
public void Configure(EntityTypeBuilder<Shipping> builder) public void Configure(EntityTypeBuilder<Shipping> builder)
{ {
@@ -13,6 +13,7 @@ public class ShippingConfiguration : IEntityTypeConfiguration<Shipping>
builder.Property(s => s.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()"); builder.Property(s => s.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
builder.Property(s => s.UpdatedAt).HasDefaultValueSql("now()"); builder.Property(s => s.UpdatedAt).HasDefaultValueSql("now()");
builder.Property(s => s.Status).IsRequired(); builder.Property(s => s.Status).IsRequired();
builder.Property(s => s.TrackingNumber).HasMaxLength(255);
builder.HasOne(s => s.Order) builder.HasOne(s => s.Order)
.WithOne(o => o.Shipping) .WithOne(o => o.Shipping)
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; namespace LiteCharms.Features.MidrandBooks.Orders.Entities;
public class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingProvider> public sealed class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingProvider>
{ {
public void Configure(EntityTypeBuilder<ShippingProvider> builder) public void Configure(EntityTypeBuilder<ShippingProvider> builder)
{ {
@@ -13,5 +13,6 @@ public class ShippingProviderConfiguration : IEntityTypeConfiguration<ShippingPr
builder.Property(f => f.Name).IsRequired().HasMaxLength(100); builder.Property(f => f.Name).IsRequired().HasMaxLength(100);
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2); builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
builder.Property(f => f.Enabled).HasDefaultValue(true); builder.Property(f => f.Enabled).HasDefaultValue(true);
builder.Property(f => f.TrackingUrl).HasMaxLength(200);
} }
} }
@@ -1,5 +1,11 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Models; namespace LiteCharms.Features.MidrandBooks.Orders.Models;
public record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes); public sealed record CreateOrder(long CustomerId, decimal TotalPrice, string? Notes);
public record CreateOrderItem(long AuthorBookId, long ProductPriceId, int Quantity); 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);
@@ -14,5 +14,7 @@ public class Shipping
public long ShippingProviderId { get; set; } public long ShippingProviderId { get; set; }
public string? TrackingNumber { get; set; }
public ShippingStatuses Status { get; set; } public ShippingStatuses Status { get; set; }
} }
@@ -14,5 +14,7 @@ public class ShippingProvider
public decimal? Price { get; set; } public decimal? Price { get; set; }
public string? TrackingUrl { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
} }
@@ -1,10 +1,12 @@
using LiteCharms.Features.MidrandBooks.Abstractions; using LiteCharms.Features.MidrandBooks.Abstractions;
using LiteCharms.Features.MidrandBooks.Extensions;
using LiteCharms.Features.MidrandBooks.Orders.Models; using LiteCharms.Features.MidrandBooks.Orders.Models;
using LiteCharms.Features.MidrandBooks.Postgres; using LiteCharms.Features.MidrandBooks.Postgres;
using LiteCharms.Features.Models;
namespace LiteCharms.Features.MidrandBooks.Orders; namespace LiteCharms.Features.MidrandBooks.Orders;
public class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result<long>> CreateOrderAsync(long customerId, CreateOrder request, CancellationToken cancellationToken = default) public async ValueTask<Result<long>> CreateOrderAsync(long customerId, CreateOrder request, CancellationToken cancellationToken = default)
{ {
@@ -106,4 +108,356 @@ public class OrderService(IDbContextFactory<MidrandBooksDbContext> contextFactor
return Result.Fail(new Error(ex.Message).CausedBy(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));
}
}
} }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Pages.Entities; namespace LiteCharms.Features.MidrandBooks.Pages.Entities;
public class BookPageConfiguration : IEntityTypeConfiguration<BookPage> public sealed class BookPageConfiguration : IEntityTypeConfiguration<BookPage>
{ {
public void Configure(EntityTypeBuilder<BookPage> builder) public void Configure(EntityTypeBuilder<BookPage> builder)
{ {
@@ -1,3 +1,3 @@
namespace LiteCharms.Features.MidrandBooks.Pages.Models; namespace LiteCharms.Features.MidrandBooks.Pages.Models;
public class UpdateBookPage : CreateBookPage; public sealed class UpdateBookPage : CreateBookPage;
@@ -5,7 +5,7 @@ using LiteCharms.Features.MidrandBooks.Postgres;
namespace LiteCharms.Features.MidrandBooks.Pages; namespace LiteCharms.Features.MidrandBooks.Pages;
public class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class PageService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result> DeleteAllAsync(long authorBookId, CancellationToken cancellationToken = default) public async ValueTask<Result> DeleteAllAsync(long authorBookId, CancellationToken cancellationToken = default)
{ {
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; using LiteCharms.Features.MidrandBooks.Orders.Entities;
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
[EntityTypeConfiguration<RefundConfiguration, Refund>] [EntityTypeConfiguration<RefundConfiguration, Refund>]
public class Refund : Models.Refund public class Refund : Models.Refund
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Entities; namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
public class RefundConfiguration : IEntityTypeConfiguration<Refund> public sealed class RefundConfiguration : IEntityTypeConfiguration<Refund>
{ {
public void Configure(EntityTypeBuilder<Refund> builder) public void Configure(EntityTypeBuilder<Refund> builder)
{ {
@@ -1,14 +1,14 @@
namespace LiteCharms.Features.MidrandBooks.Orders.Models; namespace LiteCharms.Features.MidrandBooks.Payments.Models;
public class Refund public class Refund
{ {
public Guid Id { get; set; } public long Id { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; } public DateTime? UpdatedAt { get; set; }
public Guid OrderId { get; set; } public long OrderId { get; set; }
public RefundTypes Type { get; set; } public RefundTypes Type { get; set; }
@@ -0,0 +1,7 @@
using LiteCharms.Features.MidrandBooks.Abstractions;
namespace LiteCharms.Features.MidrandBooks.Payments;
public sealed class PaymentService : IService
{
}
@@ -3,11 +3,12 @@ using LiteCharms.Features.MidrandBooks.Authors.Entities;
using LiteCharms.Features.MidrandBooks.Customers.Entities; using LiteCharms.Features.MidrandBooks.Customers.Entities;
using LiteCharms.Features.MidrandBooks.Orders.Entities; using LiteCharms.Features.MidrandBooks.Orders.Entities;
using LiteCharms.Features.MidrandBooks.Pages.Entities; using LiteCharms.Features.MidrandBooks.Pages.Entities;
using LiteCharms.Features.MidrandBooks.Payments.Entities;
using LiteCharms.Features.MidrandBooks.Products.Entities; using LiteCharms.Features.MidrandBooks.Products.Entities;
namespace LiteCharms.Features.MidrandBooks.Postgres; namespace LiteCharms.Features.MidrandBooks.Postgres;
public class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> options) : DbContext(options) public sealed class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext> options) : DbContext(options)
{ {
public DbSet<Author> Authors => Set<Author>(); public DbSet<Author> Authors => Set<Author>();
@@ -2,14 +2,13 @@
namespace LiteCharms.Features.MidrandBooks.Postgres; namespace LiteCharms.Features.MidrandBooks.Postgres;
public class MidrandBooksDbContextFactory : IDesignTimeDbContextFactory<MidrandBooksDbContext> public sealed class MidrandBooksDbContextFactory : IDesignTimeDbContextFactory<MidrandBooksDbContext>
{ {
public MidrandBooksDbContext CreateDbContext(string[] args) public MidrandBooksDbContext CreateDbContext(string[] args)
{ {
var configuration = new ConfigurationBuilder() var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) .SetBasePath(Directory.GetCurrentDirectory())
.AddUserSecrets(typeof(MidrandBooksDbContext).Assembly) .AddUserSecrets(typeof(MidrandBooksDbContext).Assembly)
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables() .AddEnvironmentVariables()
.Build(); .Build();
@@ -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");
}
}
}
@@ -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
}
}
}
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Products.Entities; namespace LiteCharms.Features.MidrandBooks.Products.Entities;
public class ProductConfiguration : IEntityTypeConfiguration<Product> public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
{ {
public void Configure(EntityTypeBuilder<Product> builder) public void Configure(EntityTypeBuilder<Product> builder)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Products.Entities; namespace LiteCharms.Features.MidrandBooks.Products.Entities;
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice> public sealed class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
{ {
public void Configure(EntityTypeBuilder<ProductPrice> builder) public void Configure(EntityTypeBuilder<ProductPrice> builder)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.MidrandBooks.Products.Models; namespace LiteCharms.Features.MidrandBooks.Products.Models;
public class CreateProductPrice public sealed class CreateProductPrice
{ {
public long ProductId { get; set; } public long ProductId { get; set; }
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.MidrandBooks.Products.Models; namespace LiteCharms.Features.MidrandBooks.Products.Models;
public record CreateProduct public sealed record CreateProduct
{ {
public required ProductTypes Type { get; set; } public required ProductTypes Type { get; set; }
@@ -6,7 +6,7 @@ using LiteCharms.Features.Models;
namespace LiteCharms.Features.MidrandBooks.Products; namespace LiteCharms.Features.MidrandBooks.Products;
public class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
{ {
public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default) public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
{ {
@@ -1,22 +0,0 @@
{
"Email": {
"Credentials": {
"Username": "shop@litecharms.co.za"
},
"Port": 465,
"Host": "mail.litecharms.co.za",
"UseSsl": true
},
"Monitoring": {
"ApiKey": "",
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
"ServiceName": "MidrandBooks"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -1,5 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LiteCharms.Features.Models;
using LiteCharms.Features.TechShop.Postgres; using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models; using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -1,4 +1,5 @@
using System; using System;
using LiteCharms.Features.Models;
using LiteCharms.Features.TechShop.Products.Models; using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
@@ -1,5 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using LiteCharms.Features.Models;
using LiteCharms.Features.TechShop.Postgres; using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models; using LiteCharms.Features.TechShop.Products.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -1,4 +1,6 @@
namespace LiteCharms.Features.TechShop.Products.Models; using LiteCharms.Features.Models;
namespace LiteCharms.Features.TechShop.Products.Models;
public record CreateProduct public record CreateProduct
{ {
@@ -1,4 +1,5 @@
using LiteCharms.Features.TechShop.Extensions; using LiteCharms.Features.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres; using LiteCharms.Features.TechShop.Postgres;
using LiteCharms.Features.TechShop.Products.Models; using LiteCharms.Features.TechShop.Products.Models;
@@ -7,7 +7,7 @@ public abstract class EventBase
{ {
public Guid Id { get; set; } = Guid.CreateVersion7(); public Guid Id { get; set; } = Guid.CreateVersion7();
public DateTimeOffset EnqueueAt { get; set; } = SouthAfricanTimeZone.UtcNow(); public DateTimeOffset EnqueueAt { get; set; } = (DateTimeOffset)SouthAfricanTimeZone.UtcNow();
public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString(); public string CorrelationId { get; set; } = Guid.CreateVersion7().ToString();
} }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Configuration; namespace LiteCharms.Features.Email.Configuration;
public class Account public sealed class Account
{ {
public string? Username { get; set; } public string? Username { get; set; }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Configuration; namespace LiteCharms.Features.Email.Configuration;
public class SmtpSettings public sealed class SmtpSettings
{ {
public Account? Credentials { get; set; } public Account? Credentials { get; set; }
+47 -30
View File
@@ -4,7 +4,7 @@ using LiteCharms.Features.Email.Models;
namespace LiteCharms.Features.Email; namespace LiteCharms.Features.Email;
public class EmailService(IOptions<SmtpSettings> options) : IDisposable public sealed class EmailService(IOptions<SmtpSettings> options) : IDisposable
{ {
private readonly SmtpSettings settings = options.Value; private readonly SmtpSettings settings = options.Value;
private readonly SmtpClient client = new(); private readonly SmtpClient client = new();
@@ -16,6 +16,7 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
public async ValueTask<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default) public async ValueTask<Result<Response>> SendEmailAsync(Message message, CancellationToken cancellationToken = default)
{ {
using var activity = EmailTelemetry.Source.StartActivity("Email Send"); using var activity = EmailTelemetry.Source.StartActivity("Email Send");
activity?.SetTag("email.recipient", message.Recipient?.Address); activity?.SetTag("email.recipient", message.Recipient?.Address);
try try
@@ -27,21 +28,7 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
return Result.Fail<Response>("Smtp service is disconnected."); return Result.Fail<Response>("Smtp service is disconnected.");
} }
var email = new MimeMessage(); var email = ConstructEmail(message, cancellationToken);
email.From.Add(new MailboxAddress(message.Sender!.Name, message.Sender.Address!));
email.To.Add(new MailboxAddress(message.Recipient!.Name, message.Recipient!.Address!));
email.Subject = message.Subject!;
var bodyBuilder = new BodyBuilder();
if (message.Body!.Properties.HasAttachments)
foreach (var attachment in message.Body?.Attachments!)
bodyBuilder.Attachments.Add(attachment.Name!, attachment.FileStream!, cancellationToken);
if (!message.Body.Properties.IsHtml) bodyBuilder.TextBody = message.Body.Message;
if (message.Body.Properties.IsHtml) bodyBuilder.HtmlBody = message.Body.Message;
email.Body = bodyBuilder.ToMessageBody();
var response = await client.SendAsync(email, cancellationToken); var response = await client.SendAsync(email, cancellationToken);
@@ -69,21 +56,9 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
await DisconnectAsync(cancellationToken); await DisconnectAsync(cancellationToken);
if (response.Contains("421")) var failCheckResult = HandleNegativeResponse(response);
{
Status = EmailStatuses.TooManyConnections;
return Result.Fail<Response>(response); if (failCheckResult.IsFailed) return failCheckResult;
}
if (response.Contains("451"))
{
Status = EmailStatuses.ConnectionAborted;
return Result.Fail<Response>(response);
}
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "error_message", response } });
Status = EmailStatuses.Disconnected; Status = EmailStatuses.Disconnected;
@@ -100,6 +75,48 @@ public class EmailService(IOptions<SmtpSettings> options) : IDisposable
} }
} }
private static MimeMessage ConstructEmail(Message message, CancellationToken cancellationToken)
{
var email = new MimeMessage();
email.From.Add(new MailboxAddress(message.Sender!.Name, message.Sender.Address!));
email.To.Add(new MailboxAddress(message.Recipient!.Name, message.Recipient!.Address!));
email.Subject = message.Subject!;
var bodyBuilder = new BodyBuilder();
if (message.Body!.Properties.HasAttachments)
foreach (var attachment in message.Body?.Attachments!)
bodyBuilder.Attachments.Add(attachment.Name!, attachment.FileStream!, cancellationToken);
if (!message.Body.Properties.IsHtml) bodyBuilder.TextBody = message.Body.Message;
if (message.Body.Properties.IsHtml) bodyBuilder.HtmlBody = message.Body.Message;
email.Body = bodyBuilder.ToMessageBody();
return email;
}
private Result<Response> HandleNegativeResponse(string response)
{
if (response.Contains("421", StringComparison.Ordinal))
{
Status = EmailStatuses.TooManyConnections;
return Result.Fail<Response>(response);
}
if (response.Contains("451", StringComparison.Ordinal))
{
Status = EmailStatuses.ConnectionAborted;
return Result.Fail<Response>(response);
}
EmailTelemetry.EmailsFailed.Add(1, new TagList { { "error_message", response } });
return Result.Fail<Response>(response);
}
public async ValueTask<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default) public async ValueTask<Result<Response>> ConnectAsync(CancellationToken cancellationToken = default)
{ {
using var activity = EmailTelemetry.Source.StartActivity("Email Connect"); using var activity = EmailTelemetry.Source.StartActivity("Email Connect");
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class Attachment public sealed class Attachment
{ {
public string? Name { get; set; } public string? Name { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class Body : IDisposable public sealed class Body : IDisposable
{ {
public string? Message { get; set; } public string? Message { get; set; }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class BodyProperties public sealed class BodyProperties
{ {
public bool IsHtml { get; set; } public bool IsHtml { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class Message : IDisposable public sealed class Message : IDisposable
{ {
public Party? Sender { get; set; } public Party? Sender { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class Party public sealed class Party
{ {
public string? Name { get; set; } public string? Name { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Email.Models; namespace LiteCharms.Features.Email.Models;
public class Response public sealed class Response
{ {
public int Code { get; set; } public int Code { get; set; }
+3 -3
View File
@@ -2,12 +2,12 @@
public static class Hash public static class Hash
{ {
public static Func<string?, string?> StringToSha256Hash = (input) => public static readonly Func<string?, string?> StringToSha256Hash = (input) =>
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!))); Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
public static Func<Stream, string?> StreamToSha256Hash = (stream) => public static readonly Func<Stream, string?> StreamToSha256Hash = (stream) =>
Convert.ToHexString(SHA256.HashData(stream)); Convert.ToHexString(SHA256.HashData(stream));
public static Func<byte[], string?> BytesToSha256Hash = (bytes) => public static readonly Func<byte[], string?> BytesToSha256Hash = (bytes) =>
Convert.ToHexString(SHA256.HashData(bytes)); Convert.ToHexString(SHA256.HashData(bytes));
} }
+1 -1
View File
@@ -64,7 +64,7 @@ public static class Quartz
config.UseDefaultThreadPool(options => options.MaxConcurrency = 1); config.UseDefaultThreadPool(options => options.MaxConcurrency = 1);
config.UseTimeZoneConverter(); config.UseTimeZoneConverter();
config.SetProperty("quartz.jobStore.misfireThreshold", TimeSpan.FromMinutes(2).TotalMilliseconds.ToString()); config.SetProperty("quartz.jobStore.misfireThreshold", TimeSpan.FromMinutes(2).TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
config.UsePersistentStore(storage => config.UsePersistentStore(storage =>
{ {
+1 -1
View File
@@ -20,7 +20,7 @@ public static class Timezones
? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset) ? new DateTimeOffset(sourceDateAdjusted.Ticks, SouthAfricanTimeZone.BaseUtcOffset).LocaliseDateTimeOffset(SouthAfricanTimeZone.BaseUtcOffset)
: new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset); : new DateTimeOffset(sourceDateAdjusted.Ticks, timezone!.BaseUtcOffset).LocaliseDateTimeOffset(timezone.BaseUtcOffset);
return DateTimeOffset.Parse(localised!); return DateTimeOffset.Parse(localised!, CultureInfo.InvariantCulture);
} }
public static DateTime UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone).UtcDateTime; public static DateTime UtcNow(this TimeZoneInfo timezone) => ToDateTimeWithTimeZone(DateTime.Now, timezone).UtcDateTime;
@@ -31,6 +31,10 @@
<!-- Quartz Scheduler--> <!-- Quartz Scheduler-->
<ItemGroup> <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="OpenTelemetry" Version="1.15.3" />
<PackageReference Include="Quartz" Version="3.18.1" /> <PackageReference Include="Quartz" Version="3.18.1" />
<PackageReference Include="Quartz.Plugins" Version="3.18.1" /> <PackageReference Include="Quartz.Plugins" Version="3.18.1" />
@@ -142,6 +146,7 @@
<!-- Shared Usings --> <!-- Shared Usings -->
<ItemGroup> <ItemGroup>
<Using Include="System.Globalization" />
<Using Include="Microsoft.AspNetCore.Builder" /> <Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.Extensions.Hosting" /> <Using Include="Microsoft.Extensions.Hosting" />
<Using Include="System.Text" /> <Using Include="System.Text" />
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Models; namespace LiteCharms.Features.Models;
public class DateRange public sealed class DateRange
{ {
public DateOnly From { get; set; } public DateOnly From { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Models; namespace LiteCharms.Features.Models;
public class PageReference public sealed class PageReference
{ {
public string? Tag { get; set; } public string? Tag { get; set; }
+1 -1
View File
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Models; namespace LiteCharms.Features.Models;
public class ProductFilter public sealed class ProductFilter
{ {
public string? Name { get; set; } public string? Name { get; set; }
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Models; namespace LiteCharms.Features.Models;
public class ProductMetadata public sealed class ProductMetadata
{ {
public string? Manufacturer { get; set; } public string? Manufacturer { get; set; }
+1 -1
View File
@@ -1,7 +1,7 @@
namespace LiteCharms.Features.Models; namespace LiteCharms.Features.Models;
public class SocialMedia public sealed class SocialMedia
{ {
public SocialMediaTypes Type { get; set; } public SocialMediaTypes Type { get; set; }
@@ -3,7 +3,7 @@ using LiteCharms.Features.Quartz.Abstractions;
namespace LiteCharms.Features.Quartz; namespace LiteCharms.Features.Quartz;
public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestrator public sealed class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestrator
{ {
public async Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default) public async Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
where TNotification : IEvent where TNotification : IEvent
@@ -11,7 +11,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
var chainedJobGroup = "onetime-jobs"; var chainedJobGroup = "onetime-jobs";
var scheduler = await schedulerFactory.GetScheduler(cancellationToken); var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
var jobKey = new JobKey($"{notification.Name.ToLower()}-{notification.CorrelationId.ToLower()}", chainedJobGroup); var jobKey = new JobKey($"{notification.Name.ToLower(CultureInfo.InvariantCulture)}-{notification.CorrelationId.ToLower(CultureInfo.InvariantCulture)}", chainedJobGroup);
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup); var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
var job = JobBuilder.Create<MediatorJob<TNotification>>() var job = JobBuilder.Create<MediatorJob<TNotification>>()
@@ -35,7 +35,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
var chainedJobGroup = "scheduled-jobs"; var chainedJobGroup = "scheduled-jobs";
var scheduler = await schedulerFactory.GetScheduler(cancellationToken); var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
var jobKey = new JobKey($"{notification.Name.ToLower()}", chainedJobGroup); var jobKey = new JobKey($"{notification.Name.ToLower(CultureInfo.InvariantCulture)}", chainedJobGroup);
var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup); var triggerKey = new TriggerKey($"{jobKey.Name}-trigger", chainedJobGroup);
var job = JobBuilder.Create<MediatorJob<TNotification>>() var job = JobBuilder.Create<MediatorJob<TNotification>>()
@@ -53,7 +53,7 @@ public class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestra
.WithDescription($"Scheduled via Main Job at {now:g}") .WithDescription($"Scheduled via Main Job at {now:g}")
.WithCronSchedule(cronExpression, cron => cron .WithCronSchedule(cronExpression, cron => cron
.WithMisfireHandlingInstructionIgnoreMisfires()) .WithMisfireHandlingInstructionIgnoreMisfires())
.StartAt(now) .StartAt((DateTimeOffset)now)
.Build(); .Build();
await scheduler.AddJob(job, replace: true, cancellationToken); await scheduler.AddJob(job, replace: true, cancellationToken);
+1 -1
View File
@@ -4,7 +4,7 @@ using LiteCharms.Features.Mediator;
namespace LiteCharms.Features.Quartz; namespace LiteCharms.Features.Quartz;
[DisallowConcurrentExecution] [DisallowConcurrentExecution]
public class MediatorJob<TNotification>(IMediator mediator) : IJob where TNotification : IEvent public sealed class MediatorJob<TNotification>(IMediator mediator) : IJob where TNotification : IEvent
{ {
public async Task Execute(IJobExecutionContext context) public async Task Execute(IJobExecutionContext context)
{ {
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Quartz; namespace LiteCharms.Features.Quartz;
public class RetryJobListener : IJobListener public sealed class RetryJobListener : IJobListener
{ {
public string Name => "RetryJobListener"; public string Name => "RetryJobListener";
@@ -31,7 +31,7 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
if(string.IsNullOrWhiteSpace(fileHash)) if(string.IsNullOrWhiteSpace(fileHash))
return Result.Fail<string>("Failed to compute file hash."); return Result.Fail<string>("Failed to compute file hash.");
var fileKey = $"{fileHash.ToLower()}{Path.GetExtension(fileName)}"; var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}";
var putRequest = new PutObjectRequest var putRequest = new PutObjectRequest
{ {
@@ -3,7 +3,7 @@ using static LiteCharms.Features.S3.Constants;
namespace LiteCharms.Features.S3; namespace LiteCharms.Features.S3;
public class BookshopInvoicesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopInvoicesBucketName)] IAmazonS3 amazonS3) : public sealed class BookshopInvoicesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopInvoicesBucketName)] IAmazonS3 amazonS3) :
S3ServiceBase(amazonS3), IS3Service S3ServiceBase(amazonS3), IS3Service
{ {
protected override string BucketName => configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:BucketName").Value ?? ""; protected override string BucketName => configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:BucketName").Value ?? "";
@@ -3,7 +3,7 @@ using static LiteCharms.Features.S3.Constants;
namespace LiteCharms.Features.S3; namespace LiteCharms.Features.S3;
public class BookshopQuotesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopQuotesBucketName)] IAmazonS3 amazonS3) : public sealed class BookshopQuotesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopQuotesBucketName)] IAmazonS3 amazonS3) :
S3ServiceBase(amazonS3), IS3Service S3ServiceBase(amazonS3), IS3Service
{ {
protected override string BucketName => configuration.GetSection($"{BookshopQuotesS3SettingsSection}:BucketName").Value ?? ""; protected override string BucketName => configuration.GetSection($"{BookshopQuotesS3SettingsSection}:BucketName").Value ?? "";
+1 -1
View File
@@ -3,7 +3,7 @@ using static LiteCharms.Features.S3.Constants;
namespace LiteCharms.Features.S3; namespace LiteCharms.Features.S3;
public class BookshopS3Service(IConfiguration configuration, [FromKeyedServices(BookshopBucketName)] IAmazonS3 amazonS3) : public sealed class BookshopS3Service(IConfiguration configuration, [FromKeyedServices(BookshopBucketName)] IAmazonS3 amazonS3) :
S3ServiceBase(amazonS3), IS3Service S3ServiceBase(amazonS3), IS3Service
{ {
protected override string BucketName => configuration.GetSection($"{BookshopS3SettingsSection}:BucketName").Value ?? ""; protected override string BucketName => configuration.GetSection($"{BookshopS3SettingsSection}:BucketName").Value ?? "";
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.S3.Configuration; namespace LiteCharms.Features.S3.Configuration;
public class S3Settings public sealed class S3Settings
{ {
public string? ServiceUrl { get; set; } public string? ServiceUrl { get; set; }
@@ -4,7 +4,7 @@ using LiteCharms.Features.ServiceBus.Queues;
namespace LiteCharms.Features.ServiceBus; namespace LiteCharms.Features.ServiceBus;
public class EmailServiceBus(EmailQueue messages) : IEventBus public sealed class EmailServiceBus(EmailQueue messages) : IEventBus
{ {
public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default) public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default)
where TEvent : class, IEvent where TEvent : class, IEvent
@@ -3,7 +3,7 @@ using LiteCharms.Features.ServiceBus.Queues;
namespace LiteCharms.Features.ServiceBus.Exchanges; namespace LiteCharms.Features.ServiceBus.Exchanges;
public class EmailExchange(EmailQueue messages, ILogger<EmailExchange> logger, IPublisher mediator) : BackgroundService public sealed class EmailExchange(EmailQueue messages, ILogger<EmailExchange> logger, IPublisher mediator) : BackgroundService
{ {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.ServiceBus.Exchanges; namespace LiteCharms.Features.ServiceBus.Exchanges;
public class GeneralExchange(GeneralQueue messages) : BackgroundService public sealed class GeneralExchange(GeneralQueue messages) : BackgroundService
{ {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.ServiceBus.Exchanges; namespace LiteCharms.Features.ServiceBus.Exchanges;
public class SalesExchange(SalesQueue messages) : BackgroundService public sealed class SalesExchange(SalesQueue messages) : BackgroundService
{ {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
@@ -4,7 +4,7 @@ using LiteCharms.Features.ServiceBus.Queues;
namespace LiteCharms.Features.ServiceBus; namespace LiteCharms.Features.ServiceBus;
public class GeneralServiceBus(GeneralQueue messages) : IEventBus public sealed class GeneralServiceBus(GeneralQueue messages) : IEventBus
{ {
public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default) public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default)
where TEvent : class, IEvent where TEvent : class, IEvent
@@ -2,4 +2,4 @@
namespace LiteCharms.Features.ServiceBus.Queues; namespace LiteCharms.Features.ServiceBus.Queues;
public class EmailQueue : EventBusQueueBase, IEventBusQueue; public sealed class EmailQueue : EventBusQueueBase, IEventBusQueue;
@@ -2,4 +2,4 @@
namespace LiteCharms.Features.ServiceBus.Queues; namespace LiteCharms.Features.ServiceBus.Queues;
public class GeneralQueue : EventBusQueueBase, IEventBusQueue; public sealed class GeneralQueue : EventBusQueueBase, IEventBusQueue;
@@ -2,4 +2,4 @@
namespace LiteCharms.Features.ServiceBus.Queues; namespace LiteCharms.Features.ServiceBus.Queues;
public class SalesQueue : EventBusQueueBase, IEventBusQueue; public sealed class SalesQueue : EventBusQueueBase, IEventBusQueue;
@@ -4,7 +4,7 @@ using LiteCharms.Features.ServiceBus.Queues;
namespace LiteCharms.Features.ServiceBus; namespace LiteCharms.Features.ServiceBus;
public class SalesServiceBus(SalesQueue messages) : IEventBus public sealed class SalesServiceBus(SalesQueue messages) : IEventBus
{ {
public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default) public async Task<Result> PublishAsync<TEvent>(TEvent notification, CancellationToken cancellationToken = default)
where TEvent : class, IEvent where TEvent : class, IEvent
+1
View File
@@ -1,6 +1,7 @@
<Solution> <Solution>
<Folder Name="/Solution Items/"> <Folder Name="/Solution Items/">
<File Path=".drone.yml" /> <File Path=".drone.yml" />
<File Path=".editorconfig" />
</Folder> </Folder>
<Folder Name="/src/"> <Folder Name="/src/">
<Project Path="LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj" Id="a9c12f0b-5f50-4519-87bd-ec8823b0bc9a" /> <Project Path="LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj" Id="a9c12f0b-5f50-4519-87bd-ec8823b0bc9a" />