# Application Roles Seed Data Template

> Referenced from `core-seed-data.md` and `step-03-roles.md` — C# template for application-scoped roles in client projects.

---

## Problem Statement

When using `IClientSeedDataProvider` (client projects with `seeding_strategy = "provider"`), role-permission mappings reference roles by their `Code`:

```csharp
var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode);  // "admin", "manager", "contributor", "viewer"
```

**However**, the current templates do NOT create these application-scoped roles. They assume:
- System roles (SuperAdmin, PlatformAdmin, TenantAdmin, StandardUser) exist in Core
- Application-scoped roles (Admin, Manager, Contributor, Viewer) already exist with valid `Code` values

**Result:** Role-permission mappings fail silently when `role == null`.

---

## Solution: Application Roles Seed Data

Create application-scoped roles with random GUIDs (`Guid.NewGuid()`) and valid `Code` values.

---

## File Location

**Path:** `Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs`

This file should be created **ONCE per application** (not per module).

---

## Template

```csharp
using SmartStack.Domain.Platform.Administration.Roles;

namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;

/// <summary>
/// Application-scoped role seed data for {AppLabel}.
/// Defines the 4 standard application roles: Admin, Manager, Contributor, Viewer.
/// Consumed by IClientSeedDataProvider at application startup.
/// </summary>
public static class ApplicationRolesSeedData
{
    // Random GUIDs — idempotence is handled by Code-based lookups, not fixed IDs
    public static readonly Guid ApplicationId = NavigationApplicationSeedData.ApplicationId;

    public static readonly Guid AdminRoleId = Guid.NewGuid();
    public static readonly Guid ManagerRoleId = Guid.NewGuid();
    public static readonly Guid ContributorRoleId = Guid.NewGuid();
    public static readonly Guid ViewerRoleId = Guid.NewGuid();

    /// <summary>
    /// Returns application-scoped role entries for seeding into core.auth_Roles.
    /// </summary>
    public static IEnumerable<ApplicationRoleSeedEntry> GetRoleEntries()
    {
        yield return new ApplicationRoleSeedEntry
        {
            Id = AdminRoleId,
            Code = "admin",
            Name = "{AppLabel} Admin",
            Description = "Full administrative access to {AppLabel}",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 1
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ManagerRoleId,
            Code = "manager",
            Name = "{AppLabel} Manager",
            Description = "Management access to {AppLabel} (Create, Read, Update)",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 2
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ContributorRoleId,
            Code = "contributor",
            Name = "{AppLabel} Contributor",
            Description = "Contributor access to {AppLabel} (Create, Read)",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 3
        };

        yield return new ApplicationRoleSeedEntry
        {
            Id = ViewerRoleId,
            Code = "viewer",
            Name = "{AppLabel} Viewer",
            Description = "Read-only access to {AppLabel}",
            ApplicationId = ApplicationId,
            IsSystem = false,
            IsActive = true,
            DisplayOrder = 4
        };
    }

    // No deterministic GUID generation — all IDs are random via Guid.NewGuid()
    // Roles are resolved by Code at runtime, not by fixed GUIDs
}

/// <summary>Seed entry DTO for application role.</summary>
public class ApplicationRoleSeedEntry
{
    public Guid Id { get; init; }
    public string Code { get; init; } = null!;
    public string Name { get; init; } = null!;
    public string Description { get; init; } = null!;
    public Guid ApplicationId { get; init; }
    public bool IsSystem { get; init; }
    public bool IsActive { get; init; }
    public int DisplayOrder { get; init; }
}
```

---

## Placeholder Replacement

| Placeholder | Description | Example |
|-------------|-------------|---------|
| `{BaseNamespace}` | Root namespace of the client project | `SmartStack.Modules.RessourcesHumaines` |
| `{AppLabel}` | Human-readable application label (EN) | `Human Resources` |
| `ApplicationId` | From `NavigationApplicationSeedData.ApplicationId` — resolved at runtime via Code lookup | Auto-resolved |

---

## Integration into IClientSeedDataProvider

Add a new method `SeedRolesAsync()` to the provider:

```csharp
public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
{
    // Resolve application from DB by Code — NOT seed-time GUID
    var app = await context.NavigationApplications
        .FirstAsync(a => a.Code == "{app_code}", ct);

    // Check idempotence — scoped by actual DB ApplicationId
    var exists = await context.Roles
        .AnyAsync(r => r.ApplicationId == app.Id, ct);
    if (exists) return;

    // Create application-scoped roles using factory method
    foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
    {
        var role = Role.Create(
            name: entry.Name,
            shortName: entry.Code,
            category: RoleCategory.Application,
            description: entry.Description,
            isSystem: entry.IsSystem,
            applicationId: app.Id,
            code: entry.Code);

        context.Roles.Add(role);
    }

    await ((DbContext)context).SaveChangesAsync(ct);
}
```

---

## Execution Order in Provider

**CRITICAL:** Roles must be created BEFORE role-permission mappings.

```
1. SeedNavigationAsync()   → Creates application + modules + translations
2. SeedRolesAsync()         → Creates application-scoped roles (NEW)
3. SeedPermissionsAsync()   → Creates permissions
4. SeedRolePermissionsAsync() → Maps roles to permissions (now succeeds because roles exist)
```

---

## Verification Checklist

Before marking the task as completed, verify:

- [ ] `ApplicationRolesSeedData.cs` created in `Infrastructure/Persistence/Seeding/Data/`
- [ ] Random GUIDs used via `Guid.NewGuid()` (no deterministic/sequential/fixed values)
- [ ] 4 roles defined: Admin, Manager, Contributor, Viewer
- [ ] Each role has a valid `Code` value ("admin", "manager", "contributor", "viewer")
- [ ] Each role has `ApplicationId` set to the application GUID
- [ ] `SeedRolesAsync()` method added to `IClientSeedDataProvider`
- [ ] `SeedRolesAsync()` is idempotent (checks existence before inserting)
- [ ] `Role.Create()` factory method used (NEVER `new Role()`)
- [ ] `SaveChangesAsync()` called after role creation
- [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
- [ ] `dotnet build` passes after generation

---

## Notes

- **Application ID source:** Read from the navigation application created in `SeedNavigationAsync()` or from `{AppPascal}NavigationSeedData.cs`
- **Role factory method:** Use `Role.Create(name, shortName, category, description, isSystem, applicationId: ..., code: ...)` from SmartStack.Domain
- **Code uniqueness:** Role codes must be unique within the application scope
- **System roles:** These are NOT system roles (IsSystem = false) - they are application-scoped roles
- **Tenant isolation:** Application-scoped roles are automatically tenant-isolated via the Core authorization system

---

## Migration Impact

**For existing projects without application roles:**
1. Generate `ApplicationRolesSeedData.cs` using this template
2. Add `SeedRolesAsync()` method to the existing `IClientSeedDataProvider`
3. Update the provider's execution to call `SeedRolesAsync()` BEFORE `SeedRolePermissionsAsync()`
4. Run the application - roles will be created on next startup
5. Role-permission mappings will now succeed
