---
name: crud-generator
description: "Bir varlık (entity) için TÜM backend CRUD akışını (Entity → EF Config → DbSet → Migration → DTO → Mapster → FluentValidation → Wolverine CQRS → ince Controller) ve frontend MODEL + SERVİS dosyalarını üretir. Arayüz (UI) ÜRETMEZ — onu TODO.md'ye ekler. Sonunda BACK_FRONT_MAPPING.md'yi günceller. .NET 10 + PostgreSQL 18, modüler monolit. Kanonik örnek modüller: account, diagnostics."
---

# Skill: CRUD Üretici (backend akışı + frontend model/servis)

Bu skill bir varlık için **uçtan uca backend CRUD**'unu ve **frontend model + servis**
dosyalarını üretir. **UI ÜRETMEZ** (liste/form ekranları) — onu `frontend/TODO.md`'ye iş
olarak ekler. Bağlayıcı mimari: kök **`AGENTS.md`** (özellikle §3–§10) ve **`README.md`** §9.

> **HATAYA YER YOK.** Her adımı sırayla uygula, atlama. Sonunda backend `dotnet build`
> ve `make front typecheck` **temiz** olmalı; migration uygulanmış olmalı.

## 0) Girdi ve kararlar (önce netleştir)
- **Modül** `<module>` (snake/lowercase, ör. `gis`) ve **DbContext** `<Module>DbContext`.
- **Varlık** `<Entity>` (İngilizce PascalCase, ör. `Cemetery`) + **DB kök adı Türkçe çoğul**
  (ör. `mezarliklar`). Alanlar: ad, tip, zorunluluk, listede/formda görünürlük.
- **Roller:** Her uç noktanın gerektirdiği rol(ler). **Rol her zaman `Roles.<Ad>` sabitiyle
  kullanılır** (asla string). İstenen rol `backend/src/KentOS.Shared/Auth/Roles.cs`'te
  **yoksa oraya ekle** (PascalCase + `All` listesine ekle). Bkz. §9.
- **Kanonik örnekler:** entity+config+convention → `KentOS.Modules.Diagnostics`; DTO/validation/
  mapping/role'lü controller → `account` + `diagnostics`.

> **İsimlendirme (KRİTİK, AGENTS §8):** Kod İngilizce; kullanıcı/validasyon mesajları
> **Türkçe**; DB tablo/kolon **kök adları Türkçe**, **çoğul** tablo, **snake_case**, Türkçe
> `HasComment` + Türkçe index adları. Listeleme **pagination zorunlu**.

---

## 1) Modül projesi (yoksa oluştur; varsa atla)
Var olan modüle ekliyorsan **bu bölümü atla**. Yeni modül için kanonik şablon
`KentOS.Modules.Diagnostics`'tir. Adımlar:

```bash
cd backend
dotnet new classlib -n KentOS.Modules.<Module> -o src/KentOS.Modules.<Module>
rm -f src/KentOS.Modules.<Module>/Class1.cs
dotnet sln KentOS.slnx add src/KentOS.Modules.<Module>/KentOS.Modules.<Module>.csproj
dotnet add src/KentOS.Modules.<Module> reference src/KentOS.Shared/KentOS.Shared.csproj
# Host, modül assembly'sini bin'e alıp reflection ile yüklesin diye API referansı ŞART:
dotnet add src/KentOS.API reference src/KentOS.Modules.<Module>/KentOS.Modules.<Module>.csproj
# Paketler:
dotnet add src/KentOS.Modules.<Module> package Microsoft.EntityFrameworkCore
dotnet add src/KentOS.Modules.<Module> package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add src/KentOS.Modules.<Module> package EFCore.NamingConventions
dotnet add src/KentOS.Modules.<Module> package Asp.Versioning.Mvc
dotnet add src/KentOS.Modules.<Module> package Mapster
dotnet add src/KentOS.Modules.<Module> package FluentValidation
dotnet add src/KentOS.Modules.<Module> package FluentValidation.DependencyInjectionExtensions
dotnet add src/KentOS.Modules.<Module> package WolverineFx   # Controller'da IMessageBus için
```
csproj'a **`<FrameworkReference Include="Microsoft.AspNetCore.App" />`** ekle (ayrı ItemGroup).

> Host'ta Wolverine zaten modül assembly'lerini keşfeder ve `ServiceLocationPolicy =
> AllowedButWarn` ayarlıdır (handler'a DbContext enjeksiyonu çalışır). **Host'a Wolverine
> için dokunmana gerek yok.** Tek host teması: yukarıdaki API→modül `ProjectReference`.

`<Module>Module.cs` (IModule), `Data/<Module>DbContext.cs`, `Data/<Module>Initializer.cs`
dosyalarını `diagnostics` modülünden bire bir uyarlayarak oluştur (§2, §3, §10'daki kalıplar).

---

## 2) Entity (`Domain/<Entity>.cs`)
`BaseEntity`'den türet → `id`, `uuid` (uuidv7), `ust_veri` (jsonb metadata), audit ve
soft-delete **otomatik** gelir. Sadece varlığa özel alanları ekle.
```csharp
using KentOS.Shared.Entities;
namespace KentOS.Modules.<Module>.Domain;

public class <Entity> : BaseEntity
{
    public string Name { get; set; } = string.Empty;
    // ... diğer alanlar (enum alanlar için Shared veya modül enum'u)
}
```

## 3) EF yapılandırması + DbContext
Kolon/tablo/index adları **Türkçe**. Tabloyu **çoğul Türkçe** adla; her kolona
`HasColumnName` (Türkçe) + `HasComment`. `OnModelCreating` sonunda
**`builder.ApplyKentosConventions()`** çağır (uuid/jsonb/audit/soft-delete + Türkçe ortak
kolonlar). DbSet ekle. (Birebir örnek: `DiagnosticsDbContext`.)
```csharp
public DbSet<<Entity>> <Entity>s => Set<<Entity>>();
// OnModelCreating:
builder.Entity<<Entity>>(e =>
{
    e.ToTable("<turkce_cogul>", t => t.HasComment("<Türkçe açıklama>"));
    e.Property(x => x.Name).HasColumnName("<turkce_kolon>").HasMaxLength(200).HasComment("...");
    e.HasIndex(x => x.Name).HasDatabaseName("ix_<turkce_cogul>_<turkce_kolon>");
});
builder.ApplyKentosConventions();
```
DbContext, modülde **`KentosNpgsql.BuildDataSource`** (dinamik JSON) + `UseSnakeCaseNamingConvention`
+ `MigrationsHistoryTable("__ef_migrations", <Module>DbContext.Schema)` ile kaydedilir
(örnek: `<Module>Module.Register`, `diagnostics`'ten uyarla).

## 4) Migration
```bash
make back-migrate name=Add<Entity> mod=src/KentOS.Modules.<Module> ctx=<Module>DbContext
# Uygula (uygulama açılışta da uygular):
make back-update mod=src/KentOS.Modules.<Module> ctx=<Module>DbContext
```

## 5) DTO'lar (`Dtos/<Entity>Dtos.cs`)
Create/Update istekleri + detay + liste-özeti. (Kalıp: `ErrorLogDtos`, `AuthDtos`.)
```csharp
public sealed record Create<Entity>Request(string Name /*, ...*/);
public sealed record Update<Entity>Request(string Name /*, ...*/);
public sealed record <Entity>ListItemDto(long Id, Guid Uuid, string Name /*, özet alanlar*/);
public sealed record <Entity>Dto { /* tam alanlar + Id, Uuid, CreatedAt, UpdatedAt */ }
```

## 6) Mapster (`Mapping/<Module>MappingConfig.cs`)
`IRegister` uygula (host `TypeAdapterConfig.GlobalSettings.Scan` ile otomatik bulur). Entity↔DTO
ve Request→Entity eşlemeleri. (Kalıp: `AccountMappingConfig`.) Handler'larda `.Adapt<T>()`.

## 7) FluentValidation (`Validation/<Entity>Validators.cs`)
Create/Update istekleri için kurallar, **mesajlar Türkçe**. (Kalıp: `AuthValidators`.)
Modül `Register`'da `services.AddValidatorsFromAssemblyContaining<...>()` zaten varsa yeterli.

## 8) CQRS — Wolverine (`Features/<Entity>/`)
Her işlem bir **Command/Query** (record) + **Handler** (POCO sınıf, `Handle` metodu).
Bağımlılıklar (DbContext, IValidator) **Handle metoduna parametre** olarak enjekte edilir
(Wolverine method-injection; host'ta service location'a izin verilidir). DbContext'i handler
**doğrudan** kullanır ve **`SaveChangesAsync`'i kendisi çağırır**.

```csharp
using KentOS.Shared.Errors;
using KentOS.Shared.Persistence;   // PageRequest, PagedResult, ToPagedResultAsync
using KentOS.Shared.Validation;    // ValidateAndThrowKentosAsync
using Mapster; using Microsoft.EntityFrameworkCore; using FluentValidation;

// CREATE
public record Create<Entity>Command(Create<Entity>Request Data);
public class Create<Entity>Handler
{
    public async Task<<Entity>Dto> Handle(Create<Entity>Command cmd, <Module>DbContext db,
        IValidator<Create<Entity>Request> validator, CancellationToken ct)
    {
        await validator.ValidateAndThrowKentosAsync(cmd.Data, ct);
        var entity = cmd.Data.Adapt<<Entity>>();
        db.<Entity>s.Add(entity);
        await db.SaveChangesAsync(ct);
        return entity.Adapt<<Entity>Dto>();
    }
}

// UPDATE
public record Update<Entity>Command(long Id, Update<Entity>Request Data);
public class Update<Entity>Handler
{
    public async Task<<Entity>Dto> Handle(Update<Entity>Command cmd, <Module>DbContext db,
        IValidator<Update<Entity>Request> validator, CancellationToken ct)
    {
        await validator.ValidateAndThrowKentosAsync(cmd.Data, ct);
        var entity = await db.<Entity>s.FirstOrDefaultAsync(x => x.Id == cmd.Id, ct)
            ?? throw AppException.NotFound("Kayıt bulunamadı.");
        cmd.Data.Adapt(entity);            // mevcut entity'yi güncelle
        entity.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(ct);
        return entity.Adapt<<Entity>Dto>();
    }
}

// DELETE (soft-delete: global filtre zaten silinmişleri gizler)
public record Delete<Entity>Command(long Id);
public class Delete<Entity>Handler
{
    public async Task Handle(Delete<Entity>Command cmd, <Module>DbContext db, CancellationToken ct)
    {
        var entity = await db.<Entity>s.FirstOrDefaultAsync(x => x.Id == cmd.Id, ct)
            ?? throw AppException.NotFound("Kayıt bulunamadı.");
        entity.IsDeleted = true; entity.DeletedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(ct);
    }
}

// GET BY ID
public record Get<Entity>ByIdQuery(long Id);
public class Get<Entity>ByIdHandler
{
    public async Task<<Entity>Dto> Handle(Get<Entity>ByIdQuery q, <Module>DbContext db, CancellationToken ct)
        => (await db.<Entity>s.AsNoTracking().FirstOrDefaultAsync(x => x.Id == q.Id, ct)
            ?? throw AppException.NotFound("Kayıt bulunamadı.")).Adapt<<Entity>Dto>();
}

// LIST (pagination ZORUNLU; q/sort/filtre)
public record List<Entity>Query(PageRequest Page /*, filtreler*/);
public class List<Entity>Handler
{
    public async Task<PagedResult<<Entity>ListItemDto>> Handle(List<Entity>Query q, <Module>DbContext db, CancellationToken ct)
    {
        var query = db.<Entity>s.AsNoTracking();
        if (!string.IsNullOrWhiteSpace(q.Page.Q))
            query = query.Where(x => x.Name.Contains(q.Page.Q));
        query = q.Page.Descending
            ? query.OrderByDescending(x => x.Id) : query.OrderBy(x => x.Id);
        var projected = query.Select(x => new <Entity>ListItemDto(x.Id, x.Uuid, x.Name));
        return await projected.ToPagedResultAsync(q.Page, ct);
    }
}
```

## 9) Roller (Shared tek kaynak — UNUTMA)
Uç noktanın rolü `Roles`'te yoksa **önce ekle**: `backend/src/KentOS.Shared/Auth/Roles.cs`
→ `public const string <Yeni> = nameof(<Yeni>);` (PascalCase) + `All` dizisine ekle.
Account seeder (`AccountSeeder`) `Roles.All`'ı tohumladığından yeni rol otomatik oluşur.
**Her zaman `[Authorize(Roles = Roles.<Ad>)]` — asla string literal.**

## 10) Controller (ince) (`Controllers/<Entity>Controller.cs`)
Sadece `IMessageBus`'a yönlendirir; iş mantığı handler'da. Route **modül önekli**
`api/v{version:apiVersion}/<module>/<resource>`. Sınıf/aksiyon seviyesinde `[Authorize(Roles=...)]`.
```csharp
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/<module>/<resource>")]
[Authorize(Roles = Roles.<Ad>)]
public class <Entity>Controller(IMessageBus bus) : ControllerBase
{
    [HttpGet]
    public Task<PagedResult<<Entity>ListItemDto>> List([FromQuery] PageRequest page, CancellationToken ct)
        => bus.InvokeAsync<PagedResult<<Entity>ListItemDto>>(new List<Entity>Query(page), ct);

    [HttpGet("{id:long}")]
    public Task<<Entity>Dto> Get(long id, CancellationToken ct)
        => bus.InvokeAsync<<Entity>Dto>(new Get<Entity>ByIdQuery(id), ct);

    [HttpPost]
    public async Task<ActionResult<<Entity>Dto>> Create(Create<Entity>Request body, CancellationToken ct)
        => CreatedAtAction(nameof(Get), await bus.InvokeAsync<<Entity>Dto>(new Create<Entity>Command(body), ct));

    [HttpPut("{id:long}")]
    public Task<<Entity>Dto> Update(long id, Update<Entity>Request body, CancellationToken ct)
        => bus.InvokeAsync<<Entity>Dto>(new Update<Entity>Command(id, body), ct);

    [HttpDelete("{id:long}")]
    public async Task<IActionResult> Delete(long id, CancellationToken ct)
    { await bus.InvokeAsync(new Delete<Entity>Command(id), ct); return NoContent(); }
}
```
> Controller'lar host'ta merkezî `MapControllers` ile maplenir; modülün `MapEndpoints`'i boş.
> Enum alanlar JSON'da string döner (host `JsonStringEnumConverter` ayarlı).

## 11) Frontend MODEL (`frontend/src/modules/<module>/models/<entity>.ts`)
`interface ... extends BaseEntity` + `EntitySchema` (alan tipleri/bayrakları). Tüm modeller
tek dosyada. (Kalıp: `account/models/user.ts`.) Backend DTO ile **birebir** alan uyumu.
```ts
import type { BaseEntity, EntitySchema } from '@kentos/core';
export interface <Entity> extends BaseEntity { name: string; /* ... */ }
export const <entity>Schema: EntitySchema<<Entity>> = {
  name: '<Tekil>', pluralName: '<Çoğul>', resource: '<module>/<resource>', titleField: 'name',
  fields: [ { name:'name', label:'Ad', type:'text', required:true, inList:true, inForm:true } ],
};
```

## 12) Frontend SERVİS (`frontend/src/modules/<module>/services/<entity>Service.ts`)
Gerçek backend için `CrudService` (axios üzerinden). **resource = `<module>/<resource>`**
(modül önekli; axios baseURL `/api/v1`). Özel uç noktalar (ör. durum değiştirme) için
`axiosClient`'ı doğrudan kullan (bkz. `system/services/errorLogService.ts`).
```ts
import { ApiClient, CrudService } from '@kentos/core';
import type { <Entity> } from '../models/<entity>';
export const <entity>Service = new CrudService<<Entity>>(new ApiClient(), '<module>/<resource>');
// list/get/create/update/remove — Paginated<T> { items,total,page,pageSize }
```

## 13) TODO.md (UI işi — bu skill UI üretmez)
`frontend/TODO.md`'ye ekle:
> "C# backend uç noktaları ve TypeScript (model/servis) entegrasyonu tamamlandı.
> **<Entity>** için Frontend UI (Liste/Form) geliştirilmesi bekleniyor (skill: bu skill'in
> UI bölümü ileride / `create-frontend-module` + sayfalar)."

## 14) BACK_FRONT_MAPPING.md (ZORUNLU güncelle)
Kök `BACK_FRONT_MAPPING.md`'de `<module>` bölümüne satır ekle/güncelle:
- **Modeller:** backend DTO ↔ frontend tip+dosya, durum **✅ SYNCED**.
- **Servisler/Uç Noktalar:** her endpoint ↔ frontend servis metodu, **✅**.
- UI henüz yoksa ilgili satıra "UI bekleniyor" notu + TODO bağı.

## 15) Doğrulama (HATASIZ bitir)
- [ ] `cd backend && dotnet build KentOS.slnx` → **0 error**.
- [ ] Migration üretildi **ve** uygulandı; tablo **çoğul Türkçe**, kolon/index **Türkçe**,
      `uuid`(uuidv7)+`ust_veri`(jsonb) var.
- [ ] Yeni modülse: API→modül `ProjectReference` eklendi (reflection ile yükleniyor),
      `<Module>Initializer` migrate ediyor.
- [ ] Roller `Roles` sabitinden; eksikse `Shared/Auth/Roles.cs`'e eklendi. `[Authorize(Roles=Roles.X)]`.
- [ ] Controller ince (yalnız `IMessageBus`), route `api/v1/<module>/<resource>`, pagination var.
- [ ] `make front typecheck` → temiz. Frontend model alanları DTO ile birebir.
- [ ] `frontend/TODO.md` (UI) ve `BACK_FRONT_MAPPING.md` güncellendi.
- [ ] Akıllıca doğrula: host'u çalıştırıp (`make back-run`) bir uç noktayı dene; 401/403/200
      ve liste şekli (`{items,total,page,pageSize}`) beklenen gibi mi.

> İlgili: [[crud-updater]] (mevcut entity CRUD'unu güncelle/modernize et) ·
> kök `AGENTS.md` §9–§11 · `BACK_FRONT_MAPPING.md`.
