> ## 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.

# Controller'lar

> İnce controller deseni, rota grupları, ResponseWrapper ve RFC 7807 hata yanıtları

Controller'lar **incedir (thin)**: iş mantığı taşımazlar, yalnızca isteği bir MediatR komut/query'sine çevirip `_mediator.Send(...)` çağırırlar. Tüm validasyon, yetkilendirme ve iş kuralları Application katmanındaki pipeline behavior'lar ve handler'lar tarafından işlenir.

## Base controller'lar

İki ortak taban sınıf vardır (`src/DiyanetCleanArchitecture.API/SeedWork/Controllers/`):

| Base class                 | Kullanım                              | CORS policy                                          |
| -------------------------- | ------------------------------------- | ---------------------------------------------------- |
| `AdminApiControllerBase`   | Yönetim paneli (Backoffice)           | `AllowedBackofficePolicy` (sadece backoffice origin) |
| `WebsiteApiControllerBase` | Vatandaş portali (kimlik doğrulamalı) | `AllowedWebsitePolicy` (sadece website origin)       |

Her ikisi de `[ApiController]` + `[EnableCors(...)]` taşır ama **auth attribute'u kasten içermez.**

<Warning>
  **Auth attribute'u yalnızca derived controller'da, tek satırda yazılır.** Base'e `[Authorize(Schemes=Personel)]` + derived'a `[Authorize(Schemes=Personel, Roles=...)]` koymak, ASP.NET'in policy merge davranışı yüzünden 403'e yol açar (combined policy beklenen claim'leri göremez). Doğru kullanım:

  ```csharp theme={null}
  [Authorize(AuthenticationSchemes = KeycloakSchemeNames.Personel, Roles = "SuperAdmin,Admin")]
  ```
</Warning>

## Üç rota grubu

| Path prefix            | Auth     | Scheme                                            | CORS                                        |
| ---------------------- | -------- | ------------------------------------------------- | ------------------------------------------- |
| `api/admin/*`          | Personel | `PersonelScheme` (`KeycloakSchemeNames.Personel`) | Backoffice                                  |
| `api/website/*`        | Vatandaş | `VatandasScheme` (`KeycloakSchemeNames.Vatandas`) | Website                                     |
| `api/website/public/*` | Anonim   | —                                                 | Public (website + backoffice, sadece `GET`) |

`api/website/public/*` hem website hem backoffice origin'ine açıktır — admin panelindeki form dropdown'ları (il/ilçe lookup gibi) public veriye erişebilsin diye.

Named policy'ler `Program.cs`'te tanımlıdır:

* `AuthPolicies.PersonelAuthenticated` → `PersonelScheme` + `RequireAuthenticatedUser()`
* `AuthPolicies.VatandasAuthenticated` → `VatandasScheme` + `RequireAuthenticatedUser()`
* `AuthPolicies.AnyKeycloakAuthenticated` → her iki scheme + `RequireAuthenticatedUser()`

<Note>
  Bare `[Authorize(Schemes=X)]` boş-requirement'lı bir policy üretir ve `AuthorizationMiddleware`'de 403'e düşebilir. Bu yüzden "sadece authenticated olsun yeter" senaryolarında named policy (`RequireAuthenticatedUser()`) kullanılır. `DynamicPermissionPolicyProvider` bu policy'leri `ActiveAccountRequirement` ile augment eder — yani Pending/Banned/Suspended kullanıcılar geçemez (gerekirse action'a `[AllowPendingAccount]` eklenir).
</Note>

## Gerçek örnek: `MeController` (Admin)

`src/DiyanetCleanArchitecture.API/Controllers/Admin/MeController.cs`:

```csharp theme={null}
[Route("api/admin/me")]
[Authorize(Policy = AuthPolicies.PersonelAuthenticated)]
public class MeController : AdminApiControllerBase
{
    private readonly IMediator _mediator;
    public MeController(IMediator mediator) => _mediator = mediator;

    [HttpGet]
    public async Task<IActionResult> Me(CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(new GetAdminMeQuery(), cancellationToken);
        return result.IsSuccess ? Ok(result) : BadRequest(result);
    }

    // Pending/Banned kullanıcılar da erişebilir — [AllowPendingAccount] taşır.
    [HttpGet("status")]
    [AllowPendingAccount]
    public IActionResult GetStatus() { /* claim'lerden hesap durumu */ }
}
```

`GET /api/admin/me` → tam profil (frontend login sonrası bir kez çağırır). `GET /api/admin/me/status` → sadece hesap durumu; full `/me` 403 alırsa frontend "Onay bekliyor" ekranı için bunu okur.

## Controller listesi

**Admin (`api/admin/*`)** — `Users`, `Citizens`, `Roles`, `Announcements`, `Centers`, `Faqs`, `Locations`, `BagisBasvurular` (+ `BagisBasvuruPlanlari`), `EtkinlikBasvurular` (+ `EtkinlikBasvuruPlanlari`), `AdminNotifications`, `AdminSupportTickets`, `LegalDocuments`, `Me`.

**Website auth (`api/website/*`)** — `Me`, `Users`, `BagisBasvurular`, `EtkinlikBasvurular`, `LegalDocuments`.

**Website public (`api/website/public/*`)** — `Announcements`, `Centers`, `Faqs`, `Locations`, `Enums`, `BagisBasvuruPlanlari`, `EtkinlikBasvuruPlanlari`.

**Genel** — `AuthController` (`api/auth` — login/refresh/logout/OTP/TOTP/OAuth), `SiteSettingsController`.

## ResponseWrapper zarfı

Başarılı yanıtlar `ResponseWrapper<TResponse>` ile sarılır:

```csharp theme={null}
public class ResponseWrapper<TResponse> : IResponseWrapper<TResponse>
{
    public TResponse Response  { get; }
    public string    RequestId { get; }
    public bool      IsSuccess { get; }
    public Exception Exception { get; }
    // static Success(...) / Error(...)
}
```

Controller, `result.IsSuccess` üzerinden `Ok(result)` veya `BadRequest(result)` döner.

## Hata yanıtları — RFC 7807 ProblemDetails

Exception'lar `Hellang.Middleware.ProblemDetails` ile yakalanıp `application/problem+json` olarak döndürülür (`SeedWork/ProblemDetails/ProblemDetailsOptionsConfiguration.cs`):

| Exception                              | HTTP status | Gövde                                                   |
| -------------------------------------- | ----------- | ------------------------------------------------------- |
| `DomainException`                      | **400**     | `title` = exception mesajı                              |
| `ApplicationException`                 | **400**     | `title` = exception mesajı                              |
| `FluentValidation.ValidationException` | **422**     | `ValidationProblemDetails` + `errors` (alan → mesaj\[]) |
| `SmsServiceException`                  | **503**     | "SMS servisi şu anda kullanılamıyor."                   |
| `EmailServiceException`                | **503**     | "E-posta servisi şu anda kullanılamıyor."               |
| `UndefinedApplicationException`        | **500**     | "Beklenmeyen bir hata oluştu."                          |
| diğer `Exception`                      | **500**     | fallback                                                |

Her yanıta `traceId` extension'ı eklenir (`Activity.Current?.Id ?? ctx.TraceIdentifier`) — destek/log korelasyonu için tek bağlantı noktasıdır.

<Warning>
  `IncludeExceptionDetails` **her ortamda `false`** olarak ayarlıdır (dev dahil). Stack trace, dosya yolları, iç class isimleri yanıta **eklenmez** — bilgi sızıntısını önler. Tam exception zaten Serilog'a yazılır; geliştirici client'tan gelen `traceId` ile log'da arar ("client'a minimal, log'a tam").
</Warning>

Örnek 422 yanıtı:

```json theme={null}
{
  "title": "Bir veya daha fazla doğrulama hatası oluştu.",
  "status": 422,
  "instance": "/api/admin/users",
  "errors": {
    "Email": [ "Geçerli bir e-posta adresi giriniz." ]
  },
  "traceId": "00-a1b2c3...-01"
}
```

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="API genel bakış" href="/api/overview">
    Pipeline sırası ve servis kayıtları.
  </Card>

  <Card title="OpenAPI / Swagger" href="/api/openapi">
    NSwag, security scheme'ler, client üretimi.
  </Card>

  <Card title="Frontend SPA'lar" href="/api/frontend">
    axios interceptor RFC 7807 hatalarını nasıl işler.
  </Card>

  <Card title="Keycloak" href="/keycloak/overview">
    Çift realm, scheme'ler, claim'ler.
  </Card>
</CardGroup>
