190 lines
8.8 KiB
C#
190 lines
8.8 KiB
C#
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.");
|
|
}
|
|
} |