From e40c958066811111708d7458f41c54201a4e03d3 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sat, 30 May 2026 14:22:00 +0200 Subject: [PATCH] Implemented category feature --- .../CategoryServiceFeatureTests.cs | 70 ++ .../Categories/CategoryService.cs | 132 +++ .../Categories/Entities/Category.cs | 4 + .../Entities/CategoryConfiguration.cs | 14 + .../Categories/Models/Category.cs | 12 + .../Extensions/Mappers.cs | 15 +- .../LiteCharms.Features.MidrandBooks.csproj | 10 +- .../Postgres/MidrandBooksDbContext.cs | 3 + ...20260530104851_AddedCategories.Designer.cs | 966 ++++++++++++++++++ .../20260530104851_AddedCategories.cs | 37 + .../MidrandBooksDbContextModelSnapshot.cs | 28 + 11 files changed, 1284 insertions(+), 7 deletions(-) create mode 100644 LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs create mode 100644 LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs create mode 100644 LiteCharms.Features.MidrandBooks/Categories/Entities/Category.cs create mode 100644 LiteCharms.Features.MidrandBooks/Categories/Entities/CategoryConfiguration.cs create mode 100644 LiteCharms.Features.MidrandBooks/Categories/Models/Category.cs create mode 100644 LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.Designer.cs create mode 100644 LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.cs diff --git a/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs new file mode 100644 index 0000000..7c8fbea --- /dev/null +++ b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs @@ -0,0 +1,70 @@ +using LiteCharms.Features.MidrandBooks.Categories; +using LiteCharms.Features.MidrandBooks.Tests.Common; + +namespace LiteCharms.Features.MidrandBooks.Tests; + +public class CategoryServiceFeatureTests(Fixture fixture) : IClassFixture +{ + private readonly CategoryService categoryService = fixture.Services.GetRequiredService(); + + [IntegrationFact] + public async Task UpdateCategoryStatusAsync_ShouldReturn_ResultWithSuccess() + { + var result = await categoryService.UpdateCategoryStatusAsync(3, false, false, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + } + + [IntegrationFact] + public async Task GetCategoryAsync_ShouldReturn_ResultWithCategory() + { + var result = await categoryService.GetCategoryAsync(3, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + } + + [IntegrationFact] + public async Task GetCategoriesAsync_ShouldReturn_All_ResultWithCategoryList() + { + var result = await categoryService.GetCategoriesAsync(isMain: null,fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotEmpty(result.Value); + } + + [IntegrationFact] + public async Task GetCategoriesAsync_ShouldReturn_MainCategory_ResultWithCategoryList() + { + var result = await categoryService.GetCategoriesAsync(true, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotEmpty(result.Value); + } + + [IntegrationFact] + public async Task GetCategoriesAsync_ShouldReturn_SubMainCategory_ResultWithCategoryList() + { + var result = await categoryService.GetCategoriesAsync(false, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotEmpty(result.Value); + } + + [IntegrationFact] + public async Task CreateCategoriesAsync_ShouldReturn_ResultWithSuccess() + { + var result = await categoryService.CreateCategoriesAsync(fixture.CancellationToken, "Test", "Test 1", "Test 2"); + + Assert.True(result.IsSuccess); + } + + [IntegrationFact] + public async Task CreateCategoryAsync_ShouldReturn_ResultWithCategoryId() + { + var result = await categoryService.CreateCategoryAsync("Test", false, fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.True(result.Value > 0); + } +} diff --git a/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs b/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs new file mode 100644 index 0000000..dd6c557 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Categories/CategoryService.cs @@ -0,0 +1,132 @@ +using LiteCharms.Features.MidrandBooks.Abstractions; +using LiteCharms.Features.MidrandBooks.Categories.Models; +using LiteCharms.Features.MidrandBooks.Extensions; +using LiteCharms.Features.MidrandBooks.Postgres; + +namespace LiteCharms.Features.MidrandBooks.Categories; + +public sealed class CategoryService(IDbContextFactory contextFactory) : IService +{ + public async ValueTask UpdateCategoryStatusAsync(long categoryId, bool enabled, bool isMain, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var rowsUpdated = await context.Categories + .Where(c => c.Id == categoryId && c.Enabled) + .ExecuteUpdateAsync(setters => setters + .SetProperty(c => c.Enabled, enabled) + .SetProperty(c => c.IsMain, isMain), cancellationToken); + + return rowsUpdated > 0 + ? Result.Ok() + : Result.Fail(new Error($"Failed to update category")); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask> GetCategoryAsync(long categoryId, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var category = await context.Categories.AsNoTracking().FirstOrDefaultAsync(c => c.Id == categoryId, cancellationToken); + + return category is not null + ? Result.Ok(category.ToModel()) + : Result.Fail("Failed to create new category"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask> GetCategoriesAsync(bool? isMain = null, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var query = context.Categories.AsNoTracking() + .OrderByDescending(o => o.IsMain) + .ThenByDescending(o => o.Id) + .ThenBy(o => o.IsMain) + .AsQueryable(); + + query = isMain is null + ? query.Where(c => c.Enabled).AsQueryable() + : query.Where(c => c.Enabled && c.IsMain == isMain.Value); + + var categories = await query.ToListAsync(cancellationToken); + + return categories?.Count > 0 + ? Result.Ok(categories.Select(c => c.ToModel()).ToArray()) + : Result.Fail("No categories found"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask CreateCategoriesAsync(CancellationToken cancellationToken = default, params string[] categories) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + foreach (var category in categories) + { + if (await context.Categories.AnyAsync(c => EF.Functions.ILike(c.Name!, category!), cancellationToken)) + continue; + + context.Categories.Add(new Entities.Category + { + Name = category.Humanize(LetterCasing.Title), + IsMain = false, + Enabled = true, + }); + } + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok() + : Result.Fail("Failed to add any category in the list"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } + + public async ValueTask> CreateCategoryAsync(string category, bool isMain, CancellationToken cancellationToken = default) + { + try + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (await context.Categories.AnyAsync(c => EF.Functions.ILike(c.Name!, category!), cancellationToken)) + return Result.Fail($"Category '{category}' already exists"); + + var newCategory = context.Categories.Add(new Entities.Category + { + Name = StringHumanizeExtensions.Humanize(category, LetterCasing.Title), + IsMain = isMain, + Enabled = true, + }); + + return await context.SaveChangesAsync(cancellationToken) > 0 + ? Result.Ok(newCategory.Entity.Id) + : Result.Fail("Failed to create new category"); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} diff --git a/LiteCharms.Features.MidrandBooks/Categories/Entities/Category.cs b/LiteCharms.Features.MidrandBooks/Categories/Entities/Category.cs new file mode 100644 index 0000000..5aac931 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Categories/Entities/Category.cs @@ -0,0 +1,4 @@ +namespace LiteCharms.Features.MidrandBooks.Categories.Entities; + +[EntityTypeConfiguration] +public sealed class Category : Models.Category; diff --git a/LiteCharms.Features.MidrandBooks/Categories/Entities/CategoryConfiguration.cs b/LiteCharms.Features.MidrandBooks/Categories/Entities/CategoryConfiguration.cs new file mode 100644 index 0000000..bc5a7bc --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Categories/Entities/CategoryConfiguration.cs @@ -0,0 +1,14 @@ +namespace LiteCharms.Features.MidrandBooks.Categories.Entities; + +public sealed class CategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Categories"); + + builder.HasKey(c => c.Id); + builder.Property(c => c.Name).IsRequired().HasMaxLength(15); + builder.Property(c => c.IsMain).HasDefaultValue(false); + builder.Property(c => c.Enabled).HasDefaultValue(true); + } +} diff --git a/LiteCharms.Features.MidrandBooks/Categories/Models/Category.cs b/LiteCharms.Features.MidrandBooks/Categories/Models/Category.cs new file mode 100644 index 0000000..b793204 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Categories/Models/Category.cs @@ -0,0 +1,12 @@ +namespace LiteCharms.Features.MidrandBooks.Categories.Models; + +public class Category +{ + public long Id { get; set; } + + public string? Name { get; set; } + + public bool IsMain { get; set; } + + public bool Enabled { get; set; } +} diff --git a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs index 753bcc7..3c3790d 100644 --- a/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs +++ b/LiteCharms.Features.MidrandBooks/Extensions/Mappers.cs @@ -1,5 +1,6 @@ using LiteCharms.Features.MidrandBooks.AuthorBooks.Models; using LiteCharms.Features.MidrandBooks.Authors.Models; +using LiteCharms.Features.MidrandBooks.Categories.Models; using LiteCharms.Features.MidrandBooks.Customers.Models; using LiteCharms.Features.MidrandBooks.Orders.Models; using LiteCharms.Features.MidrandBooks.Pages.Models; @@ -9,6 +10,14 @@ namespace LiteCharms.Features.MidrandBooks.Extensions; public static class Mappers { + public static Category ToModel(this Categories.Entities.Category entity) => new() + { + Id = entity.Id, + Name = entity.Name, + IsMain = entity.IsMain, + Enabled = entity.Enabled, + }; + public static ShippingProvider ToModel(this Orders.Entities.ShippingProvider entity) => new() { Id = entity.Id, @@ -65,7 +74,7 @@ public static class Mappers SocialMedia = entiry.SocialMedia, UpdatedAt = entiry.UpdatedAt, VatNumber = entiry.VatNumber, - Website = entiry.Website + Website = entiry.Website }; public static Address ToModel(this Customers.Entities.Address entity) => new() @@ -83,7 +92,7 @@ public static class Mappers Street = entity.Street, City = entity.City, State = entity.State, - Country = entity.Country + Country = entity.Country }; public static Contact ToModel(this Customers.Entities.Contact entity) => new() @@ -112,7 +121,7 @@ public static class Mappers Enabled = entity.Enabled, Notes = entity.Notes, References = entity.References, - Type = entity.Type + Type = entity.Type }; public static AuthorBook ToModel(this AuthorBooks.Entities.AuthorBook entity) => new() diff --git a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj index 17db6dd..e2e58d3 100644 --- a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj +++ b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj @@ -31,7 +31,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -104,7 +105,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -115,8 +116,8 @@ - - + + @@ -147,6 +148,7 @@ + diff --git a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs index a70e1cf..30af0d7 100644 --- a/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs +++ b/LiteCharms.Features.MidrandBooks/Postgres/MidrandBooksDbContext.cs @@ -1,5 +1,6 @@ using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities; using LiteCharms.Features.MidrandBooks.Authors.Entities; +using LiteCharms.Features.MidrandBooks.Categories.Entities; using LiteCharms.Features.MidrandBooks.Customers.Entities; using LiteCharms.Features.MidrandBooks.Orders.Entities; using LiteCharms.Features.MidrandBooks.Pages.Entities; @@ -35,4 +36,6 @@ public sealed class MidrandBooksDbContext(DbContextOptions Shippings => Set(); public DbSet ShippingProviders => Set(); + + public DbSet Categories => Set(); } diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.Designer.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.Designer.cs new file mode 100644 index 0000000..e007a80 --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.Designer.cs @@ -0,0 +1,966 @@ +// +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("20260530104851_AddedCategories")] + partial class AddedCategories + { + /// + 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.PrimitiveCollection("Categories") + .HasColumnType("text[]"); + + 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.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.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("Prices"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.cs new file mode 100644 index 0000000..96d30de --- /dev/null +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/20260530104851_AddedCategories.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations +{ + /// + public partial class AddedCategories : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(15)", maxLength: 15, nullable: false), + IsMain = table.Column(type: "boolean", nullable: false, defaultValue: false), + Enabled = table.Column(type: "boolean", nullable: false, defaultValue: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Categories"); + } + } +} diff --git a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs index 6045ed4..ec18d1c 100644 --- a/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs +++ b/LiteCharms.Features.MidrandBooks/Postgres/Migrations/MidrandBooksDbContextModelSnapshot.cs @@ -130,6 +130,34 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations 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")