From 50eee03dbec0a60ad47d732841d4988dbe4a52b3 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Fri, 29 May 2026 23:02:06 +0200 Subject: [PATCH 1/2] Product seeding completed --- .../Configuration/CdnSettings.cs | 6 + .../ProductsSeederService.cs | 68 ++++- .../appsettings.json | 237 +++++++++++++++++- 3 files changed, 302 insertions(+), 9 deletions(-) diff --git a/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs b/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs index af16232..6eb1107 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs +++ b/LiteCharms.Features.MidrandBooks.Seed/Configuration/CdnSettings.cs @@ -5,4 +5,10 @@ public class CdnSettings public string? BaseCdn { get; set; } public string[]? BookCovers { get; set; } + + public string[]? Authors { get; set; } + + public string[]? AuthorThumbnails { get; set; } + + public string[]? BookThumbnails { get; set; } } diff --git a/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs index a506bf3..9dfc582 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs +++ b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs @@ -65,8 +65,19 @@ public class ProductsSeederService(ProductService productService, AuthorService "Unlocking Creative Flow Under Pressure" ); - // Defensive Length Processing to avoid Entity Framework / Postgres string truncation crashes - var rawTitle = $"{faker.Company.CatchPhrase()} with {bookTopic}"; + // Dynamic raw title generation formulas executed via random function picker + var titlePatterns = new Func[] + { + () => $"{faker.Company.CatchPhrase()} with {bookTopic}", + () => $"The {faker.Commerce.ProductAdjective()} Guide to {bookTopic}", + () => $"Mastering {bookTopic}: A {faker.Company.Bs()} Blueprint", + () => $"{bookTopic} for the Modern {faker.Name.JobTitle()}", + () => $"Advanced {bookTopic}: Demystifying the {faker.Company.CatchPhrase()}", + () => $"{faker.Random.Replace("###")} Blueprints for {bookTopic}" + }; + + // Pick a format template and resolve it down to raw string text + var rawTitle = faker.PickRandom(titlePatterns)(); 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."; @@ -80,6 +91,21 @@ public class ProductsSeederService(ProductService productService, AuthorService var authorLastName = faker.Name.LastName(); var publisherCompany = faker.Company.CompanyName(); + // Safe bounded random picking for book thumbnails + string? pickedBookThumbnail = null; + string? pickedBookThumbnail1 = null; + string? pickedBookThumbnail2 = null; + string? pickedBookThumbnail3 = null; + string? pickedBookThumbnail4 = null; + if (cdnSettings.BookThumbnails is not null && cdnSettings.BookThumbnails.Length > 0) + { + pickedBookThumbnail = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; + pickedBookThumbnail1 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; + pickedBookThumbnail2 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; + pickedBookThumbnail3 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; + pickedBookThumbnail4 = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.BookThumbnails)}"; + } + // Step 1: Add Product var productCreateResult = await productService.CreateProductAsync(new Products.Models.CreateProduct { @@ -95,7 +121,8 @@ public class ProductsSeederService(ProductService productService, AuthorService Manufacturer = $"{authorFirstName} {authorLastName} / {publisherCompany}", SerialNumber = faker.Phone.PhoneNumber("978-##########") }, - Categories = ["Coding", "Computers", "IT"] + Categories = ["Coding", "Computers", "IT"], + ThumbnailUrls = pickedBookThumbnail is not null ? [pickedBookThumbnail, pickedBookThumbnail1!, pickedBookThumbnail2!, pickedBookThumbnail3!, pickedBookThumbnail4!] : null }, stoppingToken); if (productCreateResult.IsFailed) @@ -116,7 +143,6 @@ public class ProductsSeederService(ProductService productService, AuthorService // 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); @@ -127,6 +153,34 @@ public class ProductsSeederService(ProductService productService, AuthorService break; } + // Safe bounded picking for Authors (Real Avatars) + string authorAvatarUrl = faker.Internet.Avatar(); // Fallback + if (cdnSettings.Authors is not null && cdnSettings.Authors.Length > 0) + { + authorAvatarUrl = $"{cdnSettings.BaseCdn}{faker.PickRandom(cdnSettings.Authors)}"; + } + + // Safe bounded picking for Author Thumbnails (Cartoon Avatars) + string? authorThumbnailUrl = null; + if (cdnSettings.AuthorThumbnails is not null && cdnSettings.AuthorThumbnails.Length > 0) + { + var selectedThumb = faker.PickRandom(cdnSettings.AuthorThumbnails); + authorThumbnailUrl = $"{cdnSettings.BaseCdn}{selectedThumb}.jpg"; + } + + // Synthesize a highly dynamic, organic opening bio statement + var professionalBackgrounds = new[] + { + $"{authorFirstName} {authorLastName} is an award-winning {faker.Name.JobDescriptor()} {faker.Name.JobTitle()} with over {faker.Random.Number(5, 25)} years of core engineering domain expertise.", + $"As a veteran systems consultant and practicing {faker.Name.JobTitle()}, {authorFirstName} has spent decades leading digital infrastructure transformations and managing complex topologies.", + $"Operating from modern innovation hubs, {authorFirstName} {authorLastName} specializes in global product strategies and serves as an authority in {faker.Name.JobDescriptor()} computing.", + $"With a rich professional background as a principal {faker.Name.JobTitle()} at {publisherCompany}, {authorFirstName} has spent a lifetime refining the system workflows highlighted here." + }; + + // Pick a randomized context hook and append a 2-paragraph contextual narrative block + var biographyPrefix = faker.PickRandom(professionalBackgrounds); + var authorBiography = $"{biographyPrefix} {faker.Lorem.Paragraph(2)}"; + // Step 4: Create Author var authorCreateResult = await authorService.CreateAuthorAsync(request: new Authors.Models.CreateAuthor { @@ -137,7 +191,7 @@ public class ProductsSeederService(ProductService productService, AuthorService PublisherType = faker.PickRandom(), Email = faker.Internet.Email(authorFirstName, authorLastName), Website = faker.Internet.Url(), - ImageUrl = faker.Internet.Avatar(), + ImageUrl = authorAvatarUrl, SocialMedia = [ new Models.SocialMedia @@ -155,8 +209,8 @@ public class ProductsSeederService(ProductService productService, AuthorService 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 + Biography = authorBiography, + ThumbnailImageUrl = authorThumbnailUrl }, stoppingToken); if (authorCreateResult.IsFailed) diff --git a/LiteCharms.Features.MidrandBooks.Seed/appsettings.json b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json index 1dc7baa..ce89459 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/appsettings.json +++ b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json @@ -23,6 +23,239 @@ "d44a3c04-f124-4f0b-8301-3841ae2fd439_1764780121224.webp", "e6ba52f208914285bcdf1966cfb08f6f.jpg", "fa9cbbe6-f947-4f83-8e98-61d2661f43e0_1764841636705.webp" - ] + ], + "Authors": [ + "authors/uifaces-human-avatar.jpg", + "authors/uifaces-human-avatar-1.jpg", + "authors/uifaces-human-avatar-2.jpg", + "authors/uifaces-human-avatar-3.jpg", + "authors/uifaces-human-avatar-4.jpg", + "authors/uifaces-human-avatar-5.jpg", + "authors/uifaces-human-avatar-6.jpg", + "authors/uifaces-human-avatar-7.jpg", + "authors/uifaces-human-avatar-8.jpg", + "authors/uifaces-human-avatar-9.jpg", + "authors/uifaces-human-avatar-10.jpg", + "authors/uifaces-human-avatar-11.jpg", + "authors/uifaces-human-avatar-12.jpg", + "authors/uifaces-human-avatar-13.jpg", + "authors/uifaces-human-avatar-14.jpg", + "authors/uifaces-human-avatar-15.jpg", + "authors/uifaces-human-avatar-16.jpg" + ], + "AuthorThumbnails": [ + "authors/thumbnails/uifaces-cartoon-avatar-1", + "authors/thumbnails/uifaces-cartoon-avatar-2", + "authors/thumbnails/uifaces-cartoon-avatar-3", + "authors/thumbnails/uifaces-cartoon-avatar-4", + "authors/thumbnails/uifaces-cartoon-avatar-5", + "authors/thumbnails/uifaces-cartoon-avatar-6", + "authors/thumbnails/uifaces-cartoon-avatar-7", + "authors/thumbnails/uifaces-cartoon-avatar-8", + "authors/thumbnails/uifaces-cartoon-avatar-9", + "authors/thumbnails/uifaces-cartoon-avatar-10" + ], + "BookThumbnails": [ + "thumbnails/book_thumbnail_001.jpg", + "thumbnails/book_thumbnail_002.jpg", + "thumbnails/book_thumbnail_003.jpg", + "thumbnails/book_thumbnail_004.jpg", + "thumbnails/book_thumbnail_005.jpg", + "thumbnails/book_thumbnail_006.jpg", + "thumbnails/book_thumbnail_007.jpg", + "thumbnails/book_thumbnail_008.jpg", + "thumbnails/book_thumbnail_009.jpg", + "thumbnails/book_thumbnail_010.jpg", + "thumbnails/book_thumbnail_011.jpg", + "thumbnails/book_thumbnail_012.jpg", + "thumbnails/book_thumbnail_013.jpg", + "thumbnails/book_thumbnail_014.jpg", + "thumbnails/book_thumbnail_015.jpg", + "thumbnails/book_thumbnail_016.jpg", + "thumbnails/book_thumbnail_017.jpg", + "thumbnails/book_thumbnail_018.jpg", + "thumbnails/book_thumbnail_019.jpg", + "thumbnails/book_thumbnail_020.jpg", + "thumbnails/book_thumbnail_021.jpg", + "thumbnails/book_thumbnail_022.jpg", + "thumbnails/book_thumbnail_023.jpg", + "thumbnails/book_thumbnail_024.jpg", + "thumbnails/book_thumbnail_025.jpg", + "thumbnails/book_thumbnail_026.jpg", + "thumbnails/book_thumbnail_027.jpg", + "thumbnails/book_thumbnail_028.jpg", + "thumbnails/book_thumbnail_029.jpg", + "thumbnails/book_thumbnail_030.jpg", + "thumbnails/book_thumbnail_031.jpg", + "thumbnails/book_thumbnail_032.jpg", + "thumbnails/book_thumbnail_033.jpg", + "thumbnails/book_thumbnail_034.jpg", + "thumbnails/book_thumbnail_035.jpg", + "thumbnails/book_thumbnail_036.jpg", + "thumbnails/book_thumbnail_037.jpg", + "thumbnails/book_thumbnail_038.jpg", + "thumbnails/book_thumbnail_039.jpg", + "thumbnails/book_thumbnail_040.jpg", + "thumbnails/book_thumbnail_041.jpg", + "thumbnails/book_thumbnail_042.jpg", + "thumbnails/book_thumbnail_043.jpg", + "thumbnails/book_thumbnail_044.jpg", + "thumbnails/book_thumbnail_045.jpg", + "thumbnails/book_thumbnail_046.jpg", + "thumbnails/book_thumbnail_047.jpg", + "thumbnails/book_thumbnail_048.jpg", + "thumbnails/book_thumbnail_049.jpg", + "thumbnails/book_thumbnail_050.jpg", + "thumbnails/book_thumbnail_051.jpg", + "thumbnails/book_thumbnail_052.jpg", + "thumbnails/book_thumbnail_053.jpg", + "thumbnails/book_thumbnail_054.jpg", + "thumbnails/book_thumbnail_055.jpg", + "thumbnails/book_thumbnail_056.jpg", + "thumbnails/book_thumbnail_057.jpg", + "thumbnails/book_thumbnail_058.jpg", + "thumbnails/book_thumbnail_059.jpg", + "thumbnails/book_thumbnail_060.jpg", + "thumbnails/book_thumbnail_061.jpg", + "thumbnails/book_thumbnail_062.jpg", + "thumbnails/book_thumbnail_063.jpg", + "thumbnails/book_thumbnail_064.jpg", + "thumbnails/book_thumbnail_065.jpg", + "thumbnails/book_thumbnail_066.jpg", + "thumbnails/book_thumbnail_067.jpg", + "thumbnails/book_thumbnail_068.jpg", + "thumbnails/book_thumbnail_069.jpg", + "thumbnails/book_thumbnail_070.jpg", + "thumbnails/book_thumbnail_071.jpg", + "thumbnails/book_thumbnail_072.jpg", + "thumbnails/book_thumbnail_073.jpg", + "thumbnails/book_thumbnail_074.jpg", + "thumbnails/book_thumbnail_075.jpg", + "thumbnails/book_thumbnail_076.jpg", + "thumbnails/book_thumbnail_077.jpg", + "thumbnails/book_thumbnail_078.jpg", + "thumbnails/book_thumbnail_079.jpg", + "thumbnails/book_thumbnail_080.jpg", + "thumbnails/book_thumbnail_081.jpg", + "thumbnails/book_thumbnail_082.jpg", + "thumbnails/book_thumbnail_083.jpg", + "thumbnails/book_thumbnail_084.jpg", + "thumbnails/book_thumbnail_085.jpg", + "thumbnails/book_thumbnail_086.jpg", + "thumbnails/book_thumbnail_087.jpg", + "thumbnails/book_thumbnail_088.jpg", + "thumbnails/book_thumbnail_089.jpg", + "thumbnails/book_thumbnail_090.jpg", + "thumbnails/book_thumbnail_091.jpg", + "thumbnails/book_thumbnail_092.jpg", + "thumbnails/book_thumbnail_093.jpg", + "thumbnails/book_thumbnail_094.jpg", + "thumbnails/book_thumbnail_095.jpg", + "thumbnails/book_thumbnail_096.jpg", + "thumbnails/book_thumbnail_097.jpg", + "thumbnails/book_thumbnail_098.jpg", + "thumbnails/book_thumbnail_099.jpg", + "thumbnails/book_thumbnail_100.jpg", + "thumbnails/book_thumbnail_101.jpg", + "thumbnails/book_thumbnail_102.jpg", + "thumbnails/book_thumbnail_103.jpg", + "thumbnails/book_thumbnail_104.jpg", + "thumbnails/book_thumbnail_105.jpg", + "thumbnails/book_thumbnail_106.jpg", + "thumbnails/book_thumbnail_107.jpg", + "thumbnails/book_thumbnail_108.jpg", + "thumbnails/book_thumbnail_109.jpg", + "thumbnails/book_thumbnail_110.jpg", + "thumbnails/book_thumbnail_111.jpg", + "thumbnails/book_thumbnail_112.jpg", + "thumbnails/book_thumbnail_113.jpg", + "thumbnails/book_thumbnail_114.jpg", + "thumbnails/book_thumbnail_115.jpg", + "thumbnails/book_thumbnail_116.jpg", + "thumbnails/book_thumbnail_117.jpg", + "thumbnails/book_thumbnail_118.jpg", + "thumbnails/book_thumbnail_119.jpg", + "thumbnails/book_thumbnail_120.jpg", + "thumbnails/book_thumbnail_121.jpg", + "thumbnails/book_thumbnail_122.jpg", + "thumbnails/book_thumbnail_123.jpg", + "thumbnails/book_thumbnail_124.jpg", + "thumbnails/book_thumbnail_125.jpg", + "thumbnails/book_thumbnail_126.jpg", + "thumbnails/book_thumbnail_127.jpg", + "thumbnails/book_thumbnail_128.jpg", + "thumbnails/book_thumbnail_129.jpg", + "thumbnails/book_thumbnail_130.jpg", + "thumbnails/book_thumbnail_131.jpg", + "thumbnails/book_thumbnail_132.jpg", + "thumbnails/book_thumbnail_133.jpg", + "thumbnails/book_thumbnail_134.jpg", + "thumbnails/book_thumbnail_135.jpg", + "thumbnails/book_thumbnail_136.jpg", + "thumbnails/book_thumbnail_137.jpg", + "thumbnails/book_thumbnail_138.jpg", + "thumbnails/book_thumbnail_139.jpg", + "thumbnails/book_thumbnail_140.jpg", + "thumbnails/book_thumbnail_141.jpg", + "thumbnails/book_thumbnail_142.jpg", + "thumbnails/book_thumbnail_143.jpg", + "thumbnails/book_thumbnail_144.jpg", + "thumbnails/book_thumbnail_145.jpg", + "thumbnails/book_thumbnail_146.jpg", + "thumbnails/book_thumbnail_147.jpg", + "thumbnails/book_thumbnail_148.jpg", + "thumbnails/book_thumbnail_149.jpg", + "thumbnails/book_thumbnail_150.jpg", + "thumbnails/book_thumbnail_151.jpg", + "thumbnails/book_thumbnail_152.jpg", + "thumbnails/book_thumbnail_153.jpg", + "thumbnails/book_thumbnail_154.jpg", + "thumbnails/book_thumbnail_155.jpg", + "thumbnails/book_thumbnail_156.jpg", + "thumbnails/book_thumbnail_157.jpg", + "thumbnails/book_thumbnail_158.jpg", + "thumbnails/book_thumbnail_159.jpg", + "thumbnails/book_thumbnail_160.jpg", + "thumbnails/book_thumbnail_161.jpg", + "thumbnails/book_thumbnail_162.jpg", + "thumbnails/book_thumbnail_163.jpg", + "thumbnails/book_thumbnail_164.jpg", + "thumbnails/book_thumbnail_165.jpg", + "thumbnails/book_thumbnail_166.jpg", + "thumbnails/book_thumbnail_167.jpg", + "thumbnails/book_thumbnail_168.jpg", + "thumbnails/book_thumbnail_169.jpg", + "thumbnails/book_thumbnail_170.jpg", + "thumbnails/book_thumbnail_171.jpg", + "thumbnails/book_thumbnail_172.jpg", + "thumbnails/book_thumbnail_173.jpg", + "thumbnails/book_thumbnail_174.jpg", + "thumbnails/book_thumbnail_175.jpg", + "thumbnails/book_thumbnail_176.jpg", + "thumbnails/book_thumbnail_177.jpg", + "thumbnails/book_thumbnail_178.jpg", + "thumbnails/book_thumbnail_179.jpg", + "thumbnails/book_thumbnail_180.jpg", + "thumbnails/book_thumbnail_181.jpg", + "thumbnails/book_thumbnail_182.jpg", + "thumbnails/book_thumbnail_183.jpg", + "thumbnails/book_thumbnail_184.jpg", + "thumbnails/book_thumbnail_185.jpg", + "thumbnails/book_thumbnail_186.jpg", + "thumbnails/book_thumbnail_187.jpg", + "thumbnails/book_thumbnail_188.jpg", + "thumbnails/book_thumbnail_189.jpg", + "thumbnails/book_thumbnail_190.jpg", + "thumbnails/book_thumbnail_191.jpg", + "thumbnails/book_thumbnail_192.jpg", + "thumbnails/book_thumbnail_193.jpg", + "thumbnails/book_thumbnail_194.jpg", + "thumbnails/book_thumbnail_195.jpg", + "thumbnails/book_thumbnail_196.jpg", + "thumbnails/book_thumbnail_197.jpg", + "thumbnails/book_thumbnail_198.jpg", + "thumbnails/book_thumbnail_199.jpg", + "thumbnails/book_thumbnail_200.jpg" + ] } -} +} \ No newline at end of file From 2db3b3d293a037d5ca3ebf2a26e4fc13f305f3b6 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sat, 30 May 2026 00:11:19 +0200 Subject: [PATCH 2/2] Added customer seeder with order data --- .../CustomerSeederService.cs | 275 ++++++++++++++++++ ...teCharms.Features.MidrandBooks.Seed.csproj | 10 +- .../ProductsSeederService.cs | 4 +- .../Program.cs | 9 +- .../appsettings.json | 6 +- 5 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 LiteCharms.Features.MidrandBooks.Seed/CustomerSeederService.cs diff --git a/LiteCharms.Features.MidrandBooks.Seed/CustomerSeederService.cs b/LiteCharms.Features.MidrandBooks.Seed/CustomerSeederService.cs new file mode 100644 index 0000000..6814a6d --- /dev/null +++ b/LiteCharms.Features.MidrandBooks.Seed/CustomerSeederService.cs @@ -0,0 +1,275 @@ +using LiteCharms.Features.MidrandBooks.Customers; +using LiteCharms.Features.MidrandBooks.Customers.Models; +using LiteCharms.Features.MidrandBooks.Orders; +using LiteCharms.Features.MidrandBooks.Orders.Models; + +namespace LiteCharms.Features.MidrandBooks.Seed; + +public class CustomerSeederService(CustomerService customerService, OrderService orderService, IFeatureManager features, + ILogger logger) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (!await features.IsEnabledAsync("CustomerSeederService")) return; + + logger.LogInformation("Customer Seeding started"); + + // 1: Add shipping providers (shippingProvider IDs will be created in sequence: 1, 2, 3) + await orderService.CreateShippingProviderAsync(new CreateShippingProvider(ShippingProviderTypes.FastWay, "FastWay Couriers", 39, "https://www.fastway.co.za/our-services/track-your-parcel"), stoppingToken); + await orderService.CreateShippingProviderAsync(new CreateShippingProvider(ShippingProviderTypes.DHL, "DHL Couriers", 60, "https://www.dhl.com/za-en/home/tracking.html"), stoppingToken); + await orderService.CreateShippingProviderAsync(new CreateShippingProvider(ShippingProviderTypes.PostNet, "Postnet Overnight Mail", 45, "https://www.postnet.co.za/tracker"), stoppingToken); + + // Initialize Bogus Faker engine + var faker = new Faker(); + var culture = CultureInfo.InvariantCulture; + + // Ensure repeatable datasets across executions + Randomizer.Seed = new Random(84); + + // South African Provinces array lookup helper + var southAfricanProvinces = new[] + { + "Gauteng", "Western Cape", "KwaZulu-Natal", "Eastern Cape", + "Free State", "Limpopo", "Mpumalanga", "North West", "Northern Cape" + }; + + // South African major towns matching geographic boundaries roughly + var southAfricanCities = new[] { "Midrand", "Johannesburg", "Pretoria", "Cape Town", "Durban", "Gqeberha", "Polokwane", "Nelspruit", "Bloemfontein" }; + + // Tracks sequential Address IDs added globally to the system across all loops + long addressSequenceCounter = 0; + + // 2: Create 15 customers with resources sequentially + for (int c = 0; c < 15; c++) + { + if (stoppingToken.IsCancellationRequested) break; + + // Determine if this specific iteration represents a Corporate Client or an Individual Consumer + bool isCompanyCustomer = faker.Random.Bool(0.4f); // 40% chance of seeding a corporate entity + + string customerFirstName = faker.Name.FirstName(); + string customerLastName = faker.Name.LastName(); + + string companyName = isCompanyCustomer ? faker.Company.CompanyName() : ""; + string companySuffix = isCompanyCustomer ? faker.Company.CompanySuffix() : ""; + string fullCompanyName = isCompanyCustomer ? $"{companyName} {companySuffix}" : ""; + + string customerEmail = isCompanyCustomer + ? faker.Internet.Email(firstName: companyName, provider: "co.za").ToLower(culture) + : faker.Internet.Email(customerFirstName, customerLastName).ToLower(culture); + + string customerPhone = faker.Phone.PhoneNumber("087#######"); // Corporate VOIP / Personal South African cell line format + string customerWebsite = isCompanyCustomer ? faker.Internet.Url().Replace("www.", $"www.{companyName.ToLower(culture)}.") : ""; + string customerVat = isCompanyCustomer ? faker.Phone.PhoneNumber("4#########") : ""; // SA VAT registration starts with a 4 + + // Randomly select distinct Social Media channels + var chosenSocialType = faker.PickRandom(); + string socialMediaUrl = chosenSocialType switch + { + SocialMediaTypes.LinkedIn => isCompanyCustomer ? $"https://linkedin.com/company/{companyName.ToLower(culture)}" : $"https://linkedin.com/in/{customerFirstName.ToLower(culture)}-{customerLastName.ToLower(culture)}", + SocialMediaTypes.GitHub => $"https://github.com/{(isCompanyCustomer ? "orgs/" + companyName.ToLower(culture) : customerFirstName.ToLower(culture))}", + _ => $"https://x.com/{(isCompanyCustomer ? companyName.ToLower(culture) : customerFirstName.ToLower(culture))}" + }; + + // 3: Create customer + var createCustomerResult = await customerService.CreateCustomerAsync(new CreateCustomer + { + Company = fullCompanyName, + Email = customerEmail, + Phone = customerPhone, + Website = customerWebsite, + SocialMedia = + [ + new Models.SocialMedia + { + Name = chosenSocialType.ToString(), + Type = chosenSocialType, + ImageUrl = $"https://cdn.example.com/icons/{chosenSocialType.ToString().ToLower(culture)}.png", + Url = socialMediaUrl + } + ], + VatNumber = customerVat + }, stoppingToken); + + if (createCustomerResult.IsFailed) + { + logger.LogError("Failed to create customer record at index {Index}: {Error}", c, createCustomerResult.Errors[0].Message); + break; + } + + var assignedCustomerId = createCustomerResult.Value; + + // 4: Create customer contact (only if customer is a company entity) + if (isCompanyCustomer) + { + var contactFirstName = faker.Name.FirstName(); + var contactLastName = faker.Name.LastName(); + + var createContactResult = await customerService.CreateCustomerContactAsync(assignedCustomerId, new CreateCustomerContact + { + Name = contactFirstName, + LastName = contactLastName, + Phone = faker.Phone.PhoneNumber("082#######"), // Typical South African mobile prefix format + Email = faker.Internet.Email(contactFirstName, contactLastName, provider: "company.co.za").ToLower(culture), + Type = ContactTypes.Business + }, stoppingToken); + + if (createContactResult.IsFailed) + { + logger.LogError("Failed to create company customer contact relation: {Error}", createContactResult.Errors[0].Message); + break; + } + } + + // Shared Randomizations for Regional Postal/Building details + var primaryState = faker.PickRandom(southAfricanProvinces); + var primaryCity = faker.PickRandom(southAfricanCities); + var shippingPostalCode = faker.Random.Replace("####"); + + var billingState = faker.PickRandom(southAfricanProvinces); + var billingCity = faker.PickRandom(southAfricanCities); + var billingPostalCode = faker.Random.Replace("####"); + + // 5: Create customer address - SHIPPING + var createShippingAddressResult = await customerService.CreateCustomerAddressAsync(assignedCustomerId, new CreateCustomerAddress + { + Name = isCompanyCustomer ? "Head Office Distribution" : "My Home Residence", + BuildingType = faker.PickRandom(), + Type = AddressType.Shipping, + Street = $"{faker.Address.BuildingNumber()} {faker.Address.StreetName()} Street", + City = primaryCity, + State = primaryState, + Country = "South Africa", + IsPrimary = true, + Enabled = true, + PostalCode = shippingPostalCode + }, stoppingToken); + + long currentCustomerShippingAddressId = 0; + if (createShippingAddressResult.IsSuccess) + { + addressSequenceCounter++; + currentCustomerShippingAddressId = addressSequenceCounter; + } + else + { + logger.LogWarning("Failed to attach Shipping address profile: {Error}", createShippingAddressResult.Errors[0].Message); + } + + // 6: Create customer address - BILLING + var createBillingAddressResult = await customerService.CreateCustomerAddressAsync(assignedCustomerId, new CreateCustomerAddress + { + Name = isCompanyCustomer ? "Accounts Payable Department" : "Billing Address", + BuildingType = faker.PickRandom(), + Type = AddressType.Billing, + Street = isCompanyCustomer ? $"{faker.Address.BuildingNumber()} {faker.Address.StreetName()} Boulevard" : $"{faker.Address.BuildingNumber()} {faker.Address.StreetName()} Street", + City = billingCity, + State = billingState, + Country = "South Africa", + IsPrimary = false, + Enabled = true, + PostalCode = billingPostalCode + }, stoppingToken); + + long currentCustomerBillingAddressId = 0; + if (createBillingAddressResult.IsSuccess) + { + addressSequenceCounter++; + currentCustomerBillingAddressId = addressSequenceCounter; + } + else + { + logger.LogError("Failed to attach Billing address profile: {Error}", createBillingAddressResult.Errors[0].Message); + break; + } + + // 7: Challenge Extrapolation — Create a random number of orders (0 to 4 orders) per customer + int ordersToGenerate = faker.Random.Number(0, 4); + for (int o = 0; o < ordersToGenerate; o++) + { + var deliveryInstructions = faker.PickRandom( + "Leave at reception desk", + "Please call before delivery", + "At the intercom, dial 1 then option 2", + "Leave with security guard at front gate", + "Deliver to back delivery bay" + ); + + // Use the calculated sequential Billing Address Id for order creation + var orderResult = await orderService.CreateOrderAsync( + assignedCustomerId, + new CreateOrder(currentCustomerBillingAddressId, deliveryInstructions), + stoppingToken + ); + + if (orderResult.IsFailed) + { + logger.LogWarning("Failed to create purchase order shell context: {Error}", orderResult.Errors[0].Message); + continue; + } + + long seededOrderId = orderResult.Value; + + // Build a varying array of items using valid product bounds (IDs: 0 to 21) + int lineItemsCount = faker.Random.Number(1, 5); + var itemsList = new List(); + + for (int i = 0; i < lineItemsCount; i++) + { + long randomProductId = faker.Random.Number(0, 21); + long randomProductPriceId = faker.Random.Number(0, 21); + int itemQuantity = faker.Random.Number(1, 3); + + itemsList.Add(new CreateOrderItem(randomProductId, randomProductPriceId, itemQuantity)); + } + + // Push bulk items payload into order via matching test framework signatures + var addItemsResult = await orderService.AddItemsToOrderAsync(seededOrderId, [.. itemsList], stoppingToken); + if (addItemsResult.IsFailed) + { + logger.LogWarning("Failed to link item collections to Order Id {Id}", seededOrderId); + continue; + } + + // Randomly select an order status matrix pathing + var targetedOrderStatus = faker.PickRandom(); + await orderService.UpdateOrderStatusAsync(seededOrderId, targetedOrderStatus, stoppingToken); + + // Check lifecycle workflow criteria: Attach dynamic shipping if status warrants it + if (targetedOrderStatus != OrderStatus.Pending && + targetedOrderStatus != OrderStatus.Cancelled && + targetedOrderStatus != OrderStatus.Failed && + currentCustomerShippingAddressId > 0) + { + // Select from seeded Shipping Providers in step 1 (IDs: 1, 2, or 3) + long randomShippingProviderId = faker.Random.Number(1, 3); + + var addShippingResult = await orderService.AddShippingToOrderAsync( + seededOrderId, + new CreateShipping(currentCustomerShippingAddressId, randomShippingProviderId), + stoppingToken + ); + + if (addShippingResult.IsSuccess) + { + long assignedShippingId = addShippingResult.Value; + + // Transition logistics flags matching delivery metrics + var shippingStatus = faker.PickRandom(); + await orderService.UpdateShippingStatusAsync(seededOrderId, shippingStatus, stoppingToken); + + if (shippingStatus == ShippingStatuses.Shipped || shippingStatus == ShippingStatuses.Delivered) + { + string rawTrackingCode = $"ZA{faker.Random.Replace("#########")}NV"; + await orderService.UpdateShippingTrackingNumberAsync(seededOrderId, assignedShippingId, rawTrackingCode); + } + } + } + } + + logger.LogInformation("Successfully seeded customer profile #{Index}: {Name} alongside {Count} orders.", c, isCompanyCustomer ? fullCompanyName : $"{customerFirstName} {customerLastName}", ordersToGenerate); + } + + logger.LogInformation("Customer Seeding completed successfully."); + } +} \ No newline at end of file diff --git a/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj b/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj index 14e2ffd..b9be4ff 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj +++ b/LiteCharms.Features.MidrandBooks.Seed/LiteCharms.Features.MidrandBooks.Seed.csproj @@ -11,10 +11,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -84,7 +85,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -95,8 +96,8 @@ - - + + @@ -128,6 +129,7 @@ + diff --git a/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs index 9dfc582..868a454 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs +++ b/LiteCharms.Features.MidrandBooks.Seed/ProductsSeederService.cs @@ -6,12 +6,14 @@ 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 + IFeatureManager features, IOptions options, ILogger logger) : BackgroundService { private readonly CdnSettings cdnSettings = options.Value; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + if (await features.IsEnabledAsync("ProductsSeederService") is not true) return; + logger.LogInformation("Product Seeding started"); if (cdnSettings.BookCovers is null || cdnSettings.BookCovers.Length == 0) diff --git a/LiteCharms.Features.MidrandBooks.Seed/Program.cs b/LiteCharms.Features.MidrandBooks.Seed/Program.cs index f31cdd3..01b4fbf 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/Program.cs +++ b/LiteCharms.Features.MidrandBooks.Seed/Program.cs @@ -5,13 +5,18 @@ using LiteCharms.Features.MidrandBooks.Seed.Configuration; var builder = Host.CreateApplicationBuilder(args); builder.Configuration - .AddJsonFile("appsettings.json") - .AddUserSecrets(typeof(Program).Assembly); + .AddCommandLine(args) + .AddUserSecrets(typeof(Program).Assembly) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables(); + +builder.Services.AddScopedFeatureManagement(); builder.Services .AddLogging() .AddShopServices() .AddHostedService() + .AddHostedService() .AddMidrandShopDatabase(builder.Configuration); builder.Services.Configure(options => builder.Configuration.GetSection(nameof(CdnSettings)).Bind(options)); diff --git a/LiteCharms.Features.MidrandBooks.Seed/appsettings.json b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json index ce89459..b710f54 100644 --- a/LiteCharms.Features.MidrandBooks.Seed/appsettings.json +++ b/LiteCharms.Features.MidrandBooks.Seed/appsettings.json @@ -1,4 +1,8 @@ { + "FeatureManagement": { + "CustomerSeederService": false, + "ProductsSeederService": false + }, "CdnSettings": { "BaseCdn": "https://bookshop.cdn.khongisa.co.za/design/", "BookCovers": [ @@ -257,5 +261,5 @@ "thumbnails/book_thumbnail_199.jpg", "thumbnails/book_thumbnail_200.jpg" ] - } + } } \ No newline at end of file