fix(demo): scope category/item IDs per café to prevent PK collisions
CI/CD / CI · API (dotnet build + test) (push) Successful in 56s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 52s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 43s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Successful in 1m31s
CI/CD / CI · API (dotnet build + test) (push) Successful in 56s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 52s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m3s
CI/CD / CI · Admin Web (tsc) (push) Successful in 35s
CI/CD / CI · Website (tsc) (push) Successful in 43s
CI/CD / CI · Koja (tsc) (push) Successful in 48s
CI/CD / Deploy · all services (push) Successful in 1m31s
DemoMenuSeeder used hardcoded IDs like cat_demo_coffee for every café. If the dev seeder (runs when ASPNETCORE_ENVIRONMENT=Development) already inserted those IDs for cafe_demo_001, a production café clicking "Add demo data" hit a primary-key constraint violation. Fix: EnsureMenuAsync now accepts useScopedIds=true which prefixes every category and item ID with cafeId (e.g. cafe_abc_cat_demo_coffee). CategoryId FKs on items are remapped through the same function. DemoSeedService (the API endpoint handler) always passes useScopedIds=true. DevelopmentDataSeeder keeps useScopedIds=false (default) so the existing cafe_demo_001 rows in dev databases are not touched. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,10 +56,12 @@ public class DemoSeedService : IDemoSeedService
|
|||||||
.FirstAsync(ct);
|
.FirstAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Seed menu (categories + items) using café-agnostic seeder
|
// 2. Seed menu (categories + items) using café-agnostic seeder.
|
||||||
|
// useScopedIds=true prefixes all IDs with cafeId so multiple cafés
|
||||||
|
// can each have their own demo menu without primary-key collisions.
|
||||||
var beforeCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
|
var beforeCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
|
||||||
var beforeItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
|
var beforeItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
|
||||||
await DemoMenuSeeder.EnsureMenuAsync(_db, cafeId, taxId, _logger);
|
await DemoMenuSeeder.EnsureMenuAsync(_db, cafeId, taxId, _logger, useScopedIds: true);
|
||||||
var afterCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
|
var afterCats = await _db.MenuCategories.CountAsync(c => c.CafeId == cafeId, ct);
|
||||||
var afterItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
|
var afterItems = await _db.MenuItems.CountAsync(i => i.CafeId == cafeId, ct);
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,23 @@ namespace Meezi.Infrastructure.Data;
|
|||||||
|
|
||||||
public static class DemoMenuSeeder
|
public static class DemoMenuSeeder
|
||||||
{
|
{
|
||||||
public static async Task EnsureMenuAsync(AppDbContext db, string cafeId, string taxId, ILogger logger)
|
/// <param name="useScopedIds">
|
||||||
|
/// When true, category and item IDs are prefixed with <paramref name="cafeId"/> so
|
||||||
|
/// multiple cafés can each have their own copy of the demo menu without a primary-key
|
||||||
|
/// collision. Pass false only for the legacy demo café (cafe_demo_001) whose IDs are
|
||||||
|
/// already in the database without a café prefix.
|
||||||
|
/// </param>
|
||||||
|
public static async Task EnsureMenuAsync(
|
||||||
|
AppDbContext db, string cafeId, string taxId, ILogger logger,
|
||||||
|
bool useScopedIds = false)
|
||||||
{
|
{
|
||||||
|
// When useScopedIds=true every row gets a deterministic ID that is unique per café:
|
||||||
|
// category → "{cafeId}_{catalogId}"
|
||||||
|
// item → "{cafeId}_{catalogId}"
|
||||||
|
// The catalog item's CategoryId is remapped through the same function.
|
||||||
|
string Scoped(string catalogId) =>
|
||||||
|
useScopedIds ? $"{cafeId}_{catalogId}" : catalogId;
|
||||||
|
|
||||||
if (!await db.Taxes.AnyAsync(t => t.Id == taxId && t.CafeId == cafeId))
|
if (!await db.Taxes.AnyAsync(t => t.Id == taxId && t.CafeId == cafeId))
|
||||||
{
|
{
|
||||||
db.Taxes.Add(new Tax
|
db.Taxes.Add(new Tax
|
||||||
@@ -29,7 +44,8 @@ public static class DemoMenuSeeder
|
|||||||
var categoriesAdded = 0;
|
var categoriesAdded = 0;
|
||||||
foreach (var cat in DemoMenuCatalog.Categories)
|
foreach (var cat in DemoMenuCatalog.Categories)
|
||||||
{
|
{
|
||||||
if (existingCategoryIds.TryGetValue(cat.Id, out var row))
|
var catId = Scoped(cat.Id);
|
||||||
|
if (existingCategoryIds.TryGetValue(catId, out var row))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(row.Icon) && !string.IsNullOrWhiteSpace(cat.Icon))
|
if (string.IsNullOrWhiteSpace(row.Icon) && !string.IsNullOrWhiteSpace(cat.Icon))
|
||||||
row.Icon = cat.Icon;
|
row.Icon = cat.Icon;
|
||||||
@@ -46,7 +62,7 @@ public static class DemoMenuSeeder
|
|||||||
|
|
||||||
db.MenuCategories.Add(new MenuCategory
|
db.MenuCategories.Add(new MenuCategory
|
||||||
{
|
{
|
||||||
Id = cat.Id,
|
Id = catId,
|
||||||
CafeId = cafeId,
|
CafeId = cafeId,
|
||||||
Name = cat.Name,
|
Name = cat.Name,
|
||||||
NameEn = cat.NameEn,
|
NameEn = cat.NameEn,
|
||||||
@@ -69,14 +85,15 @@ public static class DemoMenuSeeder
|
|||||||
var itemsAdded = 0;
|
var itemsAdded = 0;
|
||||||
foreach (var item in DemoMenuCatalog.Items)
|
foreach (var item in DemoMenuCatalog.Items)
|
||||||
{
|
{
|
||||||
if (existingItemIds.Contains(item.Id))
|
var itemId = Scoped(item.Id);
|
||||||
|
if (existingItemIds.Contains(itemId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
db.MenuItems.Add(new MenuItem
|
db.MenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Id = item.Id,
|
Id = itemId,
|
||||||
CafeId = cafeId,
|
CafeId = cafeId,
|
||||||
CategoryId = item.CategoryId,
|
CategoryId = Scoped(item.CategoryId), // FK must point at scoped category ID
|
||||||
Name = item.Name,
|
Name = item.Name,
|
||||||
NameEn = item.NameEn,
|
NameEn = item.NameEn,
|
||||||
NameAr = item.NameAr,
|
NameAr = item.NameAr,
|
||||||
|
|||||||
Reference in New Issue
Block a user