Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e77666d9e | |||
| 4d21740124 |
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user