Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41b6b71b31 | |||
| 0702caa42d | |||
| ee6beef603 | |||
| 4f6dbfcd37 | |||
| 1c3f3eaf0d | |||
| 91ede2d568 | |||
| 2e77666d9e | |||
| 4d21740124 | |||
| 1977b6b301 | |||
| 18d1640808 | |||
| e40c958066 |
@@ -0,0 +1,133 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Categories;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Products;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Seed;
|
||||||
|
|
||||||
|
public class CategorySeederService(CategoryService categoryService, ProductService productService, IFeatureManager features,
|
||||||
|
ILogger<CategorySeederService> logger) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
if (!await features.IsEnabledAsync("CategorySeederService")) return;
|
||||||
|
|
||||||
|
logger.LogInformation("Category and Product-Tag Mapping Seeding started (15-char limit applied)");
|
||||||
|
|
||||||
|
// Initialize Bogus to ensure repeatable distribution matrix pathing
|
||||||
|
var faker = new Faker();
|
||||||
|
Randomizer.Seed = new Random(101);
|
||||||
|
|
||||||
|
// 1. Curate Broad Book Categories (IsMain = true, Max 20, Max 15 chars)
|
||||||
|
var broadMainCategories = new[]
|
||||||
|
{
|
||||||
|
"Fiction", "Non-Fiction", "Youth & Kids", "Academic",
|
||||||
|
"Biographies", "Business", "Sci-Fi & Fantasy",
|
||||||
|
"Thrillers", "Self-Help", "History",
|
||||||
|
"Spirituality", "Arts & Photo", "Technology",
|
||||||
|
"Cookbooks", "Travel & Maps", "Poetry & Drama", "Graphic Novels"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Curate Niche Subcategories/Tags (IsMain = false, Max 15 chars)
|
||||||
|
var specializedSubCategories = new[]
|
||||||
|
{
|
||||||
|
"Cyberpunk", "Space Opera", "Historical Fix", "Cozy Mystery", "True Crime",
|
||||||
|
"Agile Project", "Software Eng", "AI & ML", "Cloud Comput",
|
||||||
|
"SA History", "African Lit", "Apartheid Era", "Mandela Legacy",
|
||||||
|
"Finance", "Investments", "Startup", "Leadership",
|
||||||
|
"CBT Therapy", "Mindfulness", "Yoga & Health",
|
||||||
|
"Baking Basics", "African Food", "Vegan Recipes",
|
||||||
|
"Ancient World", "WWII History", "Geopolitics",
|
||||||
|
"Writing Guides", "Criticism", "Classic Poetry",
|
||||||
|
"Early Learning", "Teen Romance", "Survival",
|
||||||
|
"Urban Fantasy", "Dark Fantasy", "Psych Thriller", "Hard Sci-Fi",
|
||||||
|
"Data Science", "DevOps", "Cybersecurity",
|
||||||
|
"Economics", "Real Estate", "Governance",
|
||||||
|
"Essays", "Memoirs", "Art History",
|
||||||
|
"Architecture", "Photography", "Travel Writing",
|
||||||
|
"Gaming Culture", "Philosophy", "Ethics",
|
||||||
|
"DIY Home", "SA Gardening", "Parenting"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Seed Main Categories into the System
|
||||||
|
logger.LogInformation("Seeding broad main categories...");
|
||||||
|
foreach (var mainCat in broadMainCategories)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
// Defensive truncation fallback just in case strings get modified later
|
||||||
|
string safeName = mainCat.Length > 15 ? mainCat.Substring(0, 15) : mainCat;
|
||||||
|
|
||||||
|
var result = await categoryService.CreateCategoryAsync(safeName, isMain: true, stoppingToken);
|
||||||
|
if (result.IsFailed && !result.Errors[0].Message.Contains("already exists", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogWarning("Notice while adding main category '{Name}': {Msg}", safeName, result.Errors[0].Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Seed Subcategories into the System
|
||||||
|
logger.LogInformation("Seeding boundless specialized niche tags...");
|
||||||
|
foreach (var subCat in specializedSubCategories)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
string safeName = subCat.Length > 15 ? subCat.Substring(0, 15) : subCat;
|
||||||
|
|
||||||
|
var result = await categoryService.CreateCategoryAsync(safeName, isMain: false, stoppingToken);
|
||||||
|
if (result.IsFailed && !result.Errors[0].Message.Contains("already exists", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogWarning("Notice while adding subcategory '{Name}': {Msg}", safeName, result.Errors[0].Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Query back all enabled categories to extract active IDs for junction mapping
|
||||||
|
var fetchMainResult = await categoryService.GetCategoriesAsync(isMain: true, stoppingToken);
|
||||||
|
var fetchSubResult = await categoryService.GetCategoriesAsync(isMain: false, stoppingToken);
|
||||||
|
|
||||||
|
if (fetchMainResult.IsFailed || fetchSubResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogError("Aborting junction seeding: Could not retrieve categories from data store.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainCategoryIds = fetchMainResult.Value.Select(c => c.Id).ToArray();
|
||||||
|
var subCategoryIds = fetchSubResult.Value.Select(c => c.Id).ToArray();
|
||||||
|
|
||||||
|
// 6. Map Categories to your Product Collection (Product IDs 0 - 21)
|
||||||
|
logger.LogInformation("Beginning Product-Category mapping assignments for Product IDs 0 through 21...");
|
||||||
|
|
||||||
|
for (long productId = 0; productId <= 21; productId++)
|
||||||
|
{
|
||||||
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
// Every book belongs to 1 or 2 main categories
|
||||||
|
int mainCategoriesToAssign = faker.Random.Number(1, 2);
|
||||||
|
var chosenMainIds = faker.PickRandom(mainCategoryIds, mainCategoriesToAssign).Distinct();
|
||||||
|
|
||||||
|
foreach (var mainId in chosenMainIds)
|
||||||
|
{
|
||||||
|
var linkResult = await productService.AddProductCategoryAsync(productId, mainId, stoppingToken);
|
||||||
|
if (linkResult.IsFailed)
|
||||||
|
{
|
||||||
|
if (!linkResult.Errors[0].Message.Contains("exist", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogDebug("Junction note for Product {PId} and Main Category {CId}: {Msg}", productId, mainId, linkResult.Errors[0].Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every book gets 1 to 4 granular subgenre tags
|
||||||
|
int subCategoriesToAssign = faker.Random.Number(1, 4);
|
||||||
|
var chosenSubIds = faker.PickRandom(subCategoryIds, subCategoriesToAssign).Distinct();
|
||||||
|
|
||||||
|
foreach (var subId in chosenSubIds)
|
||||||
|
{
|
||||||
|
var linkResult = await productService.AddProductCategoryAsync(productId, subId, stoppingToken);
|
||||||
|
if (linkResult.IsFailed && !linkResult.Errors[0].Message.Contains("exist", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogDebug("Junction note for Product {PId} and Sub Category {CId}: {Msg}", productId, subId, linkResult.Errors[0].Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Category and Product-Tag Mapping Seeding completed successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ builder.Services
|
|||||||
.AddLogging()
|
.AddLogging()
|
||||||
.AddShopServices()
|
.AddShopServices()
|
||||||
.AddHostedService<ProductsSeederService>()
|
.AddHostedService<ProductsSeederService>()
|
||||||
|
.AddHostedService<CategorySeederService>()
|
||||||
.AddHostedService<CustomerSeederService>()
|
.AddHostedService<CustomerSeederService>()
|
||||||
.AddMidrandShopDatabase(builder.Configuration);
|
.AddMidrandShopDatabase(builder.Configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"FeatureManagement": {
|
"FeatureManagement": {
|
||||||
|
"CategorySeederService": true,
|
||||||
"CustomerSeederService": false,
|
"CustomerSeederService": false,
|
||||||
"ProductsSeederService": false
|
"ProductsSeederService": false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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<Fixture>
|
||||||
|
{
|
||||||
|
private readonly CategoryService categoryService = fixture.Services.GetRequiredService<CategoryService>();
|
||||||
|
|
||||||
|
[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", true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.True(result.Value > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,40 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
private readonly ProductService productService = fixture.Services.GetRequiredService<ProductService>();
|
||||||
|
|
||||||
[IntegrationFact]
|
[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);
|
var result = await productService.GetProductPriceAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
@@ -21,7 +54,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[IntegrationFact]
|
[IntegrationFact]
|
||||||
public async Task GetProductPricesAsync_ShouldReturn_RetultProductPriceList()
|
public async Task GetProductPricesAsync_ShouldReturn_ResultProductPriceList()
|
||||||
{
|
{
|
||||||
var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken);
|
var result = await productService.GetProductPricesAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
@@ -32,7 +65,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[IntegrationFact]
|
[IntegrationFact]
|
||||||
public async Task SearchProductsAsync_ShouldReturn_RetultMatchingProducts()
|
public async Task SearchProductsAsync_ShouldReturn_ResultMatchingProducts()
|
||||||
{
|
{
|
||||||
var filter = new ProductFilter
|
var filter = new ProductFilter
|
||||||
{
|
{
|
||||||
@@ -52,7 +85,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[IntegrationFact]
|
[IntegrationFact]
|
||||||
public async Task GetProductAsync_ShouldReturn_RetultOneProduct()
|
public async Task GetProductAsync_ShouldReturn_ResultOneProduct()
|
||||||
{
|
{
|
||||||
var result = await productService.GetProductAsync(2, fixture.CancellationToken);
|
var result = await productService.GetProductAsync(2, fixture.CancellationToken);
|
||||||
|
|
||||||
@@ -63,7 +96,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[IntegrationFact]
|
[IntegrationFact]
|
||||||
public async Task GetProductsAsync_ShouldReturn_RetultProducts()
|
public async Task GetProductsAsync_ShouldReturn_ResultProducts()
|
||||||
{
|
{
|
||||||
var range = new DateRange
|
var range = new DateRange
|
||||||
{
|
{
|
||||||
@@ -81,7 +114,7 @@ public class ProductServiceFeatureTests(Fixture fixture, ITestOutputHelper outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
[IntegrationFact]
|
[IntegrationFact]
|
||||||
public async Task UpdateProductStatusAsync_ShouldReturn_ResultTrue()
|
public async Task UpdateProductStatusAsync_ShouldResurn_ResultTrue()
|
||||||
{
|
{
|
||||||
var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken);
|
var result = await productService.UpdateProductStatusAsync(2, true, fixture.CancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public sealed class BooksService(IDbContextFactory<MidrandBooksDbContext> contex
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(b => b.Author)
|
.Include(b => b.Author)
|
||||||
.Include(b => b.Product)
|
.Include(b => b.Product)
|
||||||
.ThenInclude(b => b.Prices)
|
.ThenInclude(b => b!.Prices)
|
||||||
.OrderByDescending(b => b.CreatedAt)
|
.OrderByDescending(b => b.CreatedAt)
|
||||||
.Where(b => b.AuthorId == authorId)
|
.Where(b => b.AuthorId == authorId)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|||||||
@@ -8,6 +8,29 @@ namespace LiteCharms.Features.MidrandBooks.Authors;
|
|||||||
|
|
||||||
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class AuthorService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
|
public async ValueTask<Result<Author>> GetAuthorByProductIdAsync(long productId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var author = await context.Books
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(i => i.Author)
|
||||||
|
.Where(b => b.ProductId == productId)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (author is null)
|
||||||
|
return Result.Fail<Author>(new Error($"No author association discovered for Product ID {productId}"));
|
||||||
|
|
||||||
|
return Result.Ok(author.Author!.ToModel());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Author>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> UpdateAuthorStatusAsync(long authorId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -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<MidrandBooksDbContext> contextFactory) : IService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> 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<Result<Category>> 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<Category>("Failed to create new category");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Category>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<Category[]>> 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.Name)
|
||||||
|
.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<Category[]>("No categories found");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<Category[]>(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> 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<Result<long>> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Categories.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<CategoryConfiguration, Category>]
|
||||||
|
public sealed class Category : Models.Category;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Categories.Entities;
|
||||||
|
|
||||||
|
public sealed class CategoryConfiguration : IEntityTypeConfiguration<Category>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Category> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
using LiteCharms.Features.MidrandBooks.Authors.Models;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Categories.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
using LiteCharms.Features.MidrandBooks.Orders.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
using LiteCharms.Features.MidrandBooks.Pages.Models;
|
||||||
@@ -9,6 +10,14 @@ namespace LiteCharms.Features.MidrandBooks.Extensions;
|
|||||||
|
|
||||||
public static class Mappers
|
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()
|
public static ShippingProvider ToModel(this Orders.Entities.ShippingProvider entity) => new()
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = entity.Id,
|
||||||
@@ -65,7 +74,7 @@ public static class Mappers
|
|||||||
SocialMedia = entiry.SocialMedia,
|
SocialMedia = entiry.SocialMedia,
|
||||||
UpdatedAt = entiry.UpdatedAt,
|
UpdatedAt = entiry.UpdatedAt,
|
||||||
VatNumber = entiry.VatNumber,
|
VatNumber = entiry.VatNumber,
|
||||||
Website = entiry.Website
|
Website = entiry.Website
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Address ToModel(this Customers.Entities.Address entity) => new()
|
public static Address ToModel(this Customers.Entities.Address entity) => new()
|
||||||
@@ -83,7 +92,7 @@ public static class Mappers
|
|||||||
Street = entity.Street,
|
Street = entity.Street,
|
||||||
City = entity.City,
|
City = entity.City,
|
||||||
State = entity.State,
|
State = entity.State,
|
||||||
Country = entity.Country
|
Country = entity.Country
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Contact ToModel(this Customers.Entities.Contact entity) => new()
|
public static Contact ToModel(this Customers.Entities.Contact entity) => new()
|
||||||
@@ -112,7 +121,7 @@ public static class Mappers
|
|||||||
Enabled = entity.Enabled,
|
Enabled = entity.Enabled,
|
||||||
Notes = entity.Notes,
|
Notes = entity.Notes,
|
||||||
References = entity.References,
|
References = entity.References,
|
||||||
Type = entity.Type
|
Type = entity.Type
|
||||||
};
|
};
|
||||||
|
|
||||||
public static AuthorBook ToModel(this AuthorBooks.Entities.AuthorBook entity) => new()
|
public static AuthorBook ToModel(this AuthorBooks.Entities.AuthorBook entity) => new()
|
||||||
@@ -138,25 +147,21 @@ public static class Mappers
|
|||||||
Enabled = entity.Enabled
|
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,
|
||||||
Id = entity.Id,
|
UpdatedAt = entity.UpdatedAt,
|
||||||
CreatedAt = entity.CreatedAt,
|
Name = entity.Name,
|
||||||
UpdatedAt = entity.UpdatedAt,
|
Summary = entity.Summary,
|
||||||
Name = entity.Name,
|
Description = entity.Description,
|
||||||
Summary = entity.Summary,
|
Type = entity.Type,
|
||||||
Description = entity.Description,
|
ImageUrl = entity.ImageUrl,
|
||||||
Type = entity.Type,
|
ThumbnailUrls = entity.ThumbnailUrls,
|
||||||
ImageUrl = entity.ImageUrl,
|
Metadata = entity.Metadata,
|
||||||
ThumbnailUrls = entity.ThumbnailUrls,
|
Enabled = entity.Enabled,
|
||||||
Metadata = entity.Metadata,
|
Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null,
|
||||||
Categories = entity.Categories,
|
};
|
||||||
Enabled = entity.Enabled,
|
|
||||||
Price = entity.Prices?.FirstOrDefault(p => p.Enabled)?.ToModel() ?? null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Author ToModel(this Authors.Entities.Author entity) => new()
|
public static Author ToModel(this Authors.Entities.Author entity) => new()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
|
|
||||||
<!-- Quartz Scheduler-->
|
<!-- Quartz Scheduler-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Meziantou.Analyzer" Version="3.0.96">
|
<PackageReference Include="Humanizer" Version="3.0.10" />
|
||||||
|
<PackageReference Include="Meziantou.Analyzer" Version="3.0.98">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -104,7 +105,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
|
||||||
|
|
||||||
<!-- Global Usings -->
|
<!-- Global Usings -->
|
||||||
<Using Include="Npgsql" />
|
<Using Include="Npgsql" />
|
||||||
@@ -115,8 +116,8 @@
|
|||||||
|
|
||||||
<!-- Email -->
|
<!-- Email -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MailKit" Version="4.16.0" />
|
<PackageReference Include="MailKit" Version="4.17.0" />
|
||||||
<PackageReference Include="MimeKit" Version="4.16.0" />
|
<PackageReference Include="MimeKit" Version="4.17.0" />
|
||||||
|
|
||||||
<!-- Global Usings-->
|
<!-- Global Usings-->
|
||||||
<Using Include="MimeKit" />
|
<Using Include="MimeKit" />
|
||||||
@@ -147,6 +148,7 @@
|
|||||||
|
|
||||||
<!-- Shared Usings -->
|
<!-- Shared Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="Humanizer" />
|
||||||
<Using Include="System.Globalization" />
|
<Using Include="System.Globalization" />
|
||||||
<Using Include="System.Reflection" />
|
<Using Include="System.Reflection" />
|
||||||
<Using Include="Microsoft.AspNetCore.Builder" />
|
<Using Include="Microsoft.AspNetCore.Builder" />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
using LiteCharms.Features.MidrandBooks.AuthorBooks.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
using LiteCharms.Features.MidrandBooks.Authors.Entities;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Categories.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
using LiteCharms.Features.MidrandBooks.Customers.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
using LiteCharms.Features.MidrandBooks.Orders.Entities;
|
||||||
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
using LiteCharms.Features.MidrandBooks.Pages.Entities;
|
||||||
@@ -35,4 +36,8 @@ public sealed class MidrandBooksDbContext(DbContextOptions<MidrandBooksDbContext
|
|||||||
public DbSet<Shipping> Shippings => Set<Shipping>();
|
public DbSet<Shipping> Shippings => Set<Shipping>();
|
||||||
|
|
||||||
public DbSet<ShippingProvider> ShippingProviders => Set<ShippingProvider>();
|
public DbSet<ShippingProvider> ShippingProviders => Set<ShippingProvider>();
|
||||||
|
|
||||||
|
public DbSet<Category> Categories => Set<Category>();
|
||||||
|
|
||||||
|
public DbSet<ProductCategory> ProductCategories => Set<ProductCategory>();
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+966
@@ -0,0 +1,966 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.AuthorBooks.Entities.AuthorBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Ranking")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Authors.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Biography")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("PublisherType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailImageUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Categories.Entities.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsMain")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("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<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BuildingType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("City")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Country")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PostalCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("State")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Street")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Addresses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Contact", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsPrimary")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerId");
|
||||||
|
|
||||||
|
b.ToTable("Contacts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Company")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("VatNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Website")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Customers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("CustomerId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("InvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<decimal>("Total")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.OrderItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ProductPriceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.HasIndex("ProductPriceId");
|
||||||
|
|
||||||
|
b.ToTable("OrderItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.Shipping", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AddressId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("ShippingProviderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingNumber")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddressId");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ShippingProviderId");
|
||||||
|
|
||||||
|
b.ToTable("Shippings", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Orders.Entities.ShippingProvider", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("TrackingUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ShippingProviders");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Pages.Entities.BookPage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorBookId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Notes")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(0);
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorBookId");
|
||||||
|
|
||||||
|
b.ToTable("BookPages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Payments.Entities.Refund", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<long>("OrderId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("character varying(1000)");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("Refunds", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Categories")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("ImageUrl")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("ThumbnailUrls")
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Products", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.Property<decimal>("Discount")
|
||||||
|
.HasPrecision(18, 2)
|
||||||
|
.HasColumnType("numeric(18,2)");
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<long>("ProductId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("now()");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProductId");
|
||||||
|
|
||||||
|
b.ToTable("Prices", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.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<long>("AuthorId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("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<long>("CustomerId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("ImageUrl");
|
||||||
|
|
||||||
|
b1.Property<string>("Name");
|
||||||
|
|
||||||
|
b1.Property<int>("Type");
|
||||||
|
|
||||||
|
b1.Property<string>("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<long>("BookPageId");
|
||||||
|
|
||||||
|
b1.Property<int>("__synthesizedOrdinal")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b1.Property<string>("Description");
|
||||||
|
|
||||||
|
b1.Property<string>("Tag");
|
||||||
|
|
||||||
|
b1.Property<string>("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<long>("ProductId");
|
||||||
|
|
||||||
|
b1.Property<string>("CopyrightInfo");
|
||||||
|
|
||||||
|
b1.Property<string>("ManufactureDate");
|
||||||
|
|
||||||
|
b1.Property<string>("Manufacturer");
|
||||||
|
|
||||||
|
b1.Property<string>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedCategories : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Categories",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "character varying(15)", maxLength: 15, nullable: false),
|
||||||
|
IsMain = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Categories", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Categories");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1007
File diff suppressed because it is too large
Load Diff
+68
@@ -0,0 +1,68 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedProductCategories : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Categories",
|
||||||
|
table: "Products");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ProductCategories",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
ProductId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CategoryId = table.Column<long>(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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ProductCategories");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "Categories",
|
||||||
|
table: "Products",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
-3
@@ -130,6 +130,34 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.ToTable("Authors", (string)null);
|
b.ToTable("Authors", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Categories.Entities.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("Enabled")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<bool>("IsMain")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Customers.Entities.Address", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@@ -558,9 +586,6 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Categories")
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -605,6 +630,29 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.ToTable("Products", (string)null);
|
b.ToTable("Products", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("CategoryId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
@@ -883,6 +931,25 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
b.Navigation("Metadata");
|
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 =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.ProductPrice", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
b.HasOne("LiteCharms.Features.MidrandBooks.Products.Entities.Product", "Product")
|
||||||
@@ -927,6 +994,8 @@ namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
modelBuilder.Entity("LiteCharms.Features.MidrandBooks.Products.Entities.Product", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Categories");
|
||||||
|
|
||||||
b.Navigation("Prices");
|
b.Navigation("Prices");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
|
|||||||
@@ -3,5 +3,7 @@
|
|||||||
[EntityTypeConfiguration<ProductConfiguration, Product>]
|
[EntityTypeConfiguration<ProductConfiguration, Product>]
|
||||||
public class Product : Models.Product
|
public class Product : Models.Product
|
||||||
{
|
{
|
||||||
|
public virtual ICollection<ProductCategory> Categories { get; set; } = [];
|
||||||
|
|
||||||
public virtual ICollection<ProductPrice> Prices { get; set; } = [];
|
public virtual ICollection<ProductPrice> Prices { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using LiteCharms.Features.MidrandBooks.Categories.Entities;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
[EntityTypeConfiguration<ProductCategoryConfiguration, ProductCategory>]
|
||||||
|
public class ProductCategory : Models.ProductCategory
|
||||||
|
{
|
||||||
|
public virtual Product? Product { get; set; }
|
||||||
|
|
||||||
|
public virtual Category? Category { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace LiteCharms.Features.MidrandBooks.Products.Entities;
|
||||||
|
|
||||||
|
public sealed class ProductCategoryConfiguration : IEntityTypeConfiguration<ProductCategory>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ProductCategory> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@ public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
|
|||||||
builder.Property(f => f.Description).HasMaxLength(1024);
|
builder.Property(f => f.Description).HasMaxLength(1024);
|
||||||
builder.Property(f => f.ImageUrl).HasMaxLength(1024);
|
builder.Property(f => f.ImageUrl).HasMaxLength(1024);
|
||||||
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
builder.Property(f => f.Enabled).HasDefaultValue(false);
|
||||||
builder.Property(f => f.Categories).IsRequired(false);
|
|
||||||
builder.Property(f => f.ThumbnailUrls).IsRequired(false);
|
builder.Property(f => f.ThumbnailUrls).IsRequired(false);
|
||||||
|
|
||||||
builder.OwnsOne(f => f.Metadata, b => { b.ToJson(); });
|
builder.OwnsOne(f => f.Metadata, b => { b.ToJson(); });
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ public class Product
|
|||||||
|
|
||||||
public string[]? ThumbnailUrls { get; set; }
|
public string[]? ThumbnailUrls { get; set; }
|
||||||
|
|
||||||
public string[]? Categories { get; set; }
|
|
||||||
|
|
||||||
public ProductMetadata? Metadata { get; set; }
|
public ProductMetadata? Metadata { get; set; }
|
||||||
|
|
||||||
public ProductPrice? Price { get; set; }
|
public ProductPrice? Price { get; set; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
using LiteCharms.Features.MidrandBooks.Categories.Models;
|
||||||
using LiteCharms.Features.MidrandBooks.Extensions;
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
using LiteCharms.Features.MidrandBooks.Postgres;
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
||||||
using LiteCharms.Features.MidrandBooks.Products.Models;
|
using LiteCharms.Features.MidrandBooks.Products.Models;
|
||||||
@@ -8,6 +9,100 @@ namespace LiteCharms.Features.MidrandBooks.Products;
|
|||||||
|
|
||||||
public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
||||||
{
|
{
|
||||||
|
public async ValueTask<Result> 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<Result<Category[]>> 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<Category[]>("Failed to get product categories");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result> 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<Result> 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<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
|
public async ValueTask<Result> UpdateProductPriceStatusAsync(long productPriceId, bool isEnabled, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -68,9 +163,6 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
if (!string.IsNullOrWhiteSpace(filter.Title))
|
if (!string.IsNullOrWhiteSpace(filter.Title))
|
||||||
query = query.Where(p => EF.Functions.ILike(p.Name!, $"%{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))
|
if (!string.IsNullOrWhiteSpace(filter.Manufacturer))
|
||||||
query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%"));
|
query = query.Where(p => EF.Functions.ILike(p.Metadata!.Manufacturer!, $"%{filter.Manufacturer}%"));
|
||||||
|
|
||||||
@@ -152,7 +244,6 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
ImageUrl = request.ImageUrl,
|
ImageUrl = request.ImageUrl,
|
||||||
ThumbnailUrls = request.ThumbnailUrls,
|
ThumbnailUrls = request.ThumbnailUrls,
|
||||||
Metadata = request.Metadata,
|
Metadata = request.Metadata,
|
||||||
Categories = request.Categories,
|
|
||||||
Enabled = true
|
Enabled = true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,7 +304,8 @@ public sealed class ProductService(IDbContextFactory<MidrandBooksDbContext> cont
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var product = await context.Products.AsNoTracking().FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
var product = await context.Products
|
||||||
|
.AsNoTracking().FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);
|
||||||
|
|
||||||
return product is null
|
return product is null
|
||||||
? Result.Fail<Product>(new Error($"Product with ID {productId} not found."))
|
? Result.Fail<Product>(new Error($"Product with ID {productId} not found."))
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ public sealed class ProductFilter
|
|||||||
|
|
||||||
public string? Title { get; set; }
|
public string? Title { get; set; }
|
||||||
|
|
||||||
public string? Category { get; set; }
|
|
||||||
|
|
||||||
public string? Manufacturer { get; set; }
|
public string? Manufacturer { get; set; }
|
||||||
|
|
||||||
public string? SerialNumber { get; set; }
|
public string? SerialNumber { get; set; }
|
||||||
|
|||||||
Reference in New Issue
Block a user