This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
using LiteCharms.Features.MidrandBooks.AuthorBooks;
|
||||
using LiteCharms.Features.MidrandBooks.Authors;
|
||||
using LiteCharms.Features.MidrandBooks.Products;
|
||||
using LiteCharms.Features.MidrandBooks.Seed.Configuration;
|
||||
|
||||
namespace LiteCharms.Features.MidrandBooks.Seed;
|
||||
|
||||
public class ProductsSeederService(ProductService productService, AuthorService authorService, BooksService booksService,
|
||||
IOptions<CdnSettings> options, ILogger<ProductsSeederService> logger) : BackgroundService
|
||||
{
|
||||
private readonly CdnSettings cdnSettings = options.Value;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
logger.LogInformation("Product Seeding started");
|
||||
|
||||
if (cdnSettings.BookCovers is null || cdnSettings.BookCovers.Length == 0)
|
||||
{
|
||||
logger.LogWarning("No book covers found in CDN settings. Seeding aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Bogus Faker engine
|
||||
var faker = new Faker();
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
|
||||
// Ensure repeatable data sets if run multiple times by anchoring the seed
|
||||
Randomizer.Seed = new Random(42);
|
||||
|
||||
foreach (var bookCover in cdnSettings.BookCovers)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) break;
|
||||
|
||||
// Generate beautifully mixed eclectic topics on the fly
|
||||
var bookTopic = faker.PickRandom(
|
||||
// --- Tech & IT ---
|
||||
"C# 12 & Modern .NET Architecture",
|
||||
"PostgreSQL Database Optimization",
|
||||
"Docker & Kubernetes in Production",
|
||||
"Domain-Driven Design Paradigms",
|
||||
"Artificial Intelligence with Python",
|
||||
|
||||
// --- Sci-Fi & Fantasy ---
|
||||
"The Chronicles of the Quantum Nebula",
|
||||
"Legends of the Lost Cybernetic Kingdom",
|
||||
"Parallel Dimensions and Rogue Time Streams",
|
||||
"The Last Android in Neo-Johannesburg",
|
||||
|
||||
// --- Thrillers, Mystery & Crime ---
|
||||
"The Midnight Code Cryptograph",
|
||||
"Shadows in the Highveld",
|
||||
"The Silent Witness of Midrand",
|
||||
"Deception on the 14th Floor",
|
||||
|
||||
// --- Business, Finance & Wealth ---
|
||||
"Mastering the South African Tech Market",
|
||||
"The Modern Entrepreneur's Blueprint",
|
||||
"Generational Wealth and Venture Capital",
|
||||
"Negotiation Tactics for High-Stakes Deals",
|
||||
|
||||
// --- Self-Help & Personal Growth ---
|
||||
"The Art of Relentless Focus",
|
||||
"Building High-Performance Habits",
|
||||
"The Mindfulness Guide for Software Engineers",
|
||||
"Unlocking Creative Flow Under Pressure"
|
||||
);
|
||||
|
||||
// Defensive Length Processing to avoid Entity Framework / Postgres string truncation crashes
|
||||
var rawTitle = $"{faker.Company.CatchPhrase()} with {bookTopic}";
|
||||
var bookTitle = rawTitle.Length > 255 ? rawTitle[..252] + "..." : rawTitle;
|
||||
|
||||
var rawSummary = $"A comprehensive guide to mastering {bookTopic}. Learn modern implementation techniques through real-world software engineering paradigms.";
|
||||
var bookSummary = rawSummary.Length > 512 ? rawSummary[..509] + "..." : rawSummary;
|
||||
|
||||
// Generating a single concise paragraph ensures a rich text description falling safely well under 1024
|
||||
var rawDescription = faker.Lorem.Paragraph(3);
|
||||
var bookDescription = rawDescription.Length > 1024 ? rawDescription[..1021] + "..." : rawDescription;
|
||||
|
||||
var authorFirstName = faker.Name.FirstName();
|
||||
var authorLastName = faker.Name.LastName();
|
||||
var publisherCompany = faker.Company.CompanyName();
|
||||
|
||||
// Step 1: Add Product
|
||||
var productCreateResult = await productService.CreateProductAsync(new Products.Models.CreateProduct
|
||||
{
|
||||
Name = bookTitle,
|
||||
Summary = bookSummary,
|
||||
Description = bookDescription,
|
||||
ImageUrl = $"{cdnSettings.BaseCdn}{bookCover}",
|
||||
Type = ProductTypes.Book,
|
||||
Metadata = new Models.ProductMetadata
|
||||
{
|
||||
CopyrightInfo = $"© {DateTime.UtcNow.Year} {publisherCompany}. All rights reserved.",
|
||||
ManufactureDate = faker.Date.Past(3).ToString("yyyy-MM-dd", culture),
|
||||
Manufacturer = $"{authorFirstName} {authorLastName} / {publisherCompany}",
|
||||
SerialNumber = faker.Phone.PhoneNumber("978-##########")
|
||||
},
|
||||
Categories = ["Coding", "Computers", "IT"]
|
||||
}, stoppingToken);
|
||||
|
||||
if (productCreateResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to create product: {Error}", productCreateResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 2: Enable product so it can show on the shop
|
||||
var enableProductResult = await productService.UpdateProductStatusAsync(productId: productCreateResult.Value, isEnabled: true, stoppingToken);
|
||||
|
||||
if (enableProductResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to enable created product: {Error}", enableProductResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 3: Create Product Price
|
||||
var productPriceCreateResult = await productService.CreateProductPriceAsync(productId: productCreateResult.Value, request: new Products.Models.CreateProductPrice
|
||||
{
|
||||
// Generates fair, dynamic prices in Rands between R150 and R650, snapped neatly to integers
|
||||
Amount = Math.Round(faker.Random.Decimal(150m, 650m), 2),
|
||||
Discount = 0.0m
|
||||
}, stoppingToken);
|
||||
|
||||
if (productPriceCreateResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to create product price: {Error}", productPriceCreateResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 4: Create Author
|
||||
var authorCreateResult = await authorService.CreateAuthorAsync(request: new Authors.Models.CreateAuthor
|
||||
{
|
||||
Name = authorFirstName,
|
||||
LastName = authorLastName,
|
||||
Company = publisherCompany,
|
||||
VatNumber = faker.Random.Bool() ? faker.Phone.PhoneNumber("4#########") : "",
|
||||
PublisherType = faker.PickRandom<PublisherTypes>(),
|
||||
Email = faker.Internet.Email(authorFirstName, authorLastName),
|
||||
Website = faker.Internet.Url(),
|
||||
ImageUrl = faker.Internet.Avatar(),
|
||||
SocialMedia =
|
||||
[
|
||||
new Models.SocialMedia
|
||||
{
|
||||
Name = "LinkedIn",
|
||||
ImageUrl = "https://cdn.example.com/icons/linkedin.png",
|
||||
Type = SocialMediaTypes.LinkedIn,
|
||||
Url = $"https://linkedin.com/in/{authorFirstName.ToLower(culture)}-{authorLastName.ToLower(culture)}"
|
||||
},
|
||||
new Models.SocialMedia
|
||||
{
|
||||
Name = "GitHub",
|
||||
ImageUrl = "https://cdn.example.com/icons/github.png",
|
||||
Type = SocialMediaTypes.GitHub,
|
||||
Url = $"https://github.com/tech-{authorFirstName.ToLower(culture)}"
|
||||
}
|
||||
],
|
||||
Biography = $"{authorFirstName} {authorLastName} is a veteran technologist and systems architect with over a decade of domain expertise. " + faker.Lorem.Paragraph(2),
|
||||
ThumbnailImageUrl = null
|
||||
}, stoppingToken);
|
||||
|
||||
if (authorCreateResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to create author: {Error}", authorCreateResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 5: Create Author-Book link (product linkage)
|
||||
var authorBookCreateResult = await booksService.CreateBookAsync(authorId: authorCreateResult.Value, productId: productCreateResult.Value, stoppingToken);
|
||||
|
||||
if (authorBookCreateResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to create author-book linkage: {Error}", authorBookCreateResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
var enableAuthorBookResult = await booksService.UpdateBookStatusAsync(bookId: authorBookCreateResult.Value, isEnabled: true, stoppingToken);
|
||||
|
||||
if (enableAuthorBookResult.IsFailed)
|
||||
{
|
||||
logger.LogError("Failed to enable author-book link: {Error}", enableAuthorBookResult.Errors[0].Message);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.LogInformation("Successfully seeded book product: {Title}", bookTitle);
|
||||
}
|
||||
|
||||
logger.LogInformation("Product Seeding completed successfully.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user