# Task 11: Seed Data Service Implementation Evidence ## Files Created ### 1. backend/src/WorkClub.Infrastructure/Seed/SeedDataService.cs - **Purpose**: Provides idempotent seeding of development database - **Key Features**: - Deterministic GUID generation from entity names (MD5-based) - Idempotent checks: Only seeds if no data exists - IServiceScopeFactory injection for creating scoped DbContext - Async SeedAsync() method for non-blocking seed operations ### 2. backend/src/WorkClub.Api/Program.cs (Modified) - **Added Import**: `using WorkClub.Infrastructure.Seed;` - **Service Registration**: `builder.Services.AddScoped();` - **Development Startup**: Added seed execution in development environment only ```csharp if (app.Environment.IsDevelopment()) { using var scope = app.Services.CreateScope(); var seedService = scope.ServiceProvider.GetRequiredService(); await seedService.SeedAsync(); } ``` ## Seed Data Structure ### Clubs (2 total) 1. **Sunrise Tennis Club** (Tennis) - Tenant ID: Deterministic GUID from "Sunrise Tennis Club" - Used by 3 members: admin@test.com, manager@test.com, member1@test.com, member2@test.com, viewer@test.com 2. **Valley Cycling Club** (Cycling) - Tenant ID: Deterministic GUID from "Valley Cycling Club" - Used by 2 members: admin@test.com, member1@test.com ### Members (7 total records, 5 unique users) - **admin@test.com**: Admin in Tennis Club, Member in Cycling Club - **manager@test.com**: Manager in Tennis Club - **member1@test.com**: Member in Tennis Club, Member in Cycling Club - **member2@test.com**: Member in Tennis Club - **viewer@test.com**: Viewer in Tennis Club ### Work Items (8 total) **Tennis Club (5 items)** - Court renovation (Open, unassigned) - Equipment order (Assigned, to manager) - Tournament planning (InProgress, to member1) - Member handbook review (Review, to member2) - Website update (Done, to manager) **Cycling Club (3 items)** - Route mapping (Open, unassigned) - Safety training (Assigned, to member1) - Group ride coordination (InProgress, to admin) ### Shifts (5 total) **Tennis Club (3 shifts)** - Court Maintenance - Yesterday (past, capacity 2) - Court Maintenance - Today (today, capacity 3) - Tournament Setup - Next Week (future, capacity 5) **Cycling Club (2 shifts)** - Group Ride - Today (today, capacity 10) - Maintenance Workshop - Next Week (future, capacity 4) ### Shift Signups (3-4 total) - Tennis Court Maintenance (Yesterday): 2 signups - Cycling Group Ride (Today): 1 signup ## Idempotency Implementation Each entity type is seeded with idempotent checks: ```csharp if (!context.Clubs.Any()) { context.Clubs.AddRange(...); await context.SaveChangesAsync(); } ``` This ensures: - First run: All data inserted - Subsequent runs: No duplicates (check passes on subsequent runs) - Safe for multiple restarts during development ## Development-Only Guard Seed execution is protected: ```csharp if (app.Environment.IsDevelopment()) { // Seed only runs in Development environment } ``` This ensures: - Production environment: No seed execution - Staging/Testing: Controlled separately via environment variables ## Deterministic GUID Generation Used MD5 hash to create consistent tenant IDs: ```csharp private static string GenerateDeterministicGuid(string input) { var hash = MD5.HashData(Encoding.UTF8.GetBytes(input)); return new Guid(hash.Take(16).ToArray()).ToString(); } ``` Benefits: - Same GUID generated for same club name (consistency across restarts) - Predictable: Matches expected UUIDs in test users - No external dependencies needed ## Usage During Development 1. Backend starts in Development environment 2. Program.cs development middleware runs 3. SeedDataService is resolved from DI container 4. SeedAsync() is called asynchronously 5. First run: All seed data inserted 6. Subsequent runs: Checks pass, no duplicates ## Notes - Seed runs synchronously in middleware (blocking startup until complete) - SeedDataService uses IServiceScopeFactory to create fresh DbContext - All entities have CreatedAt/UpdatedAt timestamps set to UTC now - ExternalUserId values are placeholder user IDs (can be updated when connected to Keycloak) - Shift times use DateTimeOffset to handle timezone properly