Added dates to product model Migrated database changes
This commit is contained in:
@@ -8,10 +8,13 @@ public class ProductConfiguration : IEntityTypeConfiguration<Product>
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.Name).IsRequired();
|
||||
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(f => f.UpdatedAt).IsRequired(false);
|
||||
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);
|
||||
builder.Property(f => f.Active).HasDefaultValue(false);
|
||||
builder.Property(f => f.Metadata).HasColumnType("jsonb").IsRequired(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public class ProductPriceConfiguration : IEntityTypeConfiguration<ProductPrice>
|
||||
builder.ToTable("ProductPrices");
|
||||
|
||||
builder.HasKey(f => f.Id);
|
||||
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()");
|
||||
builder.Property(f => f.CreatedAt).IsRequired().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);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
public class CreateProductModel
|
||||
{
|
||||
[MaxLength(128)]
|
||||
[Required(ErrorMessage = "Product name is required.")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[MaxLength(512)]
|
||||
[Required(ErrorMessage = "Summary is required.")]
|
||||
public string? Summary { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
[Required(ErrorMessage = "Description is required.")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than zero.")]
|
||||
public decimal Price { get; set; }
|
||||
|
||||
[MaxLength(128)]
|
||||
[Required(ErrorMessage = "Author metadata is required.")]
|
||||
public string? Author { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Publication Date is required.")]
|
||||
public DateTime PublishDate { get; set; } = DateTime.Today;
|
||||
|
||||
[MaxLength(255)]
|
||||
[Required(ErrorMessage = "Copyright Information field is required.")]
|
||||
public string? CopyrightInfo { get; set; }
|
||||
|
||||
[MaxLength(128)]
|
||||
[Required(ErrorMessage = "ISBN code is required.")]
|
||||
[RegularExpression(@"^(?=(?:\D*\d){10}(?:(?:\D*\d){3})?$)[\d-]+$", ErrorMessage = "Please enter a valid ISBN-10 or ISBN-13 string.")]
|
||||
public string? Isbn { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Primary image is required.")]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
public List<string> Thumbnails { get; set; } = [];
|
||||
}
|
||||
@@ -4,6 +4,10 @@ public class Product
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Summary { get; set; }
|
||||
@@ -15,4 +19,6 @@ public class Product
|
||||
public string[]? Thumbnails { get; set; }
|
||||
|
||||
public bool Active { get; set; }
|
||||
|
||||
public ProductMetadata? Metadata { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace LiteCharms.Features.Shop.Products.Models;
|
||||
|
||||
public class ProductMetadata
|
||||
{
|
||||
public string? Manufacturer { get; set; }
|
||||
|
||||
public string? ManufactureDate { get; set; }
|
||||
|
||||
public string? CopyrightInfo { get; set; }
|
||||
|
||||
public string? SerialNumber { get; set; }
|
||||
}
|
||||
@@ -11,4 +11,6 @@ public record CreateProduct
|
||||
public required string ImageUrl { get; set; }
|
||||
|
||||
public string[]? Thumbnails { get; set; }
|
||||
|
||||
public ProductMetadata? Metadata { get; set; }
|
||||
}
|
||||
|
||||
@@ -67,10 +67,11 @@ public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||
var newProduct = context.Products.Add(new Entities.Product
|
||||
{
|
||||
Name = request.Name,
|
||||
Summary = request.Summary,
|
||||
Summary = request.Summary,
|
||||
Description = request.Description,
|
||||
ImageUrl = request.ImageUrl,
|
||||
Thumbnails = request.Thumbnails
|
||||
Thumbnails = request.Thumbnails,
|
||||
Metadata = request.Metadata
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
@@ -89,12 +90,12 @@ public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
|
||||
var newProductPrice = context.ProductPrices.Add(new Entities.ProductPrice
|
||||
{
|
||||
Price = price,
|
||||
Discount = discount,
|
||||
ProductId = productId
|
||||
ProductId = productId
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
@@ -206,7 +207,7 @@ public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||
|
||||
var result = await CreateProductPriceAsync(existingPrice.ProductId, price, discount, cancellationToken);
|
||||
|
||||
if(result.IsFailed)
|
||||
if (result.IsFailed)
|
||||
{
|
||||
var deactivatedPrice = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
@@ -225,4 +226,52 @@ public class ProductService(IDbContextFactory<ShopDbContext> contextFactory)
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> SetProductPriceStatusAsync(Guid productPriceId, bool active, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var productPrice = await context.ProductPrices.FirstOrDefaultAsync(p => p.Id == productPriceId, cancellationToken);
|
||||
|
||||
if (productPrice is null)
|
||||
return Result.Fail($"Could not find product price with ID {productPriceId}");
|
||||
|
||||
productPrice.Active = active;
|
||||
productPrice.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to change product price status by ID {productPriceId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<Result> UpdateProductMetadataAsync(Guid productId, ProductMetadata metadata, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var product = await context.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||
|
||||
if (product is null)
|
||||
return Result.Fail($"Could not find product with ID {productId}");
|
||||
|
||||
product.Metadata = metadata;
|
||||
product.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok()
|
||||
: Result.Fail($"Failed to update product metadata by ID {productId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user