Retructured solution
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Entities;
|
||||
|
||||
[EntityTypeConfiguration<ProductConfiguration, Product>]
|
||||
public class Product : Models.Product
|
||||
{
|
||||
public virtual ICollection<ProductPrice>? ProductPrices { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Entities;
|
||||
|
||||
public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Product> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Product));
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.Name).IsRequired();
|
||||
builder.Property(f => f.Summary).IsRequired().HasMaxLength(512);
|
||||
builder.Property(f => f.Description).IsRequired().HasMaxLength(2048);
|
||||
builder.Property(f => f.ImageUrl).IsRequired(false).HasMaxLength(2048);
|
||||
builder.Property(f => f.Thumbnails).HasColumnType("jsonb").IsRequired(false);
|
||||
builder.Property(f => f.Active).HasDefaultValue(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Entities;
|
||||
|
||||
[EntityTypeConfiguration<ProductPriceConfiguration, ProductPrice>]
|
||||
public class ProductPrice : Models.ProductPrice
|
||||
{
|
||||
public virtual Product? Product { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Entities;
|
||||
|
||||
public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProductPrice> builder)
|
||||
{
|
||||
builder.ToTable(nameof(ProductPrice));
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
||||
builder.Property(f => f.ProductId).IsRequired();
|
||||
builder.Property(f => f.Price).IsRequired().HasPrecision(18, 2);
|
||||
builder.Property(f => f.Discount).HasPrecision(18, 2);
|
||||
builder.Property(f => f.Active);
|
||||
|
||||
builder.HasOne(f => f.Product)
|
||||
.WithMany(f => f.ProductPrices)
|
||||
.HasForeignKey(pp => pp.ProductId)
|
||||
.IsRequired()
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
public class Product
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Summary { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
public string[]? Thumbnails { get; set; }
|
||||
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
public class ProductPrice
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public DateTimeOffset? UpdatedAt { get; set; }
|
||||
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public decimal Discount { get; set; }
|
||||
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries;
|
||||
|
||||
public class GetProductPriceQuery : IRequest<Result<ProductPrice>>
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
private GetProductPriceQuery(Guid productId) => ProductId = productId;
|
||||
|
||||
public static GetProductPriceQuery Create(Guid productId)
|
||||
{
|
||||
if (productId == Guid.Empty)
|
||||
throw new ArgumentException("ProductId is required.", nameof(productId));
|
||||
|
||||
return new(productId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries;
|
||||
|
||||
public class GetProductPricesQuery : IRequest<Result<ProductPrice[]>>
|
||||
{
|
||||
public int MaxRecords { get; set; }
|
||||
|
||||
private GetProductPricesQuery(int maxRecords = 1000) => MaxRecords = maxRecords;
|
||||
|
||||
public static GetProductPricesQuery Create(int maxRecords = 1000)
|
||||
{
|
||||
if (maxRecords <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxRecords), "MaxRecords must be greater than zero.");
|
||||
|
||||
return new(maxRecords);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries;
|
||||
|
||||
public class GetProductQuery : IRequest<Result<Product>>
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
private GetProductQuery(Guid productId) => ProductId = productId;
|
||||
|
||||
public static GetProductQuery Create(Guid productId)
|
||||
{
|
||||
if(productId == Guid.Empty)
|
||||
throw new ArgumentException("Product ID is required.", nameof(productId));
|
||||
|
||||
return new(productId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries;
|
||||
|
||||
public class GetProductsQuery : IRequest<Result<Product[]>>
|
||||
{
|
||||
public int MaxRecords { get; set; }
|
||||
|
||||
private GetProductsQuery(int maxRecords = 1000) => MaxRecords = maxRecords;
|
||||
|
||||
public static GetProductsQuery Create(int maxRecords = 1000)
|
||||
{
|
||||
if (maxRecords <= 0)
|
||||
throw new ArgumentException("MaxRecords must be a positive integer.");
|
||||
|
||||
return new(maxRecords);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using LiteCharms.Extensions;
|
||||
using LiteCharms.Features.Shop.Postgres;
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries.Handlers;
|
||||
|
||||
public class GetProductPriceQueryHandler(IDbContextFactory<ShopDbContext> contextFactory) : IRequestHandler<GetProductPriceQuery, Result<ProductPrice>>
|
||||
{
|
||||
public async ValueTask<Result<ProductPrice>> Handle(GetProductPriceQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
if(!await context.Products.AnyAsync(p => p.Id == request.ProductId, cancellationToken))
|
||||
return Result.Fail<ProductPrice>(new Error($"Product {request.ProductId} not found."));
|
||||
|
||||
var productPrice = await context.ProductPrices.AsNoTracking()
|
||||
.Where(pp => pp.ProductId == request.ProductId && pp.Active)
|
||||
.OrderByDescending(pp => pp.CreatedAt)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return productPrice is not null
|
||||
? Result.Ok(productPrice.ToModel())
|
||||
: Result.Fail<ProductPrice>(new Error($"Product price {request.ProductId} not found."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<ProductPrice>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using LiteCharms.Extensions;
|
||||
using LiteCharms.Features.Shop.Postgres;
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries.Handlers;
|
||||
|
||||
public class GetProductPricesQueryHandler(IDbContextFactory<ShopDbContext> contextFactory) : IRequestHandler<GetProductPricesQuery, Result<ProductPrice[]>>
|
||||
{
|
||||
public async ValueTask<Result<ProductPrice[]>> Handle(GetProductPricesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var products = await context.ProductPrices.AsNoTracking()
|
||||
.OrderByDescending(o => o.Id)
|
||||
.Take(request.MaxRecords)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using LiteCharms.Extensions;
|
||||
using LiteCharms.Features.Shop.Postgres;
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries.Handlers;
|
||||
|
||||
public class GetProductQueryHandler(IDbContextFactory<ShopDbContext> contextFactory) : IRequestHandler<GetProductQuery, Result<Product>>
|
||||
{
|
||||
public async ValueTask<Result<Product>> Handle(GetProductQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
|
||||
|
||||
return product is not null
|
||||
? Result.Ok(product.ToModel())
|
||||
: Result.Fail<Product>(new Error($"Product with ID {request.ProductId} not found."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<Product>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using LiteCharms.Extensions;
|
||||
using LiteCharms.Features.Shop.Postgres;
|
||||
using LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
namespace LiteCharms.Features.Products.Queries.Handlers;
|
||||
|
||||
public class GetProductsQueryHandler(IDbContextFactory<ShopDbContext> contextFactory) : IRequestHandler<GetProductsQuery, Result<Product[]>>
|
||||
{
|
||||
public async ValueTask<Result<Product[]>> Handle(GetProductsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var products = await context.Products.AsNoTracking()
|
||||
.OrderByDescending(o => o.Id)
|
||||
.Take(request.MaxRecords)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
|
||||
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail<Product[]>(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user