# IClientSeedDataProvider Template

> Referenced from `steps/step-03b-provider.md` — C# template for client project seed data providers.

---

## Provider Class Template

**Location:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`

Where `{AppPascalName}` is derived from the application code (e.g., `free-bike` → `FreeBike`).

```csharp
using Microsoft.EntityFrameworkCore;
using SmartStack.Application.Common.Interfaces;
using SmartStack.Domain.Navigation;
using SmartStack.Domain.Platform.Administration.Roles;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;

/// <summary>
/// Seeds {AppLabel} navigation, roles, permissions, and role-permission data
/// into the SmartStack Core schema at application startup.
/// Implements <see cref="IClientSeedDataProvider"/> for runtime seeding
/// (no Core migrations required).
/// </summary>
public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
{
    public int Order => 100;

    public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Check idempotence
        var exists = await context.NavigationApplications
            .AnyAsync(a => a.Code == "{app_code}", ct);
        if (exists) return;

        // 1. Retrieve the parent context ("business", "platform", etc.)
        var parentContext = await context.NavigationContexts
            .FirstAsync(c => c.Code == "{context_code}", ct);

        // 2. Create the application
        var app = NavigationApplication.Create(
            parentContext.Id,
            "{app_code}",
            "{app_label_en}",
            "{app_desc_en}",
            "{app_icon}",
            IconType.Lucide,
            "/{context_code}/{app_code}",
            {display_order});
        context.NavigationApplications.Add(app);
        await ((DbContext)context).SaveChangesAsync(ct);

        // 3. Create modules
        // Use GUIDs and data from {Module}NavigationSeedData.cs
        foreach (var moduleData in GetModuleSeedEntries(app.Id))
        {
            var module = NavigationModule.Create(
                moduleData.ApplicationId,
                moduleData.Code,
                moduleData.Label,
                moduleData.Description,
                moduleData.Icon,
                IconType.Lucide,
                moduleData.Route,
                moduleData.DisplayOrder);
            context.NavigationModules.Add(module);
        }
        await ((DbContext)context).SaveChangesAsync(ct);

        // 4. Create translations (4 languages per entity)
        // Use data from {Module}NavigationTranslationSeedData.cs
        // ...
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Check idempotence
        var applicationId = ApplicationRolesSeedData.ApplicationId;
        var exists = await context.Roles
            .AnyAsync(r => r.ApplicationId == applicationId, ct);
        if (exists) return;

        // Create application-scoped roles (Admin, Manager, Contributor, Viewer)
        // Use data from ApplicationRolesSeedData.cs
        foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
        {
            var role = Role.Create(
                entry.Code,
                entry.Name,
                entry.Description,
                entry.ApplicationId,
                entry.IsSystem);
            context.Roles.Add(role);
        }
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Check idempotence
        var exists = await context.Permissions
            .AnyAsync(p => p.Path == "{full_path}.*", ct);
        if (exists) return;

        // Retrieve modules by Code for FK resolution
        // Use data from {Module}PermissionSeedData.cs
        // Create via Permission.CreateForModule(...) and Permission.CreateWildcard(...)
        // ...
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
    {
        // Check idempotence
        var exists = await context.RolePermissions
            .AnyAsync(rp => rp.Permission!.Path.StartsWith("{full_path}."), ct);
        if (exists) return;

        // Retrieve existing roles and created permissions
        // Use data from {Module}RolePermissionSeedData.cs
        // Create via RolePermission.Create(roleId, permissionId, "system")
        // ...
        await ((DbContext)context).SaveChangesAsync(ct);
    }

    // Private helper methods fed by SeedData classes
    // ...
}
```

---

## DI Registration

Modify `Infrastructure/DependencyInjection.cs` of the client project:

```csharp
// Add using:
using SmartStack.Application.Common.Interfaces;

// In the registration method:
services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
```

---

## Critical Rules

1. **Factory methods mandatory**: `NavigationModule.Create(...)`, `Role.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()`
2. **Idempotence**: Each Seed method checks existence before inserting
3. **SaveChangesAsync per group**: Navigation → save → Roles → save → Permissions → save → RolePermissions → save
4. **Execution order**: `SeedRolesAsync()` MUST be called BEFORE `SeedRolePermissionsAsync()` (roles must exist before mapping)
5. **Deterministic GUIDs**: Use IDs from SeedData classes (not `Guid.NewGuid()`)
6. **Resolve FK by Code**: Parent modules and roles are found by `Code`, not hardcoded GUID
7. **Order property**: Use `100` as default. If multiple providers exist, they run in Order sequence
