diff --git a/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs b/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs
new file mode 100644
index 0000000..af16232
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs
@@ -0,0 +1,8 @@
+namespace LiteCharms.Features.MidrandBooks.Seed.Configuration;
+
+public class CdnSettings
+{
+ public string? BaseCdn { get; set; }
+
+ public string[]? BookCovers { get; set; }
+}
diff --git a/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj b/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj
new file mode 100644
index 0000000..14e2ffd
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj
@@ -0,0 +1,158 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ 5c3bc894-8654-4691-99e8-f90d3414843f
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs
new file mode 100644
index 0000000..a506bf3
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs
@@ -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 options, ILogger 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(),
+ 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.");
+ }
+}
\ No newline at end of file
diff --git a/LiteCharms.Features.MidrandBooks.Seed/Program.cs b/LiteCharms.Features.MidrandBooks.Seed/Program.cs
new file mode 100644
index 0000000..f31cdd3
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks.Seed/Program.cs
@@ -0,0 +1,21 @@
+using LiteCharms.Features.MidrandBooks.Extensions;
+using LiteCharms.Features.MidrandBooks.Seed;
+using LiteCharms.Features.MidrandBooks.Seed.Configuration;
+
+var builder = Host.CreateApplicationBuilder(args);
+
+builder.Configuration
+ .AddJsonFile("appsettings.json")
+ .AddUserSecrets(typeof(Program).Assembly);
+
+builder.Services
+ .AddLogging()
+ .AddShopServices()
+ .AddHostedService()
+ .AddMidrandShopDatabase(builder.Configuration);
+
+builder.Services.Configure(options => builder.Configuration.GetSection(nameof(CdnSettings)).Bind(options));
+
+using var host = builder.Build();
+
+await host.RunAsync();
\ No newline at end of file
diff --git a/LiteCharms.Features.MidrandBooks.Seed/appsettings.json b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json
new file mode 100644
index 0000000..1dc7baa
--- /dev/null
+++ b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json
@@ -0,0 +1,28 @@
+{
+ "CdnSettings": {
+ "BaseCdn": "https://bookshop.cdn.khongisa.co.za/design/",
+ "BookCovers": [
+ "144e0314-0bd8-4e2c-8814-2b34608c0600_1764780116467.webp",
+ "2bf1f9a2-7b25-4fcf-9aa7-08941ea21e6c_1764838499686.webp",
+ "3cd172b9-416e-4b3a-b613-7ff0a3aecc4c_1764780129459.webp",
+ "762947b6-63ac-436e-98fb-91dd94de1d82_1764780126141.webp",
+ "7b42f23f-666c-4dd9-82e1-9b928e1b4db7_1764780186376.webp",
+ "8e281eea-8910-473d-b650-43f539995d9e_1764780152316.webp",
+ "91d18e2c-6ee6-44b4-84a5-8692db5dbfe4_1764780114484.webp",
+ "94fdf403-4d10-4cb4-a537-fc914a1f5ba8_1764780198423.webp",
+ "9b881786-75e1-47f7-9bc9-27188d46d1ec_1764780122458.webp",
+ "c417236d-a628-45eb-82c2-ec1cb0326e4f_1765006317965.webp",
+ "clpqjsgi71jpr1i6392e449l2.webp",
+ "clps1mk9f0m1z1i32grvq17tg.webp",
+ "clq550xxy2dab1ywb6mdv4acv.webp",
+ "clq5535o52dj51yw557ml49c1.webp",
+ "clq55455w2dcm1yvd8d8258d8.webp",
+ "clq558fkh2djy1ywbghwcawnh.webp",
+ "clq55cvqh2dqi1yyratl123wq.webp",
+ "clrhnk94e115k1y00bg8h9ixb.webp",
+ "d44a3c04-f124-4f0b-8301-3841ae2fd439_1764780121224.webp",
+ "e6ba52f208914285bcdf1966cfb08f6f.jpg",
+ "fa9cbbe6-f947-4f83-8e98-61d2661f43e0_1764841636705.webp"
+ ]
+ }
+}
diff --git a/LiteCharms.Features/Enums.cs b/LiteCharms.Features/Enums.cs
index 9cff19c..40aabdc 100644
--- a/LiteCharms.Features/Enums.cs
+++ b/LiteCharms.Features/Enums.cs
@@ -93,7 +93,8 @@ public enum SocialMediaTypes : int
YouTube = 5,
Pinterest = 6,
Reddit = 7,
- Tumblr = 8
+ Tumblr = 8,
+ GitHub = 9
}
public enum EmailStatuses : int
diff --git a/LiteCharmsShared.slnx b/LiteCharmsShared.slnx
index 1945799..be7cb5c 100644
--- a/LiteCharmsShared.slnx
+++ b/LiteCharmsShared.slnx
@@ -9,6 +9,7 @@
+