diff --git a/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs index 7c8fbea..c1f103c 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs @@ -62,7 +62,7 @@ public class CategoryServiceFeatureTests(Fixture fixture) : IClassFixture 0); diff --git a/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs index 6dc7e88..d4e6e4c 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs @@ -10,7 +10,40 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu private readonly ProductService productService = fixture.Services.GetRequiredService(); [IntegrationFact] - public async Task GetProductPriceAsync_ShouldReturn_RetultOneProductPrice() + public async Task AddProductCategoryAsync_ShouldReturn_ResultWithId() + { + var result = await productService.AddProductCategoryAsync(1, 2, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + } + + [IntegrationFact] + public async Task GetProductCategoriesAsync_ShouldReturn_ResultWithCategoryList() + { + var result = await productService.GetProductCategoriesAsync(1, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotEmpty(result.Value); + } + + [IntegrationFact] + public async Task DeleteProductCategoryAsync_ShouldReturn_ResultWithSuccess() + { + var result = await productService.DeleteProductCategoryAsync(1, 1, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + } + + [IntegrationFact] + public async Task DeleteAllProductCategoriesAsync_ShouldReturn_ResultWithSuccess() + { + var result = await productService.DeleteAllProductCategoriesAsync(1, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + } + + [IntegrationFact] + public async Task GetProductPriceAsync_ShouldReturn_ResultOneProductPrice() { var result = await productService.GetProductPriceAsync(2, fixture.CancellationToken); @@ -21,7 +54,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu } [IntegrationFact] - public async Task GetProductPricesAsync_ShouldReturn_RetultProductPriceList() + public async Task GetProductPricesAsync_ShouldReturn_ResultProductPriceList() { var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken); @@ -32,7 +65,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu } [IntegrationFact] - public async Task SearchProductsAsync_ShouldReturn_RetultMatchingProducts() + public async Task SearchProductsAsync_ShouldReturn_ResultMatchingProducts() { var filter = new ProductFilter { @@ -52,7 +85,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu } [IntegrationFact] - public async Task GetProductAsync_ShouldReturn_RetultOneProduct() + public async Task GetProductAsync_ShouldReturn_ResultOneProduct() { var result = await productService.GetProductAsync(2, fixture.CancellationToken); @@ -63,7 +96,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu } [IntegrationFact] - public async Task GetProductsAsync_ShouldReturn_RetultProducts() + public async Task GetProductsAsync_ShouldReturn_ResultProducts() { var range = new DateRange { @@ -81,7 +114,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu } [IntegrationFact] - public async Task UpdateProductStatusAsync_ShouldReturn_ResultTrue() + public async Task UpdateProductStatusAsync_ShouldResurn_ResultTrue() { var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken); diff --git a/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs b/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs index dd6c557..2a5b9f4 100644 --- a/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs +++ b/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs @@ -55,8 +55,8 @@ public sealed class CategoryService(IDbContextFactory con var query = context.Categories.AsNoTracking() .OrderByDescending(o => o.IsMain) - .ThenByDescending(o => o.Id) - .ThenBy(o => o.IsMain) + .ThenByDescending(o => o.Id) + .ThenBy(o => o.Name) .AsQueryable(); query = isMain is null diff --git a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs index 3c3790d..1a81b74 100644 --- a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs +++ b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs @@ -147,25 +147,21 @@ public static class Mappers Enabled = entity.Enabled }; - public static Product ToModel(this Products.Entities.Product entity) + public static Product ToModel(this Products.Entities.Product entity) => new Product { - return new Product - { - Id = entity.Id, - CreatedAt = entity.CreatedAt, - UpdatedAt = entity.UpdatedAt, - Name = entity.Name, - Summary = entity.Summary, - Description = entity.Description, - Type = entity.Type, - ImageUrl = entity.ImageUrl, - ThumbnailUrls = entity.ThumbnailUrls, - Metadata = entity.Metadata, - Categories = entity.Categories, - Enabled = entity.Enabled, - Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null, - }; - } + Id = entity.Id, + CreatedAt = entity.CreatedAt, + UpdatedAt = entity.UpdatedAt, + Name = entity.Name, + Summary = entity.Summary, + Description = entity.Description, + Type = entity.Type, + ImageUrl = entity.ImageUrl, + ThumbnailUrls = entity.ThumbnailUrls, + Metadata = entity.Metadata, + Enabled = entity.Enabled, + Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null, + }; public static Author ToModel(this Authors.Entities.Author entity) => new() { diff --git a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs index 30af0d7..782bb1f 100644 --- a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs +++ b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs @@ -38,4 +38,6 @@ public sealed class MidrandBooksDbContext(DbContextOptions ShippingProviders => Set(); public DbSet Categories => Set(); + + public DbSet ProductCategories => Set(); } diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.Designer.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.Designer.cs new file mode 100644 index 0000000..af132c2 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.Designer.cs @@ -0,0 +1,1007 @@ +// +using System; +using LiteCharms.Features.MidrandBooks.Postgres; +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("20260530125323_AddedProductCategories")] + partial class AddedProductCategories + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("Ranking") + .HasColumnType("integer"); + + b.Property("Rating") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Biography") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Company") + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("PublisherType") + .HasColumnType("integer"); + + b.Property("ThumbnailImageUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("VatNumber") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Website") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.ToTable("Authors", (string)null); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Categories.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsMain") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BuildingType") + .HasColumnType("integer"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CustomerId") + .HasColumnType("bigint"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsPrimary") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CustomerId") + .HasColumnType("bigint"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsPrimary") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Company") + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("VatNumber") + .HasColumnType("text"); + + b.Property("Website") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers", (string)null); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("CustomerId") + .HasColumnType("bigint"); + + b.Property("InvoiceUrl") + .HasColumnType("text"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Total") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorBookId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductPriceId") + .HasColumnType("bigint"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AddressId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ShippingProviderId") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TrackingNumber") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("TrackingUrl") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("ShippingProviders"); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AuthorBookId") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("ContentType") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.PrimitiveCollection("Notes") + .HasColumnType("text[]"); + + b.Property("Number") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("Reason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("ImageUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.PrimitiveCollection("ThumbnailUrls") + .HasColumnType("text[]"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", (string)null); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("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.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.Authors.Entities.Author", b => + { + b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 => + { + b1.Property("AuthorId"); + + b1.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd(); + + b1.Property("ImageUrl"); + + b1.Property("Name"); + + b1.Property("Type"); + + b1.Property("Url"); + + b1.HasKey("AuthorId", "__synthesizedOrdinal"); + + b1.ToTable("Authors"); + + b1 + .ToJson("SocialMedia") + .HasColumnType("jsonb"); + + b1.WithOwner() + .HasForeignKey("AuthorId"); + }); + + b.Navigation("SocialMedia"); + }); + + 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.Customers.Entities.Customer", b => + { + b.OwnsMany("LiteCharms.Features.Models.SocialMedia", "SocialMedia", b1 => + { + b1.Property("CustomerId"); + + b1.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd(); + + b1.Property("ImageUrl"); + + b1.Property("Name"); + + b1.Property("Type"); + + b1.Property("Url"); + + b1.HasKey("CustomerId", "__synthesizedOrdinal"); + + b1.ToTable("Customers"); + + b1 + .ToJson("SocialMedia") + .HasColumnType("jsonb"); + + b1.WithOwner() + .HasForeignKey("CustomerId"); + }); + + b.Navigation("SocialMedia"); + }); + + 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.OwnsMany("LiteCharms.Features.Models.PageReference", "References", b1 => + { + b1.Property("BookPageId"); + + b1.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd(); + + b1.Property("Description"); + + b1.Property("Tag"); + + b1.Property("Url"); + + b1.HasKey("BookPageId", "__synthesizedOrdinal"); + + b1.ToTable("BookPages"); + + b1 + .ToJson("References") + .HasColumnType("jsonb"); + + b1.WithOwner() + .HasForeignKey("BookPageId"); + }); + + b.Navigation("Book"); + + b.Navigation("References"); + }); + + 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.Product", b => + { + b.OwnsOne("LiteCharms.Features.Models.ProductMetadata", "Metadata", b1 => + { + b1.Property("ProductId"); + + b1.Property("CopyrightInfo"); + + b1.Property("ManufactureDate"); + + b1.Property("Manufacturer"); + + b1.Property("SerialNumber"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1 + .ToJson("Metadata") + .HasColumnType("jsonb"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("Metadata"); + }); + + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductCategory", b => + { + b.HasOne("LiteCharms.Features.MidrandBooks.Categories.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product") + .WithMany("Categories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + 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.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("Categories"); + + b.Navigation("Prices"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.cs new file mode 100644 index 0000000..4f35ac7 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530125323_AddedProductCategories.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations +{ + /// + public partial class AddedProductCategories : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Categories", + table: "Products"); + + migrationBuilder.CreateTable( + name: "ProductCategories", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ProductId = table.Column(type: "bigint", nullable: false), + CategoryId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProductCategories", x => x.Id); + table.ForeignKey( + name: "FK_ProductCategories_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProductCategories_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProductCategories_CategoryId", + table: "ProductCategories", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_ProductCategories_ProductId", + table: "ProductCategories", + column: "ProductId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ProductCategories"); + + migrationBuilder.AddColumn( + name: "Categories", + table: "Products", + type: "text[]", + nullable: true); + } + } +} diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs index ec18d1c..8af6b80 100644 --- a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs @@ -586,9 +586,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.PrimitiveCollection("Categories") - .HasColumnType("text[]"); - b.Property("CreatedAt") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -633,6 +630,29 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations b.ToTable("Products", (string)null); }); + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", (string)null); + }); + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b => { b.Property("Id") @@ -911,6 +931,25 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations b.Navigation("Metadata"); }); + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductCategory", b => + { + b.HasOne("LiteCharms.Features.MidrandBooks.Categories.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product") + .WithMany("Categories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b => { b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product") @@ -955,6 +994,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b => { + b.Navigation("Categories"); + b.Navigation("Prices"); }); #pragma warning restore 612, 618 diff --git a/LiteCharms.Features.MidrandBooks/Products/Entities/Product.cs b/LiteCharms.Features.MidrandBooks/Products/Entities/Product.cs index e7e82c6..265f787 100644 --- a/LiteCharms.Features.MidrandBooks/Products/Entities/Product.cs +++ b/LiteCharms.Features.MidrandBooks/Products/Entities/Product.cs @@ -3,5 +3,7 @@ [EntityTypeConfiguration] public class Product : Models.Product { + public virtual ICollection Categories { get; set; } = []; + public virtual ICollection Prices { get; set; } = []; } diff --git a/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategory.cs b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategory.cs new file mode 100644 index 0000000..6b84876 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategory.cs @@ -0,0 +1,11 @@ +using LiteCharms.Features.MidrandBooks.Categories.Entities; + +namespace LiteCharms.Features.MidrandBooks.Products.Entities; + +[EntityTypeConfiguration] +public class ProductCategory : Models.ProductCategory +{ + public virtual Product? Product { get; set; } + + public virtual Category? Category { get; set; } +} diff --git a/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategoryConfiguration.cs b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategoryConfiguration.cs new file mode 100644 index 0000000..768926d --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductCategoryConfiguration.cs @@ -0,0 +1,23 @@ +namespace LiteCharms.Features.MidrandBooks.Products.Entities; + +public sealed class ProductCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("ProductCategories"); + + builder.HasKey(p => p.Id); + builder.Property(p => p.ProductId).IsRequired(); + builder.Property(p => p.CategoryId).IsRequired(); + + builder.HasOne(p => p.Product) + .WithMany(p => p.Categories) + .HasForeignKey(p => p.ProductId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(c => c.Category) + .WithMany() + .HasForeignKey(c => c.CategoryId) + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/LiteCharms.Features.MidrandBooks/Products/Entities/ProductConfiguration.cs b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductConfiguration.cs index 795097d..daf7f19 100644 --- a/LiteCharms.Features.MidrandBooks/Products/Entities/ProductConfiguration.cs +++ b/LiteCharms.Features.MidrandBooks/Products/Entities/ProductConfiguration.cs @@ -17,7 +17,6 @@ public sealed class ProductConfiguration : IEntityTypeConfiguration builder.Property(f => f.Description).HasMaxLength(1024); builder.Property(f => f.ImageUrl).HasMaxLength(1024); builder.Property(f => f.Enabled).HasDefaultValue(false); - builder.Property(f => f.Categories).IsRequired(false); builder.Property(f => f.ThumbnailUrls).IsRequired(false); builder.OwnsOne(f => f.Metadata, b => { b.ToJson(); }); diff --git a/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs b/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs index 1207c34..bf425e4 100644 --- a/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs +++ b/LiteCharms.Features.MidrandBooks/Products/Models/Product.cs @@ -22,8 +22,6 @@ public class Product public string[]? ThumbnailUrls { get; set; } - public string[]? Categories { get; set; } - public ProductMetadata? Metadata { get; set; } public ProductPrice? Price { get; set; } diff --git a/LiteCharms.Features.MidrandBooks/Products/Models/ProductCategory.cs b/LiteCharms.Features.MidrandBooks/Products/Models/ProductCategory.cs new file mode 100644 index 0000000..38e70de --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Products/Models/ProductCategory.cs @@ -0,0 +1,10 @@ +namespace LiteCharms.Features.MidrandBooks.Products.Models; + +public class ProductCategory +{ + public long Id { get; set; } + + public long ProductId { get; set; } + + public long CategoryId { get; set; } +} diff --git a/LiteCharms.Features.MidrandBooks/Products/ProductService.cs b/LiteCharms.Features.MidrandBooks/Products/ProductService.cs index 4d1b0ab..a7b47ce 100644 --- a/LiteCharms.Features.MidrandBooks/Products/ProductService.cs +++ b/LiteCharms.Features.MidrandBooks/Products/ProductService.cs @@ -1,4 +1,5 @@ using LiteCharms.Features.MidrandBooks.Abstractions; +using LiteCharms.Features.MidrandBooks.Categories.Models; using LiteCharms.Features.MidrandBooks.Extensions; using LiteCharms.Features.MidrandBooks.Postgres; using LiteCharms.Features.MidrandBooks.Products.Models; @@ -8,6 +9,100 @@ namespace LiteCharms.Features.MidrandBooks.Products; public sealed class ProductService(IDbContextFactory contextFactory) : IService { + public async ValueTask AddProductCategoryAsync(long productId, long categoryId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Products.AnyAsync(p => p.Id == productId && p.Enabled, cancellationToken)) + return Result.Fail("Product does not exist"); + + if (!await context.Categories.AnyAsync(c => c.Id == categoryId && c.Enabled, cancellationToken)) + return Result.Fail("Category does not exist"); + + if (await context.ProductCategories.AnyAsync(c => c.ProductId == productId && c.CategoryId == categoryId, cancellationToken)) + return Result.Fail("Category already assigned to product"); + + context.ProductCategories.Add(new Entities.ProductCategory + { + ProductId = productId, + CategoryId = categoryId, + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail("Could not add category to product"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask> GetProductCategoriesAsync(long productId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var categories = await context.ProductCategories.AsNoTracking() + .Where(p => p.ProductId == productId) + .OrderByDescending(o => o.Id) + .Select(p => p.Category) + .ToArrayAsync(cancellationToken); + + return categories?.Length > 0 + ? Result.Ok(categories.Select(c => c!.ToModel()).ToArray()) + : Result.Fail("Failed to get product categories"); + + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask DeleteProductCategoryAsync(long productId, long categoryId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var rowsDeleted = await context.ProductCategories + .Where(p => p.ProductId == productId && p.CategoryId == categoryId) + .ExecuteDeleteAsync(cancellationToken); + + return rowsDeleted > 0 + ? Result.Ok() + : Result.Fail("No product categories were deleted"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask DeleteAllProductCategoriesAsync(long productId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var rowsDeleted = await context.ProductCategories + .Where(p => p.ProductId == productId) + .ExecuteDeleteAsync(cancellationToken); + + return rowsDeleted > 0 + ? Result.Ok() + : Result.Fail("No product categories were deleted"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + public async ValueTask UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default) { try @@ -68,9 +163,6 @@ public sealed class ProductService(IDbContextFactory cont if (!string.IsNullOrWhiteSpace(filter.Title)) query = query.Where(p => EF.Functions.ILike(p.Name!, $"%{filter.Title}%")); - if (!string.IsNullOrWhiteSpace(filter.Category)) - query = query.Where(p => p.Categories.Contains(filter.Category)); - if (!string.IsNullOrWhiteSpace(filter.Manufacturer)) query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%")); @@ -152,7 +244,6 @@ public sealed class ProductService(IDbContextFactory cont ImageUrl = request.ImageUrl, ThumbnailUrls = request.ThumbnailUrls, Metadata = request.Metadata, - Categories = request.Categories, Enabled = true }); diff --git a/LiteCharms.Features/Models/ProductFilter.cs b/LiteCharms.Features/Models/ProductFilter.cs index bfed022..78f6d90 100644 --- a/LiteCharms.Features/Models/ProductFilter.cs +++ b/LiteCharms.Features/Models/ProductFilter.cs @@ -6,8 +6,6 @@ public sealed class ProductFilter public string? Title { get; set; } - public string? Category { get; set; } - public string? Manufacturer { get; set; } public string? SerialNumber { get; set; }