> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Seed Data

> DevDataSeeder, DistrictSeeder, Fluent HasData ve prod davranışı

Başlangıç verisi üç ayrı kaynaktan gelir:

1. **`DevDataSeeder`** — config'ten okunan bootstrap admin'leri (hosted service).
2. **`DistrictSeeder`** — 973 ilçe, `SeedData/Districts.csv` dosyasından (hosted service).
3. **Fluent `HasData`** — Role, Permission ve RolePermission gibi referans verileri (migration ile kalıcı).

Üçü de `AddInfrastructureEFCore` içinde kaydedilir:

```csharp theme={null}
services.AddHostedService<MigrationService>();   // önce migrate
services.AddHostedService<DevDataSeeder>();      // bootstrap admin
services.AddHostedService<DistrictSeeder>();     // ilçeler
```

## DevDataSeeder — bootstrap admin

Yönetim paneli (admin realm'ı) **davet bazlı** çalışır: kullanıcı DB'de yoksa Keycloak login'i reddedilir. Yeni bir deployment'ın kilitlenmemesi için bu seeder en az bir `SuperAdmin`'i (ilk davet edeni) yazar.

Seed edilecek liste config'ten okunur:

```json theme={null}
{
  "SeedWork": {
    "BootstrapAdmins": [
      {
        "Email": "admin@example.com",
        "FullName": "İlk Yönetici",
        "Roles": [ "SuperAdmin" ]
      }
    ]
  }
}
```

Çekirdek mantık (idempotent, factory + role atama):

```csharp theme={null}
public async Task StartAsync(CancellationToken cancellationToken)
{
    var seeds = _configuration
        .GetSection("SeedWork:BootstrapAdmins")
        .Get<List<BootstrapAdminConfig>>() ?? new();

    if (seeds.Count == 0)
    {
        _logger.LogDebug("[Seeder] SeedWork:BootstrapAdmins boş — atlandı.");
        return;                                  // no-op — yeni deployment için güvenli default
    }

    await using var scope = _serviceProvider.CreateAsyncScope();
    var userRepo = scope.ServiceProvider.GetRequiredService<IRepository<User>>();
    var userFact = scope.ServiceProvider.GetRequiredService<IUserFactory>();
    var uow      = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();

    foreach (var seed in seeds)
    {
        var email    = new Email(seed.Email);
        var existing = await userRepo.FirstOrDefaultAsync(new UserByEmailSpecification(email), cancellationToken);
        if (existing is not null) continue;       // idempotent — zaten varsa atla

        var user = await userFact.CreateWithEmailAsync(new FullName(seed.FullName), email, cancellationToken);
        user.ActivateOnExternalLogin();           // Status = Active

        foreach (var roleName in seed.Roles ?? Array.Empty<string>())
        {
            var role = Enumeration.GetAll<Role>()
                .FirstOrDefault(r => string.Equals(r.Name, roleName, StringComparison.OrdinalIgnoreCase));
            if (role is null) continue;
            user.AssignRole(role.Id, tenantId: null, assignedBy: SystemUserId);
        }

        await userRepo.AddAsync(user, cancellationToken);
    }

    await uow.SaveEntitiesAsync(cancellationToken);
}
```

Davranış özeti:

* **Liste boşsa no-op.** Hiç kullanıcı yaratılmaz.
* **İdempotent.** Email zaten DB'deyse atlanır.
* **Aktif yazılır.** `ActivateOnExternalLogin()` ile `Status = Active`; ilk login'de ek adım kalmaz.
* **Factory + AssignRole.** Kullanıcı `IUserFactory` ile üretilir, roller `Role` enumeration'dan eşleştirilir.

<Note>
  Dev test admin'leri (`cihan.delipinar@diyanet.gov.tr`, `safa.goksel@diyanet.gov.tr`) artık hardcoded değil; `appsettings.Development.json` içindeki `SeedWork:BootstrapAdmins` listesinde tanımlıdır.
</Note>

## DistrictSeeder — 973 ilçe

İlçeler CSV'den okunur ve DB'de olmayanlar eklenir. **Tüm ortamlarda** çalışır (City enumeration referans bütünlüğü için zorunlu) ve idempotenttir:

```csharp theme={null}
var existingIds = await db.Districts.Select(d => d.Id).ToListAsync(cancellationToken);
var existingSet = new HashSet<int>(existingIds);

var toAdd = new List<District>();
using var reader = new StreamReader(csvPath);     // SeedData/Districts.csv
// header atlanır; her satır: id,cityId,name
while ((line = await reader.ReadLineAsync(cancellationToken)) is not null)
{
    // ... parse ...
    if (existingSet.Contains(id)) continue;       // idempotent
    toAdd.Add(new District(id, cityId, name));
}

if (toAdd.Count == 0)
{
    _logger.LogInformation("[DistrictSeeder] Tüm ilçeler zaten yüklü ({Total} kayıt).", existingIds.Count);
    return;
}

await db.Districts.AddRangeAsync(toAdd, cancellationToken);
await db.SaveChangesAsync(cancellationToken);     // AuditInterceptor SystemUserId atar
```

CSV iki konumdan aranır (önce `AppContext.BaseDirectory`, sonra çalışma dizini):

```csharp theme={null}
Path.Combine(AppContext.BaseDirectory,        "SeedData", "Districts.csv"),
Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "Districts.csv"),
```

Dosya bulunamazsa uyarı loglanıp seeder atlanır (exception fırlatmaz).

## Fluent HasData — referans verisi

Enumeration türevi roller ve varsayılan rol-izin matrisi `IEntityTypeConfiguration` içindeki `HasData` ile migration'a gömülür (kalıcı, versiyonlu).

`Role` seed'i:

```csharp theme={null}
builder.HasData(
    new { Id = Role.SuperAdmin.Id, Name = Role.SuperAdmin.Name },
    new { Id = Role.Admin.Id,      Name = Role.Admin.Name      },
    new { Id = Role.Staff.Id,      Name = Role.Staff.Name      },
    new { Id = Role.ReadOnly.Id,   Name = Role.ReadOnly.Name   }
);
```

`RolePermission` seed'i deterministik Guid ile üretilir (migration tutarlılığı için sabit Id'ler şart):

```csharp theme={null}
// Deterministik Guid: rolId üç bloğa, son blok 12 hane = permission Id.
private static Guid Seed(int roleId, int permissionId) =>
    new Guid($"a{roleId:D7}-{roleId:D4}-{roleId:D4}-{roleId:D4}-{permissionId:D12}");

private static readonly DateTime SeedDate = new(2026, 5, 18, 0, 0, 0, DateTimeKind.Utc);

builder.HasData(
    new { Id = Seed(Role.SuperAdmin.Id, Permission.UsersRead.Id),
          RoleId = Role.SuperAdmin.Id, PermissionId = Permission.UsersRead.Id,
          CreatedAt = (DateTime?)SeedDate },
    // ... varsayılan SuperAdmin × Admin × Staff × ReadOnly matrisi
);
```

<Warning>
  `HasData` değerleri **deterministik** olmalı (`DateTime.Now`, `Guid.NewGuid()` gibi runtime değer KULLANILMAZ) — aksi halde her migration üretiminde model snapshot değişir ve gereksiz migration oluşur. Bu yüzden `SeedDate` sabittir ve Guid'ler `Seed(...)` ile hesaplanır.
</Warning>

## Prod'da davranış

| Seeder           | Production davranışı                                                                                                                                                                                                                                           |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DevDataSeeder`  | `SeedWork:BootstrapAdmins` doluysa çalışır, boşsa **no-op**. Prod'da genelde `appsettings.Production.json` ya da env-file (`SeedWork__BootstrapAdmins__0__Email=...`) ile en az bir SuperAdmin verilir; boş bırakılırsa hiç user yaratılmaz, DBA manuel ekler. |
| `DistrictSeeder` | Tüm ortamlarda çalışır (idempotent). İlçeler master data olduğundan prod'da da yüklenmelidir.                                                                                                                                                                  |
| `HasData`        | Migration'ın parçası — her ortamda `MigrateAsync` ile uygulanır.                                                                                                                                                                                               |

<Note>
  `DevDataSeeder` adı yanıltıcı olabilir: production'da otomatik olarak kapanan bir şey değildir; davranışını tamamen `SeedWork:BootstrapAdmins` config'i belirler. Listeyi boş bırakmak prod için güvenli default'tur.
</Note>

## İlgili

<CardGroup cols={2}>
  <Card title="Migrations" href="/data/migrations">
    MigrationService ve seed'lerin çalışma sırası.
  </Card>

  <Card title="DbContext & Repository" href="/data/dbcontext">
    IUnitOfWork.SaveEntitiesAsync ve repository kullanımı.
  </Card>

  <Card title="Audit Interceptor" href="/data/audit-interceptor">
    Seed sırasında SystemActor.Id'nin audit alanlarına yazılması.
  </Card>

  <Card title="Data Genel Bakış" href="/data/overview">
    Katman rolü ve klasör yapısı.
  </Card>
</CardGroup>
