modules/ai-guide/Write-AiGuide-Core.ps1
|
param( [string] $ProjectName, [string] $OrmMode, [string] $DbProvider = "MSSQL", [string] $FrontendMode, [string[]] $Entities = @("User", "Product"), [string] $OutputPath, [bool] $SkipTests = $false, [bool] $SkipCi = $false, [bool] $SkipFrontend = $false, [bool] $Observability = $false, [string] $IdeConfig = "VSCode", [bool] $I18n = $false ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $guideDir = Join-Path $OutputPath "ai-guide" function Write-Guide($fileName, $content) { Write-CoreFile (Join-Path $guideDir $fileName) $content } $nonAuthEntities = @($Entities | Where-Object { $_ -ne "User" }) $allEntitiesStr = $Entities -join ", " $generatorVersion = Get-MinVer "version" "?" $ormShort = switch ($OrmMode) { "Dapper" { "Dapper (raw SQL, micro-ORM)" } "EFCore" { "EF Core 8 (Code-First, LINQ)" } "Hybrid" { "Hybrid - Yazma: Dapper | Okuma: EF Core" } } $dbNote = switch ($DbProvider) { "Postgres" { "PostgreSQL (Npgsql)" } "SQLite" { "SQLite (dosya tabanli, lokal gelistirme)" } default { "MS SQL Server" } } $dbConnectionNote = switch ($DbProvider) { "Postgres" { "Host=db;Port=5432;Database=${ProjectName}Db;Username=postgres;Password=postgres" } "SQLite" { "Data Source=${ProjectName}.db" } default { "Server=db;Database=${ProjectName}Db;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" } } # ─── README.md ──────────────────────────────────────────── $feSection = if ($SkipFrontend) { "Frontend: Yok (API-Only)" } else { "Frontend: $FrontendMode" } $testSection = if ($SkipTests) { "Test Katmani: Atlanmis (-SkipTests)" } else { "Test Katmani: xUnit + Moq + FluentAssertions" } $ciSection = if ($SkipCi) { "CI/CD: Atlanmis (-SkipCi)" } else { "CI/CD: GitHub Actions (ci.yml + docker.yml)" } $obsSection = if ($Observability){ "Observability: Serilog + OpenTelemetry" } else { "Observability: Yok" } $ideSection = if ($IdeConfig -ne "None") { "IDE: $IdeConfig (launch.json + tasks.json hazir)" } else { "IDE: Yapilandirilmamis" } $i18nSection = if ($I18n) { "i18n: EN/TR (i18next)" } else { "i18n: Yok" } $uiRow = if (-not $SkipFrontend) { "| UI | http://localhost:3000 |`n" } else { "" } Write-Guide "README.md" @" # $ProjectName - AI Gelistirici Kilavuzu Bu klasor, AI asistaniyla gelistirme yaparken uyulmasi gereken kurallari, mimariyi ve yeni ozellik ekleme adimlarini aciklar. --- ## Proje Ozeti | Alan | Deger | |-------------------|--------------------------------------------| | Proje | $ProjectName | | ORM | $ormShort | | Veritabani | $dbNote | | $feSection | | | Entityler | $allEntitiesStr | | $testSection | | | $ciSection | | | $obsSection | | | $ideSection | | | $i18nSection | | | Mimari | Clean Architecture | | CQRS | MediatR 14 | | Auth | JWT Bearer + BCrypt + Rol Tabanli Yetki | | API Yanit | ApiResponse<T> wrapper (success/data/msg) | | Sayfalama | PagedResult<T> + GET /paged endpoint | | Soft Delete | IsDeleted + DeletedAt (fiziksel silme yok) | | Unit of Work | IUnitOfWork (transaction yonetimi) | --- ## Klasor Yapisi `````` ai-guide/ +-- README.md <- Bu dosya - baslangic noktasi +-- architecture.md <- Katman sorumluluklari ve bagimlilik kurallari +-- add-feature.md <- Yeni entity/ozellik ekleme adim adim rehber +-- api-patterns.md <- ApiResponse, Pagination, Soft Delete, UoW ornekleri +-- orm-guide.md <- ORM'e ozgu kod ornekleri +-- rules.md <- Naming, validation, hata yonetimi kurallari +-- devlog.md <- Mimari kararlar ve gelistirme gecmisi (AI icin baglam) $(if (-not $SkipTests) { "+-- testing-guide.md <- Test yazma rehberi (Domain/Application/Integration)`n" })$(if (-not $SkipCi) { "+-- ci-cd.md <- GitHub Actions pipeline aciklamasi`n" })$(if ($Observability) { "+-- observability.md <- Serilog ve OpenTelemetry kullanim kilavuzu`n" })$(if (-not $SkipFrontend) { "+-- frontend-guide.md <- Frontend mimarisi ve React Query kullanim rehberi`n" })`````` --- ## Hizli Baslangic ``````bash docker-compose up --build `````` | Servis | URL | |---------|-----------------------------------------------| | Swagger | http://localhost:8080/swagger | | API | http://localhost:8080/api | ${uiRow}| DB | $dbConnectionNote | Varsayilan kullanici: ``admin`` / ``Admin123!`` Varsayilan rol: ``Admin`` (JWT'de ``Role`` claim olarak gelir) --- ## Mevcut Entityler $(foreach ($e in $Entities) { "- **$e** - CQRS handler, repository, controller, paged endpoint hazir`n" }) Yeni entity eklemek icin bkz. **add-feature.md** --- ## API Yanit Formati Tum endpoint'ler ``ApiResponse<T>`` zarfiyla yanitlar: ``````json { "success": true, "message": null, "data": { ... } } { "success": false, "message": "Hata mesaji", "data": null } `````` Sayfalama: ``GET /api/products/paged?page=1&size=20`` ``````json { "success": true, "data": { "items": [...], "totalCount": 50, "page": 1, "size": 20 } } `````` --- ## En Onemli Kurallar (AI icin) 1. **Hicbir is mantigi WebApi katmaninda olmamali.** Controller sadece: ``HTTP istek -> _mediator.Send(...) -> Ok(ApiResponse<T>.Ok(sonuc))`` 2. **Domain katmani dis bagimlilik almaz.** Application veya Persistence import etme. 3. **Validation: FluentValidation kullan.** Data Annotations kullanma. 4. **Controller'da try/catch yazma.** GlobalExceptionHandler tumu yakalar. 5. **Fiziksel silme yapma.** ``DeleteAsync`` soft delete uygular (``IsDeleted = true``). 6. **GetAllAsync yerine GetPagedAsync kullan** liste endpoint'lerinde. 7. **ORM: $OrmMode kurallarina uy.** Bkz. orm-guide.md "@ # ─── architecture.md ────────────────────────────────────── $ormArchNote = switch ($OrmMode) { "Dapper" { "Tum veri erisimi Dapper + raw SQL ile yapilir. EF Core ve AppDbContext YOKTUR." } "EFCore" { "Tum veri erisimi EF Core 8 ile yapilir. AppDbContext Code-First yonetilir." } "Hybrid" { "Yazma (Add/Update/Delete): Dapper | Okuma (GetAll/GetById/GetPaged): EF Core + AsNoTracking()" } } $dbContextSection = if ($OrmMode -ne "Dapper") { @" ## AppDbContext `````` Persistence/ +-- Context/ +-- AppDbContext.cs <- DbSet<Entity> + HasQueryFilter(soft delete) + HasData(seed) `````` Yeni entity eklendiginde AppDbContext'e DbSet ve HasQueryFilter ekle: ``````csharp public DbSet<Category> Categories => Set<Category>(); // OnModelCreating icinde: builder.Entity<Category>().HasQueryFilter(e => !e.IsDeleted); `````` "@ } else { "" } $migrationSection = if ($OrmMode -ne "Dapper") { @" ## EF Core Migration Akisi Projede ``scripts/migrate.ps1`` (Windows) ve ``scripts/migrate.sh`` (Linux/Mac) hazir gelir: ``````powershell # Yeni migration olustur .\scripts\migrate.ps1 -MigrationName "AddOrderTable" # Son migration'i geri al .\scripts\migrate.ps1 -Revert `````` Manuel calistirmak icin: ``````bash dotnet ef migrations add <AciklamaciAd> \ --project src/$ProjectName.Persistence \ --startup-project src/$ProjectName.WebApi dotnet ef database update \ --project src/$ProjectName.Persistence \ --startup-project src/$ProjectName.WebApi `````` "@ } else { "" } Write-Guide "architecture.md" @" # Mimari - Clean Architecture ## Katman Sorumluluklari `````` $ProjectName.Domain Entities/ <- Sadece alan modeli - dis bagimlilik YASAK BaseEntity: Id, CreatedAt, UpdatedAt, IsDeleted, DeletedAt Interfaces/ <- IRepository<T>, IUserRepository, IUnitOfWork imzalari Common/ <- PagedQuery, PagedResult<T> $ProjectName.Application DTOs/ <- Veri transferi (C# record) Common/ <- ApiResponse<T> (Ok/Fail factory) Features/ <- CQRS: Commands/ ve Queries/ (MediatR handler'lar) Behaviors/ <- ValidationBehavior (pipeline - otomatik devreye girer) Interfaces/ <- ITokenService, IPasswordHasher Mappings/ <- AutoMapper profilleri (Entity <-> DTO) $ProjectName.Infrastructure Auth/ <- JwtTokenService (Role claim dahil) Security/ <- BcryptPasswordHasher $ProjectName.Persistence $(if ($OrmMode -ne "Dapper") { "Context/ <- AppDbContext (HasQueryFilter ile soft delete)" }) Repositories/ <- IRepository<T> implementasyonlari (GetPagedAsync dahil) UnitOfWork/ <- UnitOfWork.cs (sadece EFCore/Hybrid modda) ServiceRegistration.cs $ProjectName.WebApi Controllers/ <- SADECE HTTP + _mediator.Send() + ApiResponse<T>.Ok(...) Handlers/ <- GlobalExceptionHandler (IExceptionHandler - merkezi hata) Program.cs <- DI + Authorization policy + ExceptionHandler `````` --- ## Bagimlilik Kurali (Dependency Rule) `````` WebApi --> Application, Infrastructure, Persistence Persistence --> Domain, Application Infrastructure --> Application Application --> Domain Domain --> (hicbir sey - saf) `````` **Ust katman alt katmana referans verebilir. Alt katman ust katmana VEREMEZ.** --- ## HTTP Istek Yasam Dongusu `````` HTTP Request -> GlobalExceptionHandler (try/catch BURADADIR - middleware) -> JWT Authentication (token + Role claim dogrulama) -> Authorization Policy (AdminOnly / UserOnly) -> Controller.Action() -> _mediator.Send(command/query) -> ValidationBehavior (FluentValidation - otomatik calisir) -> CommandHandler / QueryHandler -> IRepository (domain interface) -> Persistence Repository (gercek DB cagrisi) -> ApiResponse<T>.Ok(dto) (zarflama) -> HTTP 200 `````` --- ## BaseEntity Her entity ``BaseEntity``'den turetilir: ``````csharp public abstract class BaseEntity { public int Id { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public bool IsDeleted { get; set; } = false; // Soft Delete public DateTime? DeletedAt { get; set; } // Soft Delete tarihi } `````` --- ## Soft Delete ``DeleteAsync`` fiziksel silme YAPMAZ: ``````csharp public async Task DeleteAsync(int id) { var entity = await GetByIdAsync(id); if (entity != null) { entity.IsDeleted = true; entity.DeletedAt = DateTime.UtcNow; await _ctx.SaveChangesAsync(); } } `````` EFCore/Hybrid modda global query filter silinmisleri otomatik gizler: ``````csharp builder.Entity<Product>().HasQueryFilter(e => !e.IsDeleted); // Artik GetAllAsync(), GetPagedAsync() sorgularinda IsDeleted=true kayitlar gelmez `````` --- ## IUnitOfWork Coklu repository islemlerinde transaction icin: ``````csharp public interface IUnitOfWork : IDisposable { Task<int> SaveChangesAsync(CancellationToken ct = default); Task BeginTransactionAsync(); Task CommitTransactionAsync(); Task RollbackTransactionAsync(); } `````` Handler ornegi: ``````csharp await _uow.BeginTransactionAsync(); try { await _productRepo.AddAsync(product); await _orderRepo.AddAsync(order); await _uow.CommitTransactionAsync(); } catch { await _uow.RollbackTransactionAsync(); throw; } `````` --- ## Rol Tabanli Yetkilendirme JWT token ``Role`` claim iceriyor. Program.cs'de tanimli policy'ler: ``````csharp options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); options.AddPolicy("UserOnly", policy => policy.RequireRole("User", "Admin")); `````` Controller kullanimi: ``````csharp [Authorize(Policy = "AdminOnly")] [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { ... } `````` --- ## Veri Erisim Modu: $OrmMode $ormArchNote Veritabani: **$dbNote** Connection string: ``$dbConnectionNote`` $dbContextSection $migrationSection "@ # ─── rules.md ───────────────────────────────────────────── Write-Guide "rules.md" @" # Yazim ve Gelistirme Kurallari ## 1. Naming (Isimlendirme) - **Class/Method:** PascalCase (Orn: ``GetProductByIdQuery``) - **Interface:** I ile baslayan PascalCase (Orn: ``IProductRepository``) - **Variables:** camelCase (Orn: ``productName``) - **DTOs:** Entity adindan sonra ``Dto`` (Orn: ``ProductDto``) - **Commands:** ``Add{E}Command``, ``Update{E}Command``, ``Delete{E}Command`` - **Queries:** ``GetAll{E}sQuery``, ``GetPaged{E}sQuery``, ``Get{E}ByIdQuery`` ## 2. Validation - Tum validasyonlar **Application** katmaninda ``FluentValidation`` ile yapilir. - Her Command icin bir ``{Command}Validator`` sinifi olmalidir. - Domain katmaninda sadece basit self-check validasyonlari olabilir. ## 3. CQRS Kurallari - **Commands:** Veriyi degistiren islemler. Handler icinde ``int`` (ID) veya ``Unit`` doner. - **Queries:** Veriyi okuyan islemler. Handler icinde yalnizca DTO doner. - Handler icinde yalnizca repository ve gerekli servisler enjekte edilmeli. ## 4. API Yanit Formati - **Her controller action** ``ApiResponse<T>.Ok(data)`` ile sarmalanmalidir: ``````csharp return Ok(ApiResponse<ProductDto>.Ok(dto)); return Ok(ApiResponse<object>.Fail("Urun bulunamadi.")); `````` - Hata durumunda ``GlobalExceptionHandler`` otomatik ``ApiResponse`` uretir. ## 5. Sayfalama - Liste endpoint'lerinde daima paged endpoint kullan: ``GET /api/products/paged?page=1&size=20`` - ``GetPagedAsync(PagedQuery)`` tum repository'lerde mevcuttur. - ``PagedResult<T>`` dondurmeli: ``Items``, ``TotalCount``, ``Page``, ``Size``. ## 6. Soft Delete - Fiziksel silme (``DELETE FROM``) yapma. ``DeleteAsync`` soft delete uygular. - Silinen kayitlara erisilmemesi icin EFCore global query filter kullan. - Silinen kaydi geri getirmek gerekirse: ``IgnoreQueryFilters()`` ile sorgula. ## 7. Hata Yonetimi - Is mantigi hatalari icin ozel exception firlat (Orn: ``NotFoundException``). - ``GlobalExceptionHandler`` bu hatalari yakalar ve ProblemDetails formatinda yanitlar. - Handler icinde ``try-catch`` kullanmaktan kacin. - Controller'da ``try-catch`` YAZMAK YASAKTIR. ## 8. Yetkilendirme - Hassas endpoint'lerde ``[Authorize(Policy = "AdminOnly")]`` kullan. - Token'da ``Role`` claim mevcuttur: ``Admin`` veya ``User``. - Yeni rol eklemek icin hem DB seed'ini hem JWT claim'ini guncelle. "@ # ─── api-patterns.md ────────────────────────────────────── $firstE = if ($nonAuthEntities.Count -gt 0) { $nonAuthEntities[0] } else { "Product" } $firstPE = Get-Plural $firstE $firstLowE = $firstE.ToLower() Write-Guide "api-patterns.md" @" # API Desenleri Kilavuzu ## ApiResponse<T> Wrapper Tum API yanitlari bu zarfa sarmalanir: ``````csharp // Application/Common/ApiResponse.cs public class ApiResponse<T> { public bool Success { get; set; } public string? Message { get; set; } public T? Data { get; set; } public static ApiResponse<T> Ok(T data, string? msg = null) => new() { Success = true, Data = data, Message = msg }; public static ApiResponse<T> Fail(string msg) => new() { Success = false, Message = msg }; } `````` Controller kullanimi: ``````csharp [HttpGet] public async Task<IActionResult> GetAll() { var result = await _mediator.Send(new GetAll${firstE}sQuery()); return Ok(ApiResponse<IEnumerable<${firstE}Dto>>.Ok(result)); } [HttpPost] public async Task<IActionResult> Add([FromBody] Add${firstE}Command cmd) { var id = await _mediator.Send(cmd); return Ok(ApiResponse<int>.Ok(id, "${firstE} basariyla eklendi.")); } `````` --- ## Sayfalama (Pagination) ### Domain Tipleri ``````csharp // Domain/Common/PagedQuery.cs public class PagedQuery { public int Page { get; set; } = 1; public int Size { get; set; } = 20; public int Skip => (Page - 1) * Size; } // Domain/Common/PagedResult.cs public class PagedResult<T> { public IEnumerable<T> Items { get; set; } = []; public int TotalCount { get; set; } public int Page { get; set; } public int Size { get; set; } public int TotalPages => (int)Math.Ceiling((double)TotalCount / Size); public bool HasNextPage => Page < TotalPages; } `````` ### CQRS Query Ornegi ``````csharp // Features/${firstE}s/Queries/GetPaged${firstE}s/GetPaged${firstE}sQuery.cs public record GetPaged${firstE}sQuery(int Page, int Size) : IRequest<PagedResult<${firstE}Dto>>; // Handler: public async Task<PagedResult<${firstE}Dto>> Handle(GetPaged${firstE}sQuery request, CancellationToken ct) { var query = new PagedQuery { Page = request.Page, Size = request.Size }; var result = await _repo.GetPagedAsync(query); return new PagedResult<${firstE}Dto> { Items = _mapper.Map<IEnumerable<${firstE}Dto>>(result.Items), TotalCount = result.TotalCount, Page = result.Page, Size = result.Size, }; } `````` ### Controller Endpoint ``````csharp [HttpGet("paged")] public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int size = 20) { var result = await _mediator.Send(new GetPaged${firstE}sQuery(page, size)); return Ok(ApiResponse<PagedResult<${firstE}Dto>>.Ok(result)); } `````` ### Ornek Yanit ``````json GET /api/${firstLowE}s/paged?page=2&size=10 { "success": true, "message": null, "data": { "items": [ { "id": 11, "name": "...", "createdAt": "..." } ], "totalCount": 47, "page": 2, "size": 10, "totalPages": 5, "hasNextPage": true } } `````` --- ## Soft Delete ``DeleteAsync`` cagrildiginda kayit fiziksel olarak silinmez: ``````csharp // Silme await _repo.DeleteAsync(id); // Sonuc: IsDeleted=true, DeletedAt=2026-05-12T... // Normal sorgular silinen kayitlari getirmez (global filter) var all = await _repo.GetAllAsync(); // IsDeleted=true olanlari gormez // Silinen kayitlara erismek gerekirse (sadece Admin operasyonlari): var withDeleted = await _ctx.${firstPE}.IgnoreQueryFilters().ToListAsync(); `````` --- ## Unit of Work Birden fazla repository uzerinde atomik islem: ``````csharp public class TransferCommandHandler : IRequestHandler<TransferCommand, Unit> { private readonly I${firstE}Repository _repo; private readonly IUnitOfWork _uow; public async Task<Unit> Handle(TransferCommand request, CancellationToken ct) { await _uow.BeginTransactionAsync(); try { // Birden fazla degisiklik var item = await _repo.GetByIdAsync(request.Id); item!.Name = request.NewName; await _uow.SaveChangesAsync(ct); await _uow.CommitTransactionAsync(); return Unit.Value; } catch { await _uow.RollbackTransactionAsync(); throw; } } } `````` --- ## GlobalExceptionHandler Manuel try/catch yazma — handler otomatik devreye girer: ``````csharp // WebApi/Handlers/GlobalExceptionHandler.cs // ValidationException -> HTTP 400 + validation hatalar listesi // UnauthorizedAccessException -> HTTP 401 // KeyNotFoundException -> HTTP 404 // Diger exception'lar -> HTTP 500 // Yanit formati (ProblemDetails): { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "Validation failed", "status": 400, "errors": { "Name": ["Name bos olamaz."] } } `````` --- ## Health Check ``````bash GET /health # 200 OK - { "status": "Healthy", "results": { "database": { "status": "Healthy" } } } `````` "@ # ─── devlog.md ──────────────────────────────────────────── $devlogDate = (Get-Date -Format "yyyy-MM-dd") $featureFlags = @() if (-not $SkipTests) { $featureFlags += "xUnit + Moq + FluentAssertions test altyapisi" } if (-not $SkipCi) { $featureFlags += "GitHub Actions CI/CD (ci.yml + docker.yml)" } if ($Observability) { $featureFlags += "Serilog + OpenTelemetry observability stack" } if (-not $SkipFrontend) { $featureFlags += "$FrontendMode frontend (React Query, PrivateRoute, auth flow)" } if ($I18n) { $featureFlags += "i18n EN/TR dil destegi (i18next + LanguageSwitcher)" } $featureFlagsStr = if ($featureFlags.Count -gt 0) { ($featureFlags | ForEach-Object { "- $_" }) -join "`n" } else { "- Ek ozellik secilmedi" } $ormDecision = switch ($OrmMode) { "Dapper" { "Ham SQL kontrolu ve performans onceligi nedeniyle Dapper tercih edildi. EF Core bagimliligini minimize eder." } "EFCore" { "Kod-oncelikli migration, LINQ sorgu kolayligi ve global query filter (soft delete) icin EF Core 8 tercih edildi." } "Hybrid" { "Yazma islemleri Dapper ile (INSERT performansi), okuma islemleri EF Core ile (LINQ + global filter). En iyi iki dunya." } } $dbDecision = switch ($DbProvider) { "Postgres" { "PostgreSQL: Acik kaynak, JSON destegi ve gelismis sorgu planlayicisi nedeniyle tercih edildi." } "SQLite" { "SQLite: Lokal gelistirme ve CI ortamlari icin ek sunucu gerektirmeden calisir." } default { "MS SQL Server: Kurumsal ortam gereksinimleri ve mevcut altyapi ile uyum nedeniyle tercih edildi." } } Write-Guide "devlog.md" @" # Dev Log - $ProjectName Bu dosya, projedeki mimari kararlari, ozellik eklemelerini ve degisikliklerin nedenlerini kronolojik olarak kaydeder. AI asistan olarak bu dosyayi okuyarak: - Gecmiste neden bu kararin alindigi - Hangi alternatiflerin degerlendirildigi - Mevcut yapiyla nasil tutarli kalinacagi hakkinda baglamsal bilgi edinebilirsin. --- ## Yeni Giris Sablonu Yeni bir ozellik veya karar eklendiginde asagidaki formati kullan: `````` ## [YYYY-AA-GG] <Kisa baslik> **Neden:** <Motivasyon - problem ne idi, neden bu yapildi> **Nasil:** <Yaklasim - hangi pattern/teknik kullanildi> **Karar:** <Net karar ifadesi - baska bir AI okudugunda ne yapacagini bilmeli> **Etkilenen:** <Degistirilen/eklenen dosyalar veya katmanlar> `````` --- ## [$devlogDate] Proje Olusturuldu - cleanarch v$generatorVersion **Neden:** $ProjectName projesi Clean Architecture prensipleriyle sifirdan kuruldu. Bakimi kolay, test edilebilir ve buyuyebilir bir yapi hedeflendi. **Nasil:** cleanarch generator'i ile otomatik iskelet olusturuldu. Tum katmanlar, bagimliliklar ve cross-cutting concerns basta tanimlanarak manuel tekrar kodlamadan kacinildi. **Karar:** - ORM: $OrmMode — $ormDecision - Veritabani: $dbNote — $dbDecision - CQRS: MediatR ile komut/sorgu ayrimi; handler'lar ince, controller'lar saf HTTP - Validasyon: FluentValidation pipeline behavior — controller'larda try/catch yok - Hata yonetimi: GlobalExceptionHandler merkezi — handler'larda try/catch yok - Auth: JWT Bearer + BCrypt + rol tabanli policy (AdminOnly / UserOnly) - Silme: Soft delete zorunlu — fiziksel DELETE hic kullanilmiyor - Sayfalama: Tum liste endpoint'leri /paged ile basliyor — GetAll sadece ic kullanim **Ek ozellikler:** $featureFlagsStr **Etkilenen:** Tum proje altyapisi (Domain / Application / Infrastructure / Persistence / WebApi$(if (-not $SkipFrontend) { " / Frontend" })) --- ## Nasil Giris Eklenir? 1. Yeni bir entity, ozellik veya mimari karar aldiysan bu dosyaya ekle. 2. Tarih formatini koru: ``[YYYY-AA-GG]`` 3. **Neden** kismini atlama — bu, AI icin en degerli kisimdir. 4. Kararlar kalici; eski girisler silinmez, uzerine yeni giris eklenir. Ornek: ``````markdown ## [2026-06-01] Order entity eklendi **Neden:** Satis modulu gereksinimi; urun-siparis iliskisi kurulmasi gerekiyordu. **Nasil:** add-feature.md rehberi izlenerek 9 adimda eklendi. IOrderRepository -> OrderRepository -> AddOrderCommand -> OrdersController **Karar:** Order-Product arasinda navigation property yerine ID referansi kullanildi. Performans: gereksiz Include() sorgularini engeller. **Etkilenen:** Domain/Entities/Order.cs, Persistence/Repositories/OrderRepository.cs, Application/Features/Orders/**, WebApi/Controllers/OrdersController.cs, Persistence/Context/AppDbContext.cs (DbSet + HasQueryFilter + migration) `````` "@ |