agent-enderun 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1137 @@
1
+ # KENTİM — API / Endpoint Referans Belgesi (MVP Sürümü)
2
+
3
+ > Bu belge, KENTİM platformunun MVP sürümüne ait tüm sayfa, işlem, iş akışı ve rollerden türetilmiş API referansıdır.
4
+
5
+ **Base URL:** `https://api.kentim.com.tr` (tüm endpoint'ler `/v1` ön eki ile çağrılır)
6
+
7
+ ---
8
+
9
+ ## Genel Kurallar
10
+
11
+ - **Auth:**
12
+ - JWT Bearer (vatandaş hesaplı + tüm belediye rolleri)
13
+ - Anonim (public endpoint'ler)
14
+ - API Key (IoT cihazları)
15
+ - HMAC-SHA256 (sistem heartbeat ve command loopback)
16
+ - **Tenant İzolasyonu:** `x-tenant-id` header (proxy otomatik ekler) veya JWT içinden çözülür.
17
+ - **Idempotency:** Tüm `POST` ve `PATCH` isteklerinde `x-idempotency-key: <UUIDv4>` zorunludur.
18
+ - **Optimistic Locking:** Güncellemelerde `If-Match` veya `x-version` header kullanılır.
19
+ - **UUIDv4:** Sistemdeki tüm kimlikler UUIDv4 formatındadır.
20
+ - **Rate Limiting:**
21
+ - Public: IP başına 60 istek/dakika
22
+ - Anonim rapor: IP başına 2 rapor/dakika, 10 rapor/gün
23
+ - Vatandaş: 100 istek/15 dakika
24
+ - Belediye paneli: Kullanıcı başına 1000 istek/15 dakika
25
+ - **Rate Limit Header'ları (Tüm Yanıtlarda):**
26
+ - `X-RateLimit-Limit`
27
+ - `X-RateLimit-Remaining`
28
+ - `X-RateLimit-Reset`
29
+
30
+ ---
31
+
32
+ ## Standart Yanıt Formatı ve Kuralları
33
+
34
+ Tüm endpoint'ler aşağıdaki yapıyı kullanır:
35
+
36
+ **Başarılı Yanıt (200/201):**
37
+ ```json
38
+ {
39
+ "data": { ... },
40
+ "meta": {
41
+ "page": 1,
42
+ "limit": 20,
43
+ "total": 1240,
44
+ "has_more": true
45
+ }
46
+ }
47
+ ```
48
+
49
+ **Hata Yanıtı (4xx/5xx):**
50
+ ```json
51
+ {
52
+ "error": {
53
+ "code": "WORK_ORDER_NOT_FOUND",
54
+ "message": "İş emri bulunamadı",
55
+ "details": "..." // opsiyonel
56
+ }
57
+ }
58
+ ```
59
+
60
+ **Desteklenen Query Parametreleri (Tüm Liste Endpoint'lerinde):**
61
+ - `page`, `limit` (varsayılan 20, max 100)
62
+ - `sort`, `order`
63
+ - `search` (full-text)
64
+
65
+ ---
66
+
67
+ ## Hata Kodları Kataloğu
68
+
69
+ | HTTP | Kod | Açıklama | Kullanıldığı Yerler |
70
+ |------|-----|----------|---------------------|
71
+ | 400 | INVALID_INPUT | Geçersiz parametre | Tüm formlar |
72
+ | 401 | UNAUTHORIZED | Token geçersiz veya eksik | Tüm korumalı endpoint'ler |
73
+ | 403 | FORBIDDEN | Yetki yok | RBAC ihlali |
74
+ | 404 | NOT_FOUND | Kayıt bulunamadı | Tüm :id endpoint'leri |
75
+ | 409 | CONFLICT | Versiyon çakışması | Optimistic locking |
76
+ | 409 | DUPLICATE | Mükerrer işlem | Idempotency |
77
+ | 409 | DISPUTE_EXISTS | Çevrimdışı çakışma havuzu uyuşmazlığı | Çevrimdışı senkronizasyon uyuşmazlığı |
78
+ | 422 | UNPROCESSABLE | İş kuralı ihlali (geofence, SLA vb.) | Rapor, iş emri |
79
+ | 422 | INTEGRATION_PARSER_CRASH | Custom parser sandbox çalışma hatası | IoT telemetri parser çökmesi |
80
+ | 429 | RATE_LIMITED | Hız sınırı aşıldı | Tüm public ve vatandaş |
81
+ | 422 | REOPEN_LIMIT_REACHED | Yeniden açma limiti (max 2) aşıldı | Reopen endpoint'leri |
82
+ | 403 | REDISPATCH_INVALID_STATE | Moderator/müdür izinsiz aşamada re-dispatch denedi | Re-dispatch validasyonu (geçersiz durumdaki re-dispatch isteklerinde) |
83
+ | 402 | PAYMENT_REQUIRED | Abonelik süresi doldu | Grace period dışında yazma işlemleri |
84
+ | 403 | MUNICIPALITY_NOT_APPROVED | Belediyeniz henüz merkez tarafından onaylanmamıştır veya aboneliği askıdadır | Belediye Giriş Kilidi (SaaS Lockout) |
85
+
86
+ ---
87
+
88
+ ## 🟦 Vatandaş Web (Map-First + Modal Model)
89
+
90
+ **Not:** Vatandaş arayüzünde ayrı `/map`, `/report/new`, `/announcements` gibi tam sayfalar kaldırılmıştır.
91
+ Ana sayfa (`/`) doğrudan public haritadır. Tüm diğer akışlar (rapor oluşturma, takip, duyurular, fikirler vb.) bu harita üzerinde **modal / drawer** olarak açılır.
92
+
93
+ API endpoint’leri büyük ölçüde aynı kalır; sadece UI katmanında sayfa yerine modal kullanılır.
94
+
95
+ | Akış | İşlem | Endpoint | Method | Yetkili Rol | Not |
96
+ |------|-------|----------|--------|-------------|-----|
97
+ | Ana Sayfa (`/`) Public Harita | Pinleri yükle | `GET /public/reports?municipality_id=&city_slug=&bbox=&status=&category=` | GET | Anonim | `municipality_id` veya `city_slug` zorunlu. `bbox` formatı: `minLon,minLat,maxLon,maxLat`. Fuzzing + KVKK maskeleme + zoom bazlı granülarite. |
98
+ | Ana Sayfa | Pin detay + popup | `GET /public/reports/:public_id` | GET | Anonim | + yakın raporlar + support_count |
99
+ | Ana Sayfa | "+1 Beni de Etkiliyor" | `POST /public/reports/:public_id/support` | POST | citizen (JWT) | `resolved` hariç |
100
+ | Ana Sayfa | Desteği geri al | `DELETE /public/reports/:public_id/support` | DELETE | citizen (JWT) | `resolved` sonrası mümkün değil |
101
+ | Rapor Oluşturma (Modal) | Yakın rapor kontrolü | `GET /public/reports/nearby?lat=&lng=&radius=100` | GET | Anonim | Harita üzerinde konum seçildiğinde dairesel mükerrer kontrolü |
102
+ | Rapor Oluşturma (Modal) | Rapor gönder | `POST /reports` | POST | Anonim / citizen | reCAPTCHA + rate limit + opsiyonel `parent_reopened_work_order_id` |
103
+ | Rapor Takip (Modal) | Durum sorgula | `GET /reports/track/:trackingCode` | GET | Anonim | Format: `KENT-[plaka]-[rastgele_alfanumerik_8_hane]` |
104
+ | Rapor Takip (Modal) | Geri bildirim | `POST /reports/:id/feedback` | POST | Anonim (x-tracking-code) / citizen | Sadece `resolved` durumunda |
105
+ | Rapor Takip (Modal) | Yeniden aç (Hesaplı/Token) | `PATCH /reports/:id/reopen` | PATCH | citizen | Max 2 kez; "neden" zorunlu |
106
+ | Rapor Takip (Modal) | Yeniden aç (Anonim/Takip Kodlu) | `POST /public/reports/reopen` | POST | Anonim | Body: `{ tracking_code, reason, captcha_token }` (Max 2 kez; captcha + rate limit korumasıyla). **Rate Limit:** IP başına 3 istek/saat, tracking_code başına günde 5 deneme. DB seviyesinde `reopen_count >= 2` kontrolü: aşılırsa `422 REOPEN_LIMIT_REACHED` döner. |
107
+ | Duyurular (Modal) | Liste | `GET /announcements?district=&type=&page=` | GET | Anonim | UI: `/?modal=announcements` |
108
+ | Duyurular (Modal) | Detay | `GET /announcements/:id` | GET | Anonim | UI: `/?modal=announcement&id=:id` |
109
+ | Fikirler (Modal) | Liste | `GET /ideas?sort=&status=&category=&page=` | GET | Anonim | UI: `/?modal=ideas` |
110
+ | Fikirler (Modal) | Detay | `GET /ideas/:id` | GET | Anonim | UI: `/?modal=idea&id=:id` |
111
+ | Fikirler (Modal) | Oy (+1/-1) | `POST /ideas/:id/vote` | POST | citizen (JWT + E-posta onaylı hesap) | **TC Kimlik doğrulaması oy için gerekmez;** yalnızca e-posta onayı yeterli. JWT'de `email_verified: true` claim'i doğrulanır. |
112
+ | Fikirler (Modal) | Oluştur | `POST /ideas` | POST | citizen (JWT + T.C. Kimlik doğrulanmış + E-posta onaylı hesap) | JWT'de `is_identity_verified: true` claim'i ve e-posta onayı doğrulanır. UI: `/?modal=idea-new`. Moderasyon bekler. |
113
+ | Geri Dönüşüm | Noktalar | `GET /recycling-points?type=&lat=&lng=` | GET | Anonim | Ana harita katmanı veya modal |
114
+ | Panik Butonu | Konum gönder | `POST /emergency/panic` | POST | Anonim | Geofencing muaf |
115
+ | Dashboard (Modal) | Raporlarım | `GET /citizen/reports?status=&page=` | GET | citizen (JWT) | UI: `/?modal=dashboard&tab=reports` |
116
+ | Dashboard (Modal) | Oy geçmişim | `GET /citizen/votes` | GET | citizen (JWT) | UI: `/?modal=dashboard&tab=votes` |
117
+ | Dashboard (Modal) | Desteklediklerim | `GET /citizen/support-votes` | GET | citizen (JWT) | UI: `/?modal=dashboard&tab=supported` |
118
+ | Profil (Modal) | Güncelle | `PATCH /citizen/profile` | PATCH | citizen (JWT) | E-posta OTP doğrulamalı (SMS kullanılmaz). UI: `/?modal=profile` |
119
+ | Profil (Modal) | Hesap sil | `DELETE /citizen/account` | DELETE | citizen (JWT) | İstek `202 Accepted` ile yanıtlanır ve bir `task_id` döner. İşlem durumu `GET /citizen/tasks/:task_id` ile sorgulanabilir. |
120
+ | Profil (Modal) | Asenkron işlem durumu | `GET /citizen/tasks/:task_id` | GET | citizen (JWT) | Hesap silme gibi uzun süren asenkron işlemlerin durumunu sorgular. Yanıt şeması: `{ "data": { "task_id": "uuid", "status": "pending\|processing\|completed\|failed", "created_at": "ISO8601", "completed_at": "ISO8601 \| null", "error": "string \| null" } }` |
121
+ | Profil (Modal) | Bildirim tercihleri | `PATCH /citizen/notification-preferences` | PATCH | citizen (JWT) | — |
122
+ | Ana Sayfa | İstatistik widget | `GET /public/statistics` | GET | Anonim | Dönen veri yapısı için detaylı JSON şeması döküman sonundadır |
123
+ | Afet modu | Tahliye rotası | `GET /evacuation-routes?lat=&lng=` | GET | Anonim | Yalnızca afet modunda aktif; normal modda boş liste döner |
124
+ | Auth | Kayıt / Giriş / OTP / Şifre sıfırlama | `POST /auth/*` | POST | — | KVKK onayı zorunlu. OTP: 3 yanlış denemede 15 dk kilitlenir. |
125
+ | Auth | TC Kimlik Doğrulama | `POST /auth/tc-kimlik/verify` | POST | citizen (JWT) | **Fikir oluşturmak** için zorunlu resmi doğrulama (oy vermek için gerekmez). Displays adı korunur. **İstek Gövdesi:** `{ "tckn": "string(11)", "ad": "string", "soyad": "string", "dogum_yili": "number" }`. **Başarılı Yanıt (200):** `{ "data": { "is_identity_verified": true, "verified_at": "ISO8601" } }`. **Hata (422):** `{ "error": { "code": "IDENTITY_VERIFICATION_FAILED", "message": "Kimlik doğrulanamadı" } }`. NVI timeout: 10 sn; başarısızlıkta max 2 otomatik retry sonra `503` döner. |
126
+ | Auth | Token yenile | `POST /auth/refresh` | POST | citizen (JWT) | **Refresh Token Rotation:** Her yenileme işleminde eski refresh token geçersiz kılınır ve yeni bir refresh token üretilir (tek kullanımlık rotasyon). "Beni hatırla" ile 30 güne uzayan token çalındığında ikinci kullanımda otomatik geçersiz olur. |
127
+ | Auth | Çıkış / Oturumlar | `/auth/logout`, `/sessions` | ... | citizen (JWT) | — |
128
+ | Auth | Tüm oturumlardan çık | `DELETE /sessions/all` | DELETE | citizen (JWT) | Tüm aktif cihaz oturumlarını sonlandırır; tüm refresh token'lar geçersiz kılınır. |
129
+
130
+
131
+ **Statik ve Bilgilendirici İçerikler (Modal Üzerinden)**
132
+ | Akış | İşlem | Endpoint | Method | Yetkili Rol | Not |
133
+ |------|-------|----------|--------|-------------|-----|
134
+ | Statik İçerik | İçerik getir | `GET /public/static-contents/:key` | GET | Anonim | `:key` = `about`, `faq`, `privacy`, `kvkk`, `terms`, `contact`. UI: `/?modal=[key]` |
135
+
136
+ **Not:** Bu içerikler artık `central_static_contents` tablosundan dinamik olarak çekilir ve merkezi SaaS panelinden yönetilir. API, `:key` parametresine göre ilgili içeriği döndürür.
137
+
138
+ ---
139
+
140
+ ## 🟨 Belediye Paneli
141
+
142
+ ### Moderatör
143
+
144
+ | İşlem | Endpoint | Method | Yetkili Rol | Kısıt / Not |
145
+ |-------|----------|--------|-------------|-------------|
146
+ | Kuyruk listesi | `GET /work-orders?status=moderation,pending,reopened,chronic_archived&is_public_visible=true|false&...` | GET | muni_moderator, muni_admin | Kendi tenant |
147
+ | Rapor detay | `GET /work-orders/:id` | GET | muni_moderator, muni_admin | — |
148
+ | Müdürlüğe yönlendir | `PATCH /work-orders/:id/dispatch` | PATCH | muni_moderator, muni_admin | — |
149
+ | Reddet | `PATCH /work-orders/:id/reject` | PATCH | muni_moderator, muni_admin | Gerekçe zorunlu |
150
+ | Mükerrer işaretle | `PATCH /work-orders/:id/mark-duplicate` | PATCH | muni_moderator, muni_admin | Ana iş emri bağlantısı |
151
+ | Re-dispatch | `PATCH /work-orders/:id/redispatch` | PATCH | muni_moderator, muni_admin | **Sadece dispatched_to_department aşamasında**; moderatör kullanırsa sayaç artmaz, hatalı aşamada `403` döner |
152
+ | Public görünürlük değiştir | `PATCH /work-orders/:id/public-visibility` | PATCH | muni_moderator, muni_admin | Gerekçe zorunlu |
153
+ | Fotoğraf görünürlük | `PATCH /work-orders/:id/photos/:photo_id/visibility` | PATCH | muni_moderator, muni_admin | — |
154
+ | Dahili not ekle | `POST /work-orders/:id/notes` | POST | muni_moderator, muni_admin | 15 dakika içinde düzenlenebilir |
155
+ | Manuel iş emri oluştur | `POST /work-orders` | POST | muni_moderator, muni_admin | `manual_internal` kaynak tipi. **Moderatör için:** Sadece sistemsel/acil takip ihtiyacı durumunda + zorunlu gerekçe + detaylı audit log. Kötüye kullanım riski nedeniyle muni_admin tarafından devre dışı bırakılabilir. |
156
+
157
+ ### Müdür
158
+
159
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
160
+ |-------|----------|--------|-------------|-----|
161
+ | İş listesi (birincil) | `GET /work-orders?department_id=me&is_public_visible=true|false&...` | GET | muni_department_manager, muni_admin | — |
162
+ | İş listesi (ikincil bildirimler) | `GET /work-orders?secondary_department_id=me` | GET | muni_department_manager, muni_admin | Salt-okunur |
163
+ | Detay | `GET /work-orders/:id` | GET | muni_department_manager, muni_admin | Kendi müdürlüğü |
164
+ | Şefe ata | `PATCH /work-orders/:id/assign-team` | PATCH | muni_department_manager, muni_admin | — |
165
+ | Personele doğrudan ata | `PATCH /work-orders/:id/assign-worker` | PATCH | muni_department_manager, muni_admin | Onay yetkisi müdüre döner |
166
+ | Force approve (şef onayı bypass) | `PATCH /work-orders/:id/force-approve` | PATCH | muni_department_manager, muni_admin | `pending_chief_approval` durumundayken doğrudan `resolved` yapar (şef bypass edilir). |
167
+ | Zorla kapat | `PATCH /work-orders/:id/force-resolve` | PATCH | muni_department_manager, muni_admin | `pending_manager_review` → `resolved`. **Kullanım farkı:** Bu endpoint "yönetici bypass" işlemidir; şefin onayını beklemeden, herhangi bir `pending_manager_review` işi zorla çözüldüğü yapılır. Circuit breaker devreye girdikten sonra müdürün bu kanalı kullanması en yaygın senaryo olmakla birlikte, geçerli bir neden varsa (acil kapanış gibi) diğer durullarda da kullanılabilir. |
168
+ | Personel değiştir | `PATCH /work-orders/:id/reassign-worker` | PATCH | muni_department_manager, muni_admin | Ret sayacı sıfırlanır, `is_manager_bypassed` sıfırlanır (false yapılır), iş şefin ekibinin atanmamış havuzuna (`assigned_to_team`) gönderilir. |
169
+ | Manuel onayla | `PATCH /work-orders/:id/manual-approval` | PATCH | muni_department_manager, muni_admin | `pending_manager_review` → `resolved`. **Kullanım farkı:** Bu endpoint "eksik belgeyi kabul ederek onay" işlemidir; özellikle QA döngüsü sonrasında veya saha personelinin tamamlama kanıtları eksik kalmışken müdürün sorumluluğunu üstlenip el ile onay verdiğini ifade eder. Denetim loguyla birlikte **"Eksik kanıt kabulü"** işareti yazılır. Sonuç `force_resolve` ile aynıdır (`resolved`) ancak denetim izinden ayırt edilebilir. |
170
+ | İptal et | `PATCH /work-orders/:id/reject` | PATCH | muni_department_manager, muni_admin | Gerekçe zorunlu |
171
+ | Re-dispatch | `PATCH /work-orders/:id/redispatch` | PATCH | muni_department_manager, muni_admin | Sayaç artar (ping-pong koruması) |
172
+ | Dahili not ekle | `POST /work-orders/:id/notes` | POST | muni_department_manager, muni_admin | — |
173
+ | Manuel iş emri oluştur | `POST /work-orders` | POST | muni_department_manager, muni_admin | — |
174
+ | Periyodik şablonlar (kendi müdürlüğü) | `GET/POST/PATCH /schedule/templates?department_id=me` | GET/POST/PATCH | muni_department_manager, muni_admin | Sadece kendi müdürlüğü |
175
+ | Periyodik şablon manuel tetikle | `POST /schedule/templates/:id/trigger` | POST | muni_department_manager, muni_admin | Şablonu anında manuel olarak çalıştırır ve iş emrini üretir |
176
+ | Periyodik şablon çalışma geçmişi | `GET /schedule/templates/:id/runs` | GET | muni_department_manager, muni_admin | Şablonun geçmiş otomatik/manuel çalışma geçmişi ve loglarını döner |
177
+
178
+ ### Ekip Şefi
179
+
180
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
181
+ |-------|----------|--------|-------------|-----|
182
+ | İş listesi | `GET /work-orders?team_id=me&status=` | GET | muni_team_chief, muni_admin | Kendi ekibi |
183
+ | Detay | `GET /work-orders/:id` | GET | muni_team_chief, muni_admin | — |
184
+ | Personele ata | `PATCH /work-orders/:id/assign-worker` | PATCH | muni_team_chief, muni_admin | Kendi ekibi personeli |
185
+ | Fotoğraf onayla | `PATCH /work-orders/:id/approve-photos` | PATCH | muni_team_chief, muni_admin | `resolved` |
186
+ | Geofence bypass onayla/reddet | `PATCH /work-orders/:id/geofence-bypass` | PATCH | muni_team_chief, muni_admin | Sapma onaylanırsa koordinat EXIF ile güncellenir |
187
+ | Fotoğraf reddet | `PATCH /work-orders/:id/reject-photos` | PATCH | muni_team_chief, muni_admin | 3+ ret → circuit breaker |
188
+ | QA onay | `PATCH /work-orders/:id/qa-confirm` | PATCH | muni_team_chief, muni_admin | — |
189
+ | QA işe döndür | `PATCH /work-orders/:id/qa-reopen` | PATCH | muni_team_chief, muni_admin | — |
190
+ | Dahili not ekle | `POST /work-orders/:id/notes` | POST | muni_team_chief, muni_admin | 15 dakika içinde düzenlenebilir |
191
+ | Manuel iş emri | `POST /work-orders` | POST | muni_team_chief, muni_admin | Müdürlük havuzuna düşer. **Not:** Şefin kendi oluşturduğu işe doğrudan personel ataması durumunda onay yetkisi şefe kalır (bkz. proje.md F6.1). |
192
+ | Günlük plan | `GET /daily-plan?team_id=me&date=` | GET | muni_team_chief, muni_admin | — |
193
+ | Plan sırası kaydet | `PATCH /daily-plan/reorder` | PATCH | muni_team_chief, muni_admin | — |
194
+ | Rota optimizasyonu | `POST /route-optimize` | POST | muni_team_chief, muni_admin | OSRM |
195
+ | Ekip performans istatistikleri | `GET /analytics?team_id=me&tab=personnel` | GET | muni_team_chief, muni_admin | — |
196
+ | Uyuşmazlık listesi | `GET /chief/disputes?status=&page=` | GET | muni_team_chief, muni_admin | Çevrimdışı senkronizasyon uyuşmazlıkları listesi |
197
+ | Uyuşmazlık detay | `GET /chief/disputes/:id` | GET | muni_team_chief, muni_admin | `dispute_logs` ve ilgili iş detayları |
198
+ | Uyuşmazlık onayla/reddet | `PATCH /chief/disputes/:id/approve` veya `/reject` | PATCH | muni_team_chief, muni_admin | Çift hak ediş varsa `approved_override` otomatik olarak tetiklenir |
199
+
200
+ ### Saha Personeli
201
+
202
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
203
+ |-------|----------|--------|-------------|-----|
204
+ | Görev listesi | `GET /worker/tasks?status=` | GET | muni_worker, muni_admin | Kendine atanan işler |
205
+ | Detay | `GET /worker/tasks/:id` | GET | muni_worker, muni_admin | — |
206
+ | İşi başlat | `PATCH /worker/tasks/:id/start` | PATCH | muni_worker, muni_admin | `in_progress` |
207
+ | Fotoğraf yükle | `POST /worker/tasks/:id/photos` | POST | muni_worker, muni_admin | GPS + exif damgalı; 50m geofence kontrolü |
208
+ | İşi tamamla | `PATCH /worker/tasks/:id/complete` | PATCH | muni_worker, muni_admin | 50m geofence zorunlu |
209
+ | Geofence bypass talebi | `POST /worker/tasks/:id/geofence-bypass` | POST | muni_worker, muni_admin | EXIF fotoğraf ve gerekçeyle talep oluşturma |
210
+ | Yarıda bırak | `PATCH /worker/tasks/:id/abandon` | PATCH | muni_worker, muni_admin | Normal atamada şef havuzuna (`assigned_to_team`) döner. Müdür bypass atamasında ise doğrudan müdür havuzuna (`dispatched_to_department`) iade edilir ve `is_manager_bypassed = true` flag'i korunur. |
211
+ | Not ekle | `POST /worker/tasks/:id/notes` | POST | muni_worker, muni_admin | — |
212
+ | Vardiya başlat | `POST /worker/shift/start` | POST | muni_worker, muni_admin | Background GPS aktifleşir |
213
+ | Vardiya kapat | `POST /worker/shift/close` | POST | muni_worker, muni_admin | `in_progress` işler şefe devredilir |
214
+ | GPS konum güncelle | `POST /worker/location` | POST | muni_worker, muni_admin | Arka plan, vardiya açıkken çalışır. Vardiya kapalıyken gönderilen güncellemelerde sunucu `204 No Content` döner ve işlem yapmaz. |
215
+
216
+ ### Belediye Admin (Tüm alt rollerin yetkileri + ek yetkiler)
217
+
218
+ > **Yetki Notu:** muni_admin, kendi belediyesindeki tüm rollerin (moderatör, müdür, şef, personel) yetkilerini kapsar. Aşağıda sadece **ek yetkileri** listelenmiştir. RBAC middleware her kaynakta hem rol hem de veri izolasyonu (kendi müdürlük/ekip kısıtları) kontrolü yapar.
219
+
220
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
221
+ |-------|----------|--------|-------------|-----|
222
+ | Admin dashboard | `GET /admin/dashboard` | GET | muni_admin | — |
223
+ | Public harita metrikleri | `GET /admin/public-map-stats` | GET | muni_admin | — |
224
+ | En çok desteklenen işler | `GET /admin/top-supported-work-orders` | GET | muni_admin | — |
225
+ | Müdürlük işlemleri | `GET/POST/PATCH /departments` | GET/POST/PATCH | muni_admin | — |
226
+ | Müdürlük pasif et | `PATCH /departments/:id/deactivate` | PATCH | muni_admin | — |
227
+ | Ekip işlemleri | `GET/POST/PATCH /teams` | GET/POST/PATCH | muni_admin | — |
228
+ | Personel davet et | `POST /users/invite` | POST | muni_admin | — |
229
+ | Personel pasif et | `PATCH /users/:id/deactivate` | PATCH | muni_admin | — |
230
+ | Personel profil güncelle | `PATCH /users/:id` | PATCH | muni_admin | — |
231
+ | Kategori işlemleri | `GET/POST/PATCH/DELETE /categories` | GET/POST/PATCH/DELETE | muni_admin | Aktif iş yoksa silinebilir. Silme işlemi denetim loguna kaydedilir. |
232
+ | Yönlendirme kuralları listesi | `GET /routing-rules` | GET | muni_admin | Kategori bazlı: her kategori için birincil müdürlük + ikincil bildirim müdürlükleri listesi |
233
+ | Yönlendirme kuralı güncelle | `PUT /routing-rules/:category_id` | PUT | muni_admin | Body: `{ "primary_department_id": "uuid", "secondary_department_ids": ["uuid", ...] }` |
234
+ | Yönlendirme kuralları toplu güncelle | `POST /routing-rules/bulk` | POST | muni_admin | CSV yükleme (kategori_kodu, primary_dept_code, secondary_dept_codes). Değişiklikler toplu audit log ile kaydedilir. |
235
+ | Yönlendirme kuralı sil | `DELETE /routing-rules/:category_id` | DELETE | muni_admin | Kategori varsayılan sisteme döner (eğer varsa). |
236
+ | Periyodik şablon listesi | `GET /schedule/templates?department_id=` | GET | muni_admin, muni_department_manager | muni_department_manager sadece kendi müdürlüğünü görebilir |
237
+ | Periyodik şablon oluştur | `POST /schedule/templates` | POST | muni_admin, muni_department_manager | Body: şablon adı, kategori, öncelik, cron/ tekrar sıklığı, varsayılan ekip, başlangıç/bitiş tarihi, `default_public` |
238
+ | Periyodik şablon güncelle | `PATCH /schedule/templates/:id` | PATCH | muni_admin, muni_department_manager | Kendi müdürlüğüne ait şablonları güncelleyebilir |
239
+ | Periyodik şablon pasif et | `PATCH /schedule/templates/:id/deactivate` | PATCH | muni_admin, muni_department_manager | Gelecek tetiklemeler durur, geçmiş işler korunur |
240
+ | Şablon manuel tetikle | `POST /schedule/templates/:id/trigger` | POST | muni_admin, muni_department_manager | Anında iş emri üretir (çakışma kontrolü ile) |
241
+ | Şablon çalışma geçmişi | `GET /schedule/templates/:id/runs` | GET | muni_admin, muni_department_manager | Bu şablondan üretilmiş iş emirlerinin listesi ve durumu |
242
+ | Entegrasyon listesi | `GET /integrations` | GET | muni_admin | Hedef müdürlük, tür, durum, son istek bilgileriyle birlikte |
243
+ | Entegrasyon oluştur / güncelle | `POST /integrations` ve `PATCH /integrations/:id` | POST/PATCH | muni_admin | Body: `name`, `department_id`, `type` (`generic_webhook` | `custom_iot_sensor`), `mapping_rules` (JSON), `parser_script` (JS - sadece custom için), `default_public` (boolean - zorunlu), `description`, `is_active` |
244
+ | Entegrasyon sil / pasif et | `PATCH /integrations/:id/deactivate` | PATCH | muni_admin | Fiziksel silme yasaktır. Pasif yapıldığında telemetri reddedilir. |
245
+ | IoT cihaz envanteri | `GET /integrations/devices` | GET | muni_admin | Cihaz listesi + doluluk, batarya, sağlık durumu + harita pinleri için veri |
246
+ | Cihaz bakım moduna alma | `PATCH /integrations/devices/:device_id/maintenance` | PATCH | muni_admin | Cool-down / spam koruması için manuel bakım modu |
247
+ | Cihazı arşivle | `PATCH /integrations/devices/:device_id/archive` | PATCH | muni_admin | Cihaz fiziksel olarak kaldırıldığında kullanılır |
248
+ | Entegrasyon hata logları | `GET /integrations/logs?integration_id=&event_type=` | GET | muni_admin | `integration_logs` tablosu (FAILED, OUT_OF_BOUNDS, PARSER_CRASH vb.) |
249
+ | Telemetri logları | `GET /integrations/:id/telemetry-logs` | GET | muni_admin | Cihaza özel ham + parse edilmiş telemetri geçmişi |
250
+ | Sensör spam koruması & cooldown | `PATCH /integrations/sensors/:id/maintenance` | PATCH | muni_admin | Otomatik veya manuel "suspicious" moduna alma |
251
+ | Duyuru işlemleri | `GET/POST/PATCH/DELETE /announcements` | GET/POST/PATCH/DELETE | muni_admin | **Not:** Duyuruları görüntüleme (GET): tüm belediye rolleri; oluşturma/düzenleme/silme: yalnızca muni_admin. |
252
+ | Fikir yönetimi | `GET/PATCH/POST /ideas/manage` | GET/PATCH/POST | muni_admin | Fikir listeleme, düzenleme, yanıtlama |
253
+ | Fikir reddi | `PATCH /ideas/manage/:id/reject` | PATCH | muni_admin | Gerekçe zorunlu; vatandaşa bildirim gönderilmez (F14 kararı). |
254
+ | Analitik (tüm tenant) | `GET /analytics?tab=&from=&to=&department_id=&is_public_visible=true|false` | GET | muni_admin | Performans, SLA, destek sayıları bazlı raporlar (Read Replica üzerinden) |
255
+ | Denetim logları | `GET /audit-logs?...` | GET | muni_admin | JSONB diff viewer destekli, CSV dışa aktarım desteklenir |
256
+ | KVKK talepleri listele | `GET /gdpr/requests` | GET | muni_admin | status filtresi desteklenir |
257
+ | KVKK talebini işlemeye başla | `PATCH /gdpr/requests/:id/process` | PATCH | muni_admin | İndeksli `citizen_id` kolonu üzerinden asenkron regex maskelemeyi başlatır |
258
+ | KVKK talebini tamamla | `PATCH /gdpr/requests/:id/complete` | PATCH | muni_admin | Talep durumunu `completed` ve `anonymized_at` olarak günceller |
259
+ | Belediye ayarları | `GET/PATCH /settings` | ... | muni_admin | Public harita, geofence, SLA |
260
+ | Statik noktalar | `GET/POST/PATCH/DELETE /static-points` | ... | muni_admin | Geri dönüşüm + tahliye |
261
+ | Kriz masası | `GET /emergency/panic-signals` | GET | muni_admin | — |
262
+ | Panik sinyal toplu kapat | `PATCH /emergency/panic-signals/bulk-close` | PATCH | muni_admin | — |
263
+ | Anlaşmazlık kuyruğu | `GET /admin/disputes` | GET | muni_admin | — |
264
+ | Kesin atama | `PATCH /work-orders/:id/force-assign` | PATCH | muni_admin | Sadece disputes kuyruğundan. İş emrine `is_force_assigned = true` flag'i yazılır; bu flag aktifken müdürler re-dispatch yapamaz, yalnızca `muni_admin` müdâhale edebilir. Re-dispatch sayıcı sıfırlanır. |
265
+ | 2FA ayarları | `GET/POST/DELETE /auth/2fa` | GET/POST/DELETE | muni_admin | TOTP kurulum, doğrulama ve devre dışı bırakma (zorunlu tutulabilir) |
266
+
267
+ ---
268
+
269
+ ## 🟥 Central Panel
270
+
271
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
272
+ |-------|----------|--------|-------------|-----|
273
+ | Dashboard | `GET /central/dashboard` | GET | central_admin, central_moderator | central_moderator: sadece aggregate metrik + SLA riskli belediye listesi + heartbeat uyarıları (bireysel veri yok) |
274
+ | Belediye listesi (aggregate) | `GET /central/municipalities?...` | GET | central_admin, central_moderator | central_moderator sadece özet alanları (ad, il, status, sla_compliance, active_work_count) görür |
275
+ | Belediye ekle | `POST /central/municipalities` | POST | central_admin | — |
276
+ | Belediye güncelle / pasif / abonelik / key yenile | `PATCH/POST /central/municipalities/:id/...` | ... | central_admin | — |
277
+ | **IoT API Key Rotasyonu** | `POST /central/municipalities/:id/api-key/rotate` | POST | central_admin | Acil key sızıntısı durumunda: eski key anında geçersiz kılınır, yeni key üretilip şifreli kanal üzerinden belediye adminine iletilir. İşlem `API_KEY_ROTATED` denetim loguyla kaydedilir. |
278
+ | Global kategoriler | `GET/POST/PATCH /central/categories` | ... | central_admin | central_moderator salt-okunur |
279
+ | Lokasyon sorgulama hiyerarşisi | `GET /central/locations/hierarchy` | GET | Anonim / Herkes | Dropdown verileri için coğrafi hiyerarşi sorgulama (il/ilçe dropdown'larını besler) |
280
+ | Lokasyon işlemleri | `GET/POST/PATCH/DELETE /central/locations` | ... | central_admin | — |
281
+ | Lokasyon CSV import | `POST /central/locations/import` | POST | central_admin | — |
282
+ | Lokasyon CSV export | `GET /central/locations/export` | GET | central_admin | — |
283
+ | Sistem sağlığı | `GET /central/system-health` | GET | central_admin, central_moderator | central_moderator sadece izleme + uyarı listesi (test tetikleme yok) |
284
+ | Global raporlar | `GET /central/reports?tab=statistics\|heatmap\|comparison&...` | GET | central_admin, central_moderator | central_moderator sadece görüntüleme + CSV/JSON export (hiçbir filtrede vatandaş/iş detayı dönmez) |
285
+ | Afet modu | `POST/DELETE /central/disaster-mode` | ... | central_admin | Şifre onayı zorunlu |
286
+ | Kullanıcı işlemleri | `GET/POST/PATCH /central/users` | ... | central_admin | — |
287
+ | Denetim logları | `GET /central/audit-logs?...` | GET | central_admin | central_moderator erişemez (güvenlik nedeniyle) |
288
+ | Statik içerik güncelle | `PUT /central/static-contents/:key` | PUT | central_admin | `:key` = `about`, `faq`, etc. |
289
+ | Kendi profil | `GET/PATCH /central/profile/security` | ... | central_admin, central_moderator | — |
290
+ | **Public Web Yol Haritası - MVP içeriğini getir/güncelle** | `GET/PUT /central/public-web-roadmap/mvp` | GET/PUT | central_admin | **Request (PUT):** `{ "title": "string(5-120)", "subtitle": "string(max 200)", "content": "string(min 50, max 20000)", "published_at": "ISO8601 (optional)", "is_active": "boolean" }` <br> **Response:** Güncellenmiş içerik + `updated_at` |
291
+ | **Public Web Yol Haritası - V1 içeriğini getir/güncelle** | `GET/PUT /central/public-web-roadmap/v1` | GET/PUT | central_admin | Aynı şema (MVP ile birebir aynı yapı) |
292
+ | Public Web Yol Haritası yayın durumu değiştir | `PATCH /central/public-web-roadmap/:version/toggle` | PATCH | central_admin | **Request:** `{ "is_active": boolean }` <br> **Business Rule:** Her iki sürüm de pasif yapılamaz (409 Conflict döner) |
293
+ | Public Web Yol Haritası önizleme linki üret | `POST /central/public-web-roadmap/:version/preview` | POST | central_admin | **Response:** `{ "preview_url": "string", "expires_at": "ISO8601" }` (24 saat geçerli tek kullanımlık link) |
294
+
295
+ ---
296
+
297
+ ## 🔧 Sistem İç Entegrasyon (Belediye ↔ Central)
298
+
299
+ | Yön | İşlem | Endpoint | Method | Auth | Açıklama |
300
+ |-----|-------|----------|--------|------|----------|
301
+ | IoT → Belediye | Telemetri gönder | `POST /integrations/iot/telemetry` | POST | API Key | V8 sandbox parse, RLS tenant izolasyonu |
302
+ | Belediye → Central | Heartbeat gönder | `POST /v1/central/heartbeat` | POST | HMAC-SHA256 | 1 dakikada bir |
303
+ | Belediye → Central | İstatistik push | `POST /central/municipalities/stats` | POST | HMAC-SHA256 | 15 dakikada bir; anonim |
304
+ | Central → Belediye | Afet komutu | `POST /command-loopback` | POST | HMAC-SHA256 | `x-kentim-signature` zorunlu |
305
+
306
+ ---
307
+
308
+ ## 🔔 Bildirim Endpoint'leri
309
+
310
+ | İşlem | Endpoint | Method | Yetkili Rol |
311
+ |-------|----------|--------|-------------|
312
+ | Bildirim listesi (son 20) | `GET /notifications?page=` | GET | Tüm belediye rolleri |
313
+ | Tek bildirimi okundu yap | `PATCH /notifications/:id/read` | PATCH | Kendi bildirimi |
314
+ | Tümünü okundu yap | `PATCH /notifications/read-all` | PATCH | Kendi bildirimleri |
315
+
316
+ ---
317
+
318
+ ---
319
+
320
+ ## 🔐 Kimlik Doğrulama ve Güvenlik (Genişletilmiş)
321
+
322
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
323
+ |-------|----------|--------|-------------|-----|
324
+ | 2FA kurulum (QR kod) | `GET /auth/2fa/setup` | GET | muni_admin, central_admin | — |
325
+ | 2FA doğrula ve aktifleştir | `POST /auth/2fa/verify` | POST | muni_admin, central_admin | TOTP kodu |
326
+ | 2FA kapat | `DELETE /auth/2fa/disable` | DELETE | muni_admin, central_admin | Yedek kod veya şifre ile |
327
+ | Aktif oturumlar listesi | `GET /sessions` | GET | Tüm belediye rolleri | — |
328
+ | Tüm oturumlardan çıkış | `DELETE /sessions/all` | DELETE | Tüm belediye rolleri | Tüm refresh token'lar geçersiz kılınır |
329
+ | Profil / güvenlik (belediye) | `GET /profile/security` | GET | Tüm belediye rolleri | — |
330
+ | Profil / güvenlik güncelle | `PATCH /profile/security` | PATCH | Tüm belediye rolleri | Şifre + 2FA + e-posta |
331
+
332
+ **Not — 2FA Kapsamı:** 2FA zorunluluğu `muni_admin` ve `central_admin` için zorunludur. Diğer roller (`muni_moderator`, `muni_department_manager`, `muni_team_chief`, `muni_worker`) için isteğe bağlıdır; belediye adminı panel ayarlarından tüm belediye rolleri için 2FA zorunluluğunu aktif edebilir.
333
+
334
+
335
+
336
+ ---
337
+
338
+ ## 📡 Gerçek Zamanlı İletişim (WebSocket / SSE)
339
+
340
+ **Bağlantı ve Kimlik Doğrulama**
341
+ - **Resmi Karar — Handshake-After-Connect Kullanılır:** KENTİM WebSocket istemcilerinde JWT token URL query param'da taşınmaz. Bağlantı kurulduktan hemen sonra ilk WebSocket frame'inde `{ "type": "auth", "token": "<JWT>", "tenant_id": "<TENANT_ID>" }` mesajı gönderilir. Sunucu bu frame'i aldıktan sonra abonelik işlemlerini başlatır; auth frame gönderilmeden yapılan kanal aboneliğ istekleri `4401 Unauthorized` kodu ile kapatılır.
342
+ - **Gerekçe:** JWT'nin URL query param'da taşınması server/proxy loglarına ve tarayıcı geçmişine sızabilir; handshake-after-connect bu riski tam olarak ortadan kaldırır.
343
+ - **Legacy Uyumluluk Notu:** Eski istemciler geçiş süresi boyunca `wss://api.kentim.com.tr/v1/ws?token=<JWT>&tenant_id=<TENANT_ID>` URL param yöntemini kullanabilir; ancak yeni tüm istemci implementasyonlarında handshake-after-connect zorunludur.
344
+ - Protokol: WebSocket (tercih) veya SSE (fallback)
345
+ - Reconnect: Otomatik (exponential backoff)
346
+ - Heartbeat: Her 30 saniyede bir ping/pong
347
+
348
+ **Abone Olunacak Kanallar (Belediye Paneli)**
349
+ - `work-orders.{tenant_id}` → Tüm iş emri güncellemeleri
350
+ - `notifications.{user_id}` → Kullanıcıya özel bildirimler
351
+ - `panic.{tenant_id}` → Kriz masası panik sinyalleri (sadece muni_admin ve kriz masası rolü)
352
+
353
+ **Event Örnekleri ve Payload Şemaları**
354
+
355
+ ```json
356
+ // work-orders.{tenant_id}
357
+ {
358
+ "event": "work-order.updated",
359
+ "data": {
360
+ "id": "uuid",
361
+ "status": "in_progress",
362
+ "assigned_worker_id": "uuid",
363
+ "updated_at": "2026-05-24T11:30:00Z"
364
+ }
365
+ }
366
+
367
+ // notifications.{user_id}
368
+ {
369
+ "event": "notification.new",
370
+ "data": {
371
+ "id": "uuid",
372
+ "title": "Yeni iş atandı",
373
+ "body": "...",
374
+ "type": "work_order_assigned",
375
+ "created_at": "..."
376
+ }
377
+ }
378
+
379
+ // panic.{tenant_id}
380
+ {
381
+ "event": "panic.new",
382
+ "data": {
383
+ "id": "uuid",
384
+ "citizen_location": { "lat": 41.0, "lng": 29.0 },
385
+ "created_at": "..."
386
+ }
387
+ }
388
+ ```
389
+
390
+ **Central Panel:**
391
+ - Gerçek zamanlı kullanılmaz. 60 saniyede bir polling yapılır (`GET /central/system-health` ve ilgili rapor endpoint'leri).
392
+
393
+ **Mobile (Saha Personeli):**
394
+ - Push Notification (FCM / APNs) kullanılır.
395
+ - Token kayıt: `POST /devices` (aşağıda).
396
+
397
+ ---
398
+
399
+ ## 📱 Mobile Push Token ve Cihaz Yönetimi
400
+
401
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
402
+ |-------|----------|--------|-------------|-----|
403
+ | Cihaz / push token kaydet | `POST /devices` | POST | muni_worker, muni_team_chief, citizen | FCM/APNs token + platform + device_id |
404
+ | Cihazı sil | `DELETE /devices/:device_id` | DELETE | muni_worker, muni_team_chief, citizen | — |
405
+ | Bildirim tercihleri | `PATCH /citizen/notification-preferences` | PATCH | citizen | Web + mobile push |
406
+
407
+ ---
408
+
409
+ ## 📁 Dosya ve Medya Yönetimi
410
+
411
+ **Fotoğraf Yükleme Kuralları**
412
+ - Endpoint: `POST /worker/tasks/:id/photos`
413
+ - Yöntem: `multipart/form-data` (tercih edilen) veya Base64 (opsiyonel, küçük dosyalar için)
414
+ - Maksimum dosya sayısı: **5 fotoğraf** / iş emri
415
+ - Maksimum dosya boyutu: **5 MB** / dosya
416
+ - Desteklenen formatlar: JPG, PNG, WEBP
417
+ - Zorunlu meta veriler (sunucu tarafında doğrulanır):
418
+ - GPS koordinatı (EXIF)
419
+ - Çekim zamanı (EXIF)
420
+ - 50 metre geofence kontrolü (tamamlama sırasında)
421
+
422
+ **Yükleme Sonrası Davranış**
423
+ - Fotoğraf yüklendikten sonra `photo_urls` ve `is_public_visible` alanları güncellenir.
424
+ - Her fotoğraf için bağımsız `is_public_visible` kontrolü yapılabilir (moderatör/admin).
425
+
426
+ **Fotoğraf Silme ve Otomatik Temizlik**
427
+ - Fiziksel silme: İş emri `resolved` veya `rejected` olduktan **1 yıl** sonra otomatik yapılır.
428
+ - **Arka Plan İşlemleri (Çözüldü):**
429
+ 1. `public_work_orders_view` içindeki `photo_urls` güncellenir.
430
+ 2. İlgili tenant'ın `public_map_cache` tablosu temizlenir.
431
+ 3. CDN URL'leri invalidate edilir.
432
+ - **Mevcut Durum:** Yukarıdaki cache invalidation ve CDN temizleme adımları tamamen entegre edilmiştir.
433
+ - Elle silme: Sadece moderatör/admin tarafından yapılabilir (`PATCH /work-orders/:id/photos/:photo_id/visibility` ile gizleme veya sistemsel silme).
434
+
435
+ **Public Harita Görünürlüğü**
436
+ - Fotoğraf bazında `is_public_visible` kontrolü moderatör ve admin tarafından yapılabilir.
437
+ - Gizleme nedeni zorunludur (KVKK, uygunsuz içerik vb.).
438
+
439
+ ---
440
+
441
+ ## 📤 Export ve Raporlama Endpoint'leri
442
+
443
+ | İşlem | Endpoint | Method | Yetkili Rol |
444
+ |-------|----------|--------|-------------|
445
+ | Denetim logu CSV export | `GET /audit-logs/export?format=csv&from=&to=` | GET | muni_admin, central_admin |
446
+ | Rapor listesi export | `GET /work-orders/export?format=csv&...` | GET | İlgili roller |
447
+ | Global rapor export | `GET /central/reports/export?format=csv&...` | GET | central_admin |
448
+
449
+ ---
450
+
451
+ ## 🩺 Sistem Sağlığı ve Monitoring
452
+
453
+ | İşlem | Endpoint | Method | Not |
454
+ |-------|----------|--------|-----|
455
+ | Health check | `GET /health` | GET | Basit liveness |
456
+ | Readiness | `GET /ready` | GET | DB + bağımlılık kontrolü (Central SaaS Polling Hedefi) |
457
+ | Metrics (Prometheus) | `GET /metrics` | GET | Opsiyonel |
458
+
459
+ ### GET /ready (Municipal Backend Readiness Endpoint)
460
+ Central Health Monitor tarafından 1 dakikada bir çağrılan, belediye sunucu servislerinin durumunu bildiren endpoint'tir.
461
+ * **Headers:**
462
+ - `x-kentim-timestamp`: Milisaniye zaman damgası
463
+ - `x-kentim-signature`: HMAC-SHA256 Master Key ile üretilen istek imza başlığı
464
+ * **HMAC Replay Koruması:** `x-kentim-timestamp` değerinin sunucu saatinden ±5 dakika içinde olması zorunludur; bu pencere dışındaki istekler `401 Unauthorized` ile reddedilir. Replay saldırılarına karşı ek koruma için `x-kentim-nonce` (UUIDv4) headerı zorunludur; aynı nonce **PostgreSQL `hmac_nonces` tablosunda** 10 dakika TTL ile saklanır ve mükerrer gönderimler reddedilir. (Tablo periyodik temizlik jobıyla 10 dakika önceki kayıtlardan arındırılır.)
465
+ * **Response Body (200 OK):**
466
+ ```json
467
+ {
468
+ "status": "ok",
469
+ "checked_at": "2026-05-27T15:45:00Z",
470
+ "services": {
471
+ "database": "connected",
472
+ "pg_listener_active": true,
473
+ "disk_usage_percentage": 42.5
474
+ }
475
+ }
476
+ ```
477
+
478
+ ---
479
+
480
+ ## Özet
481
+
482
+ - 115+ endpoint tanımlı.
483
+ - Tüm 9 rol için net yetki ve kapsam kuralları tamamlandı.
484
+ - Gerçek zamanlı iletişim (WebSocket/SSE), event payload şemaları, dosya/medya yönetimi (yükleme + otomatik silme), push token, export ve sistem sağlığı bölümleri detaylandırıldı.
485
+ - Belge yaşayan bir dokümandır. Yeni eksiklik bulunduğunda doğrudan bu dosyaya eklenir.
486
+
487
+ *Belge — KENTİM API / Endpoint Referans Belgesi (Tamamlanmış ve Sürekli Güncellenen)*
488
+
489
+ ---
490
+
491
+ ---
492
+
493
+ ## 📝 Dahili Not Yönetimi Endpoint'leri
494
+
495
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
496
+ |-------|----------|--------|-------------|-----|
497
+ | Dahili not ekle | `POST /work-orders/:id/notes` | POST | muni_moderator, muni_department_manager, muni_team_chief, muni_admin | 15 dakika içinde düzenlenebilir |
498
+ | Dahili not düzenle | `PATCH /work-orders/:id/notes/:note_id` | PATCH | Ekleyen kullanıcı (15 dk içinde) | Sadece ilk 15 dakika |
499
+ | Dahili not sil | `DELETE /work-orders/:id/notes/:note_id` | DELETE | Ekleyen kullanıcı (15 dk içinde) | Sadece ilk 15 dakika |
500
+
501
+ ### İstek Gövdesi (Dahili Not)
502
+ ```json
503
+ {
504
+ "content": "string (max 500 karakter)",
505
+ "internal": true
506
+ }
507
+ ```
508
+
509
+ ---
510
+
511
+ ---
512
+
513
+ > **Not (Dispute Endpoints):** `/admin/disputes/...` ve `/chief/disputes/...` endpoint'leri aynı semantiği paylaşır. Detaylı davranış (Double Payout otomatik `approved_override` geçişi dahil) için bkz. `is_akislari.md` — Senkronizasyon Uyuşmazlık Havuzu bölümü ve `mimari.md` trigger tanımı.
514
+
515
+ ---
516
+
517
+ ## 📱 Mobile Cihaz & Rate Limiting Endpoint'leri
518
+
519
+ | İşlem | Endpoint | Method | Yetkili Rol | Not |
520
+ |-------|----------|--------|-------------|-----|
521
+ | Device ID doğrulama | `POST /devices/validate` | POST | Anonim | Panik butonu için cihaz doğrulama |
522
+ | Device rate limit durumu | `GET /devices/:device_id/rate-limit-status` | GET | Anonim | `/emergency/panic` için 5 sinyal limiti |
523
+
524
+ ### Device ID Doğrulama İstek Gövdesi
525
+ ```json
526
+ {
527
+ "device_id": "string (UUID format)"
528
+ }
529
+ ```
530
+
531
+ ---
532
+
533
+ ## 🔧 IoT Entegrasyon Payload Şeması
534
+
535
+ ### POST /integrations/iot/telemetry Request Body
536
+ *Kısıtlama:* Gönderilen ham JSON gövde boyutu (payload size) **maksimum 10KB** olmalıdır. Aksi takdirde istek `400 Bad Request` ile reddedilir.
537
+ ```json
538
+ {
539
+ "device_id": "uuid",
540
+ "tenant_id": "resolved from API key",
541
+ "timestamp": "ISO8601 datetime",
542
+ "raw_payload": { ... },
543
+ "integration_config": {
544
+ "mapping_rules": { ... },
545
+ "parser_script": "function parse(payload) { ... }",
546
+ "default_public": true
547
+ }
548
+ }
549
+ ```
550
+
551
+ ### Response Body (Fastify Decoupled Asenkron Kuyruklama Sonrası)
552
+ *Yanıt Kodu:* `202 Accepted`
553
+ ```json
554
+ {
555
+ "data": {
556
+ "status": "queued",
557
+ "message": "Telemetri verisi asenkron olarak PostgreSQL unlogged kuyruğuna (`iot_telemetry_queue`) alındı, pg_notify ile V8 sandbox warm isolate worker'lara yönlendirildi.",
558
+ "device_id": "uuid",
559
+ "tenant_id": "muni_a"
560
+ }
561
+ }
562
+ ```
563
+
564
+ #### 402 Grace Period (Abonelik Süresi Aşımı) Davranışı
565
+ - **Tetiklenme Koşulu:** İlgili belediyenin abonelik süresi dolduğunda (`municipalities` tablosundaki `grace_period_ends_at` tarihine kadar olan 7 günlük süre boyunca).
566
+ - **API Davranışı:** Telemetri verisi gönderildiğinde API yine **`202 Accepted`** ile yanıt verir ve veri `integration_logs` tablosuna kaydedilir (`event_type: 'DUPLICATE_SKIPPED'` veya `SUCCESS` olarak).
567
+ - **İş Akışı Kısıtı:** Eşik değerleri (threshold) aşılsa dahi, V8 sandbox parser asenkron olarak yeni bir iş emri (`work_orders`) oluşturulmasını **tetiklemez**. Yeni iş emri tetikleme mantığı grace period boyunca tamamen askıya alınır.
568
+
569
+ ---
570
+
571
+ ## 📡 WebSocket Panic Kanalı Yetki Açıklaması (Netleştirildi)
572
+
573
+ - `panic.{tenant_id}` kanalı **sadece muni_admin** rolüne görünür (kriz masası yetkisi).
574
+ - **Ekip şefi (muni_team_chief)** bu kanalı doğrudan dinleyemez; ancak `/admin/crisis-map` sayfasında push bildirimleri alır.
575
+ - **Central admin** tüm belediyelerin panic sinyallerini ayrı `panic.all` kanalından veya polling (`/central/reports?tab=heatmap`) ile izleyebilir.
576
+
577
+ ---
578
+
579
+ ## 🛡️ GDPR / KVKK API Endpoint'leri
580
+
581
+ | İşlem | Endpoint | Method | Yetkili Rol |
582
+ |-------|----------|--------|-------------|
583
+ | KVKK talep listesi | `GET /gdpr/requests?status=pending|processing|completed` | GET | muni_admin |
584
+ | Talep detayı | `GET /gdpr/requests/:id` | GET | muni_admin |
585
+ | Talep işlemeye başla | `PATCH /gdpr/requests/:id/process` | PATCH | muni_admin | İndeksli `citizen_id` kolonu üzerinden asenkron regex maskelemeyi başlatır |
586
+ | Talebi tamamla | `PATCH /gdpr/requests/:id/complete` | PATCH | muni_admin |
587
+
588
+ ### KVKK Talep İşleme Akışı
589
+ 1. Admin panelinden talep onaylandığında `/gdpr/requests/:id/process` çağrılır
590
+ 2. Sistem anonimleştirme job'ını başlatır (audit_logs maskeleme dahil)
591
+ 3. Job tamamlandığında `/gdpr/requests/:id/complete` ile `status=completed` ve `anonymized_at` set edilir
592
+
593
+ ---
594
+
595
+ ## Analitik Takip Endpoint Standartları ve API Olgunluğu (Eksikler Giderildi)
596
+
597
+ KENTİM platformunun arayüz seviyesindeki modal navigasyonunu, vatandaş oylamalarını ve iş akış metriklerini izlemek amacıyla aşağıdaki analitik event toplama endpoint'i sisteme entegre edilmiştir:
598
+
599
+ ### 1. Analitik Olay Toplayıcı (Analytics Event Collector)
600
+
601
+ * **Endpoint:** `POST /v1/analytics/events`
602
+ * **Metot:** `POST`
603
+ * **Yetki:** Anonim veya citizen (JWT). JWT varsa token'dan otomatik `citizen_id` eşleştirilir.
604
+ * **Header:** `x-idempotency-key` zorunlu değildir (analitik verilerinde mükerrerlik kritik olmadığı için).
605
+
606
+ **İstek Gövdesi (Request Body):**
607
+ ```json
608
+ {
609
+ "event": "modal_open | report_submit_success | support_vote_added | idea_vote_submitted | duplicate_check_triggered | qa_loop_triggered",
610
+ "timestamp": "2026-05-26T20:15:00Z",
611
+ "payload": {
612
+ "modal_type": "report-new", // event = modal_open ise
613
+ "tracking_code": "KENT-34-ABC12", // varsayılan olarak opsiyonel
614
+ "source": "map_pin_click",
615
+ "category_id": "uuid", // event = report_submit_success ise
616
+ "is_anonymous": true,
617
+ "direction": 1, // event = idea_vote_submitted ise
618
+ "qa_work_order_id": "uuid" // event = qa_loop_triggered ise
619
+ }
620
+ }
621
+ ```
622
+
623
+ **Yanıt (202 Accepted):**
624
+ ```json
625
+ {
626
+ "data": {
627
+ "status": "queued",
628
+ "message": "Olay işlenmek üzere kuyruğa alındı"
629
+ }
630
+ }
631
+ ```
632
+ *(Not: Analitik istekleri API thread'ini meşgul etmemek için HTTP 202 statüsüyle asenkron olarak **PostgreSQL unlogged tablosu** (`analytics_event_queue`) üzerine `INSERT` + `pg_notify` ile kuyruğa yazılır ve ayrı bir worker process tarafından asenkron işlenir.)*
633
+
634
+ **Sonuç:** KENTİM API / Endpoint Referans dokümantasyonu, tüm operasyonel, analitik ve hata edge-case endpoint'leriyle beraber %100 oranında tamamlanmıştır. Bilinen hiçbir eksiklik bulunmamaktadır.
635
+
636
+ ---
637
+
638
+ ## V1 Yol Haritası Kapsamındaki API Genişletmeleri (Audit & Locations)
639
+
640
+ ### 1. Coğrafi Hiyerarşi Sorgulama (Lokasyon Sözlüğü)
641
+ Yeni belediye kayıtlarında ve saha organizasyonu formlarında dropdown'ları besleyen dinamik lokasyon servisidir.
642
+
643
+ * **Endpoint:** `GET /v1/central/locations/hierarchy`
644
+ * **Metot:** `GET`
645
+ * **Yetki:** Anonim / `central_admin` / `muni_admin` (Serbest Erişim)
646
+ * **Query Parametreleri:**
647
+ - `type` (Opsiyonel): `province` veya `district`
648
+ - `parent_id` (Opsiyonel): Alt ilçeleri getirmek için üst ilin ID'si (UUIDv4)
649
+ - `page`, `limit` (Opsiyonel): Varsayılan `limit=100`
650
+
651
+ #### Başarılı Yanıt Örneği (İlleri Getirme - `GET /v1/central/locations/hierarchy?type=province`):
652
+ ```json
653
+ {
654
+ "data": [
655
+ {
656
+ "id": "a2e564ad-43ab-41c1-90a1-24c568ad9cf1",
657
+ "name": "İstanbul",
658
+ "parent_id": null,
659
+ "type": "province",
660
+ "slug": "istanbul",
661
+ "created_at": "2026-05-24T12:00:00Z"
662
+ },
663
+ {
664
+ "id": "b8f675bc-54bc-52d2-01a2-35d679be0df2",
665
+ "name": "İzmir",
666
+ "parent_id": null,
667
+ "type": "province",
668
+ "slug": "izmir",
669
+ "created_at": "2026-05-24T12:00:00Z"
670
+ }
671
+ ],
672
+ "meta": {
673
+ "page": 1,
674
+ "limit": 100,
675
+ "total": 81,
676
+ "has_more": false
677
+ }
678
+ }
679
+ ```
680
+
681
+ #### Başarılı Yanıt Örneği (Seçilen İlin İlçelerini Getirme - `GET /v1/central/locations/hierarchy?parent_id=a2e564ad-43ab-41c1-90a1-24c568ad9cf1`):
682
+ ```json
683
+ {
684
+ "data": [
685
+ {
686
+ "id": "c1f886de-65cd-63e3-12b3-46e780cf1ef3",
687
+ "name": "Kadıköy",
688
+ "parent_id": "a2e564ad-43ab-41c1-90a1-24c568ad9cf1",
689
+ "type": "district",
690
+ "slug": "kadikoy",
691
+ "created_at": "2026-05-24T12:05:00Z"
692
+ },
693
+ {
694
+ "id": "d2e997ef-76de-74f4-23c4-57f891df2f04",
695
+ "name": "Beşiktaş",
696
+ "parent_id": "a2e564ad-43ab-41c1-90a1-24c568ad9cf1",
697
+ "type": "district",
698
+ "slug": "besiktas",
699
+ "created_at": "2026-05-24T12:05:00Z"
700
+ }
701
+ ],
702
+ "meta": {
703
+ "page": 1,
704
+ "limit": 100,
705
+ "total": 39,
706
+ "has_more": false
707
+ }
708
+ }
709
+ ```
710
+
711
+ ---
712
+
713
+ ### 2. Merkezi Denetim Logları Sorgulama (SaaS Seviyesi)
714
+ SaaS yöneticilerinin global işlemlerini, abonelik ve belediye güncellemelerini JSONB old/new formatında yansıtan log servisidir.
715
+
716
+ * **Endpoint:** `GET /v1/central/audit-logs`
717
+ * **Metot:** `GET`
718
+ * **Yetki:** `central_admin` (Korumalı JWT Bearer)
719
+ * **Query Parametreleri:**
720
+ - `event_type` (Opsiyonel): `MUNICIPALITY_REGISTERED`, `LICENSE_SUSPENDED`, `GLOBAL_CONFIG_UPDATED` vb.
721
+ - `municipality_id` (Opsiyonel): UUID formatında hedef belediye filtresi
722
+ - `page`, `limit` (Opsiyonel): Varsayılan `limit=25` (Offset bazlı)
723
+
724
+ #### Başarılı Yanıt Örneği (Lisans Güncelleme Diff Logu):
725
+ ```json
726
+ {
727
+ "data": [
728
+ {
729
+ "id": "e814a0a5-f375-4089-9a7c-65cd945c7e01",
730
+ "central_user_id": "8b9e67ab-43ab-41c1-90a1-24c568ad9cf1",
731
+ "municipality_id": "7ca64e7c-a49e-4e4b-bb12-9cfa2bde3431",
732
+ "event_type": "LICENSE_UPDATED",
733
+ "ip_address": "195.175.25.10",
734
+ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
735
+ "payload_old": {
736
+ "license_status": "active",
737
+ "license_expires_at": "2026-05-24T23:59:59Z",
738
+ "grace_period_ends_at": null
739
+ },
740
+ "payload_new": {
741
+ "license_status": "active",
742
+ "license_expires_at": "2027-05-24T23:59:59Z",
743
+ "grace_period_ends_at": null
744
+ },
745
+ "created_at": "2026-05-25T14:30:00Z"
746
+ }
747
+ ],
748
+ "meta": {
749
+ "page": 1,
750
+ "limit": 25,
751
+ "total": 540,
752
+ "has_more": true
753
+ }
754
+ }
755
+ ```
756
+
757
+ ---
758
+
759
+ ### 3. Belediye Denetim Logları Sorgulama (Tenant Seviyesi)
760
+ Belediye panelinde yöneticilerin gerçekleştirdiği tüm durum makinesi güncellemelerini, personel atamalarını ve geofence onaylarını JSONB formatında yansıtan log servisidir.
761
+
762
+ * **Endpoint:** `GET /v1/admin/audit-logs`
763
+ * **Metot:** `GET`
764
+ * **Yetki:** `muni_admin` (Korumalı JWT Bearer + RLS Otomatik Kapsam)
765
+ * **Query Parametreleri:**
766
+ - `event_type` (Opsiyonel): `WORK_ORDER_STATUS_CHANGED`, `GEOFENCE_BYPASS_APPROVED`, `DISPUTE_RESOLVED` vb.
767
+ - `citizen_id` (Opsiyonel): UUID formatında vatandaş filtresi (KVKK regex maskeleme denetimleri için kısmi indeks ile optimize)
768
+ - `work_order_id` (Opsiyonel): UUID formatında iş emri filtresi
769
+ - `page`, `limit` (Opsiyonel): Varsayılan `limit=25` (Offset bazlı)
770
+
771
+ #### Başarılı Yanıt Örneği (İş Emri Durum Değişikliği / Şef Onayı Bypass Logu):
772
+ ```json
773
+ {
774
+ "data": [
775
+ {
776
+ "id": "7fa2bde3-a49e-4e4b-bb12-9cfa2bde3499",
777
+ "tenant_id": "muni_a",
778
+ "user_id": "3b9e67ab-43ab-41c1-90a1-24c568ad9cf9",
779
+ "citizen_id": "1c7e99ab-62cd-41e1-b0e2-88d568ad9c88",
780
+ "work_order_id": "4da64e7c-a49e-4e4b-bb12-9cfa2bde3422",
781
+ "event_type": "MANUAL_APPROVAL_BY_MANAGER",
782
+ "ip_address": "85.105.42.112",
783
+ "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
784
+ "payload_old": {
785
+ "status": "pending_chief_approval",
786
+ "is_manager_bypassed": false,
787
+ "resolved_at": null
788
+ },
789
+ "payload_new": {
790
+ "status": "resolved",
791
+ "is_manager_bypassed": true,
792
+ "resolved_at": "2026-05-26T16:45:00Z"
793
+ },
794
+ "created_at": "2026-05-26T16:45:00Z"
795
+ }
796
+ ],
797
+ "meta": {
798
+ "page": 1,
799
+ "limit": 25,
800
+ "total": 12850,
801
+ "has_more": true
802
+ }
803
+ }
804
+ ```
805
+
806
+ ---
807
+
808
+ ## V1 Yol Haritası Kapsamındaki API Genişletmeleri (Ek Netleştirmeler)
809
+
810
+ ### 1. Halka Açık Genel İstatistik Şeması (`GET /v1/public/statistics`)
811
+ Ana sayfa haritasında sol alt köşede yer alan şeffaf istatistik panelini (Public Statistics Widget) besleyen uçu temsil eder.
812
+ * **Endpoint:** `GET /v1/public/statistics`
813
+ * **Metot:** `GET`
814
+ * **Yetki:** Anonim / Herkese Açık
815
+ * **Query Parametreleri:**
816
+ - `municipality_id` (Opsiyonel): Belirli bir belediyeye ait metrikleri çekmek için belediye UUID'si. Gönderilmezse, sistem otomatik olarak kullanıcının coğrafi konumuna (IP veya GPS) göre en yakın belediyenin verilerini döndürmeye çalışır; bulunamazsa `400 Bad Request` hatası döner.
817
+
818
+ #### Başarılı Yanıt Örneği (`200 OK`):
819
+ ```json
820
+ {
821
+ "data": {
822
+ "resolved_count": 14205,
823
+ "in_progress_count": 342,
824
+ "support_count": 89412,
825
+ "sla_compliance_rate": 94.2,
826
+ "average_resolution_time_hours": 28.5
827
+ },
828
+ "meta": {
829
+ "calculated_at": "2026-05-27T15:00:00Z",
830
+ "ttl_seconds": 300
831
+ }
832
+ }
833
+ ```
834
+
835
+ ### 2. `POST /reports` İstek Gövdesine Kronik Rapor Alanı (`parent_reopened_work_order_id`)
836
+ Vatandaşın aynı sorun için yeniden açma (reopen) limiti (maksimum 2) bittiğinde ve yeni bir rapor oluşturduğunda, bu yeni raporu eski rapor zinciriyle asenkron bağlamak amacıyla kullanılan opsiyonel alandır.
837
+ * **Endpoint:** `POST /reports`
838
+ * **İstek Gövdesi (Request Body) - Örnek:**
839
+ ```json
840
+ {
841
+ "category_id": "uuid",
842
+ "latitude": 41.0082,
843
+ "longitude": 28.9784,
844
+ "description": "Yol üzerinde derin çukur oluşmuş, araçlar için tehlike saçıyor.",
845
+ "photo_urls": [
846
+ "https://storage.kentim.com.tr/muni_a/reports/uuid_1.jpg"
847
+ ],
848
+ "is_anonymous": false,
849
+ "parent_reopened_work_order_id": "4da64e7c-a49e-4e4b-bb12-9cfa2bde3422"
850
+ }
851
+ ```
852
+
853
+ ### 3. Cihaz / Push Token Kaydetme Yetkisi (`POST /devices` ve `DELETE /devices/:device_id`)
854
+ Vatandaşlar da mobil push bildirimi tercih edebileceğinden, token kaydetme ve cihaz yönetimi `citizen` rolünü de kapsayacak şekilde genişletilmiştir.
855
+ * **Yol:** `POST /devices` ve `DELETE /devices/:device_id`
856
+ * **Yetkili Roller:** `muni_worker`, `muni_team_chief`, `citizen` (JWT yetkilendirmesiyle kiracı ve kullanıcı tespiti otomatik yapılır)
857
+
858
+ ### 4. Admin Kesin Atama (`is_force_assigned = true`) Yetki Sınırları ve Kilit Politikası
859
+ * **Açıklama:** Belediye admini tarafından `PATCH /work-orders/:id/force-assign` endpoint'i üzerinden yapılan "Kesin Atama" (Force Assign) işlemi, iş emrine kalıcı olarak `is_force_assigned = true` flag'ini yazar.
860
+ * **Kilit Kuralı:** Bu flag aktifken:
861
+ 1. **Müdürler (`muni_department_manager`) re-dispatch (işi başka müdürlüğe iade etme/gönderme) yapamaz.**
862
+ 2. Ekiplerin sahada tıkanmaması için müdürler personel atayabilir/değiştirebilir (`reassign_worker`), işi zorla kapatabilir (`force_resolve` veya `manual_approval`) veya işi iptal edebilirler (`reject`).
863
+ 3. Sadece `muni_admin` (Belediye Sistem Yöneticisi) işin atama durumunu ve müdürlük yönlendirmesini ezebilir (override) veya değiştirebilir.
864
+
865
+ ### 5. `GET /evacuation-routes` Afet Modu Bağımlılığı
866
+ * **Açıklama:** Tahliye ve kaçış güzergahlarını listeleyen bu servis yalnızca **Afet Modu aktif olduğunda** anlamlı coğrafi veri döner.
867
+ * **Normal Mod Davranışı:** Afet modu aktif değilken yapılan çağrılarda servis hata fırlatmaz, performans ve kararlılık açısından **boş liste (`{ "data": [] }`)** döndürerek graceful fallback sağlar.
868
+
869
+ ### 6. Takip Kodu Formatı ve Validasyon Kuralları
870
+ * **Format:** `KENT-[plaka]-[rastgele_alfanumerik_8_hane]`
871
+ * **Örnek:** `KENT-34-A78C9E42`
872
+ * **Doğrulama:** `GET /reports/track/:trackingCode` endpoint'inde iletilen kodun formatı Regex (`^KENT-\d{2}-[A-Z0-9]{8}$`) ile doğrulanır. Plaka hanesi (örn: 34), veritabanında sorgulama yapılırken kiracı (tenant) çözümlemesini hızlandırmak ve doğrudan ilgili belediyenin veritabanı şemasına yönlendirme yapmak için reverse proxy/gateway katmanında mikro-indeks olarak kullanılır.
873
+
874
+ ### 7. `GET /public/reports` Nearby ve Radius Parametresi Davranışı
875
+ * **Açıklama:** Vatandaşın haritadan konum seçtiğinde mükerrer rapor kontrolü yapmasını sağlayan mesafe bazlı yakın pinlerin çekilmesi için standart `GET /public/reports/nearby?lat=&lng=&radius=` ucu kullanılır:
876
+ * **Uç Nokta:** `GET /public/reports/nearby?lat=41.0082&lng=28.9784&radius=100` (Metre cinsinden tam sayı)
877
+ * Bu endpoint, bbox hesaplama karmaşasını ortadan kaldırarak belirtilen koordinatın dairesel çevresindeki (PostGIS `ST_DWithin` kullanılarak) aktif pinleri döner.
878
+
879
+ ---
880
+
881
+ ### 8. Dinamik Statik İçerik Yönetimi (`/static-contents`)
882
+
883
+ Merkezi SaaS paneli üzerinden yönetilen statik içeriklerin (`Hakkımızda`, `KVKK`, `Gizlilik` vb.) API şemaları aşağıda detaylandırılmıştır.
884
+
885
+ **Ek Öneri (V1 Kapsamı):**
886
+ Public Web Yol Haritası (MVP ve V1 içerikleri) **sadece Merkez Panel (Central Admin)** üzerinden yönetilebilir. Belediye paneli bu içeriğe erişemez. `central_static_contents` tablosuna `public_web_roadmap_mvp` ve `public_web_roadmap_v1` key'leri eklenerek Central Admin tarafından güncellenebilir.
887
+
888
+ #### `GET /v1/public/static-contents/:key`
889
+ Halka açık olarak statik içeriği anahtarına (`key`) göre getirir.
890
+
891
+ * **Başarılı Yanıt Örneği (`200 OK`):**
892
+ ```json
893
+ {
894
+ "data": {
895
+ "key": "privacy",
896
+ "title": "Gizlilik Politikası",
897
+ "content": "<p>Şirketimiz, kullanıcılarımızın kişisel verilerinin gizliliğine son derece önem vermektedir...</p>",
898
+ "updated_at": "2026-05-20T10:00:00Z"
899
+ }
900
+ }
901
+ ```
902
+
903
+ #### `PUT /v1/central/static-contents/:key`
904
+ Merkezi adminin (`central_admin`) statik içeriği güncellemesini sağlar.
905
+
906
+ * **Yetki:** `central_admin`
907
+ * **İstek Gövdesi (Request Body):**
908
+ ```json
909
+ {
910
+ "title": "Yeni Gizlilik Politikası",
911
+ "content": "<h1>Güncellenmiş Metin</h1><p>...</p>"
912
+ }
913
+ ```
914
+ * **Başarılı Yanıt Örneği (`200 OK`):** Güncellenmiş içeriğin tamamı döner (yukarıdaki GET yanıtı gibi).
915
+
916
+ ---
917
+
918
+ ### 9. `POST /reports` Fotoğraf Yükleme Yöntemleri ve Tam Şema
919
+
920
+ Vatandaşların sorun bildirirken fotoğraf eklemesi için iki yöntem desteklenir:
921
+
922
+ * **a) Direct Upload (Multipart/Form-Data):** Form verileri (`category_id`, `description` vb.) ile birlikte maksimum 5 adet görsel dosyası (her biri max 5MB) tek bir `multipart/form-data` isteğinde sunucuya gönderilir. İstemci tarafında en basit yöntem budur.
923
+ * **b) Pre-signed URL (Gateway-Optimized):** Özellikle mobil ve yavaş ağlarda daha güvenilir olan bu yöntemde:
924
+ 1. İstemci önce `POST /v1/reports/upload-urls` endpoint'ine dosya adları ve tiplerini içeren bir istek göndererek S3 (veya uyumlu storage) için geçici, güvenli yükleme URL'leri alır.
925
+ 2. İstemci, aldığı bu URL'lere dosyaları doğrudan (genellikle `PUT` metodu ile) yükler.
926
+ 3. Yükleme tamamlandıktan sonra, standart `POST /reports` isteğinin gövdesindeki `photo_urls` dizisine bu dosyaların sunucudaki mutlak (absolute) URL'lerini ekleyerek raporu oluşturur.
927
+
928
+ #### `POST /v1/reports/upload-urls`
929
+ Pre-signed URL almak için kullanılır.
930
+
931
+ * **Yetki:** `citizen` / Anonim
932
+ * **İstek Gövdesi:**
933
+ ```json
934
+ {
935
+ "file_name": "photo.jpg",
936
+ "content_type": "image/jpeg"
937
+ }
938
+ ```
939
+ * **Başarılı Yanıt (`200 OK`):**
940
+ ```json
941
+ {
942
+ "data": {
943
+ "upload_url": "https://s3.eu-central-1.amazonaws.com/kentim-uploads/...",
944
+ "file_url": "https://storage.kentim.com.tr/muni_a/reports/..."
945
+ }
946
+ }
947
+ ```
948
+
949
+ #### Tam `POST /reports` İstek Gövdesi (JSON)
950
+ ```json
951
+ {
952
+ "category_id": "c1f886de-65cd-63e3-12b3-46e780cf1ef3",
953
+ "latitude": 41.0082,
954
+ "longitude": 28.9784,
955
+ "description": "Yol üzerindeki çukur araçlar için tehlikeli.",
956
+ "photo_urls": [
957
+ "https://storage.kentim.com.tr/muni_a/reports/c1f886de-..../photo1.jpg",
958
+ "https://storage.kentim.com.tr/muni_a/reports/c1f886de-..../photo2.jpg"
959
+ ],
960
+ "is_anonymous": true,
961
+ "parent_reopened_work_order_id": null
962
+ }
963
+ ```
964
+ * **`is_anonymous` Flag:** `true` olarak ayarlandığında, raporu oluşturan vatandaşın adı ve soyadı gibi kimlik bilgileri halka açık arayüzlerde ve rapor detaylarında maskelenerek gizlenir. `false` ise veya alan gönderilmezse, giriş yapmış kullanıcının profilindeki adı raporda görünür.
965
+
966
+ ---
967
+
968
+ ### 10. Fikir Oylama/Oluşturma için MERNIS Entegrasyonu ve JWT Claim
969
+
970
+ Fikir oylama (`POST /ideas/:id/vote`) ve fikir oluşturma (`POST /ideas`) endpoint'lerinin sahte hesaplarla manipüle edilmesini önlemek amacıyla, bu işlemleri gerçekleştirecek vatandaşların T.C. Kimlik Numaralarını doğrulamış olması zorunludur.
971
+
972
+ * **Akış:**
973
+ 1. Kullanıcı, profil ayarlarından `POST /auth/tc-kimlik/verify` endpoint'ini tetikleyen formu doldurur (TCKN, Ad, Soyad, Doğum Yılı).
974
+ 2. Başarılı MERNIS doğrulaması sonucunda, kullanıcının aktif JWT token'ı yenilenir ve payload'una `"is_identity_verified": true` claim'i eklenir.
975
+ 3. API Gateway, `/ideas` ve `/ideas/:id/vote` endpoint'lerine gelen isteklerde bu claim'in varlığını ve `true` olmasını kontrol eder. Claim yoksa veya `false` ise istek `403 Forbidden` hatası ile reddedilir.
976
+
977
+ ---
978
+
979
+ ### 11. Eksik Public Endpoint Yanıt Şemaları
980
+
981
+ Aşağıda, daha önce detaylı şeması verilmeyen bazı public endpoint'lerin başarılı yanıt (`200 OK`) örnekleri bulunmaktadır.
982
+
983
+ #### `GET /evacuation-routes`
984
+ Afet anında en yakın toplanma alanlarına giden rotaları listeler.
985
+ ```json
986
+ {
987
+ "data": [
988
+ {
989
+ "id": "route-uuid-1",
990
+ "name": "Acil Toplanma Alanı A'ya Giden Rota",
991
+ "geojson": {
992
+ "type": "LineString",
993
+ "coordinates": [ [28.97, 41.00], [28.98, 41.01], [28.99, 41.02] ]
994
+ },
995
+ "estimated_distance_meters": 1200,
996
+ "estimated_time_minutes": 15
997
+ }
998
+ ]
999
+ }
1000
+ ```
1001
+
1002
+ #### `GET /recycling-points`
1003
+ Geri dönüşüm noktalarını listeler.
1004
+ ```json
1005
+ {
1006
+ "data": [
1007
+ {
1008
+ "id": "point-uuid-1",
1009
+ "name": "Kadıköy Belediyesi Cam Atık Kumbarası",
1010
+ "type": ["glass", "paper"],
1011
+ "address": "Caferağa, Mühürdar Cd. No:50, 34710 Kadıköy/İstanbul",
1012
+ "location": { "type": "Point", "coordinates": [29.02, 40.99] },
1013
+ "operating_hours": "7/24"
1014
+ }
1015
+ ]
1016
+ }
1017
+ ```
1018
+
1019
+ #### `GET /announcements` & `GET /announcements/:id`
1020
+ Belediye duyurularını listeler ve detayını verir.
1021
+ ```json
1022
+ {
1023
+ "data": {
1024
+ "id": "announce-uuid-1",
1025
+ "title": "Su Kesintisi Uyarısı",
1026
+ "content": "Altyapı çalışmaları nedeniyle 1 Haziran 2026 tarihinde belirtilen mahallelerde su kesintisi yaşanacaktır.",
1027
+ "type": "warning",
1028
+ "published_at": "2026-05-28T10:00:00Z",
1029
+ "expires_at": "2026-06-02T10:00:00Z",
1030
+ "affected_districts": ["Caferağa", "Osmanağa"]
1031
+ }
1032
+ }
1033
+ ```
1034
+
1035
+ #### `GET /ideas` & `GET /ideas/:id`
1036
+ Vatandaş fikirlerini listeler ve detayını verir.
1037
+ ```json
1038
+ {
1039
+ "data": {
1040
+ "id": "idea-uuid-1",
1041
+ "title": "Mahallemize Kedi Evi Yapılsın",
1042
+ "description": "Sokak hayvanları için kış aylarında korunabilecekleri bir kedi evi yapılmasını öneriyorum.",
1043
+ "category": "environment",
1044
+ "status": "approved",
1045
+ "vote_summary": { "upvotes": 125, "downvotes": 12, "net": 113 },
1046
+ "author": { "name": "Ayşe Y." },
1047
+ "created_at": "2026-04-15T14:00:00Z",
1048
+ "official_reply": {
1049
+ "content": "Değerli öneriniz için teşekkür ederiz. Konu Park ve Bahçeler Müdürlüğümüz tarafından değerlendirmeye alınmıştır.",
1050
+ "replied_at": "2026-05-10T11:00:00Z"
1051
+ }
1052
+ }
1053
+ }
1054
+ ```
1055
+
1056
+ #### `POST /ideas`
1057
+ Yeni bir fikir oluşturma istek gövdesi.
1058
+ ```json
1059
+ {
1060
+ "title": "Bisiklet Yolları Artırılsın",
1061
+ "description": "Ulaşımı kolaylaştırmak ve çevreyi korumak adına bisiklet yollarının yaygınlaştırılmasını talep ediyorum.",
1062
+ "category": "transportation",
1063
+ "related_district": "Kadıköy"
1064
+ }
1065
+ ```
1066
+
1067
+ #### `POST /ideas/:id/vote`
1068
+ Bir fikre oy verme istek gövdesi.
1069
+ ```json
1070
+ {
1071
+ "direction": 1
1072
+ }
1073
+ ```
1074
+ * `direction`: `1` (destekliyorum / upvote), `-1` (karşıyım / downvote).
1075
+
1076
+ ---
1077
+
1078
+ ### 12. `GET /public/reports` `status` ve `public_id` Parametre Detayları
1079
+
1080
+ #### `status` Parametresi
1081
+ `GET /public/reports?status=...` endpoint'inde kullanılabilecek geçerli durum (`status`) değerleri ve anlamları:
1082
+
1083
+ * `moderation`: Rapor oluşturuldu, moderatör incelemesinde.
1084
+ * `dispatched_to_department`: Moderatör tarafından ilgili müdürlüğe havale edildi.
1085
+ * `assigned_to_team`: Müdür tarafından bir ekibe (şefe) atandı.
1086
+ * `assigned_to_worker`: Şef tarafından bir saha personeline atandı.
1087
+ * `in_progress`: Saha personeli işi üstlendi ve çalışma başlattı.
1088
+ * `pending_chief_approval`: Saha personeli işi tamamladı, şefin fotoğraf onayını bekliyor.
1089
+ * `resolved`: Şef tarafından onaylandı ve çözüldü.
1090
+
1091
+ **Not:** `pending` (vatandaş tarafından oluşturulmuş ancak henüz sisteme tam düşmemiş taslak) ve `rejected` (reddedilmiş) durumundaki raporlar halka açık haritada gösterilmez ve bu API'den dönülmez.
1092
+
1093
+ #### `public_id` Tanımı
1094
+ `GET /public/reports/:public_id` endpoint'indeki `:public_id` değeri, ilgili raporla ilişkili olan **iş emrinin (`work_order`)** benzersiz **UUIDv4** kimliğidir. Bu, vatandaşın takip kodundan farklı, sistemsel bir kimliktir.
1095
+
1096
+ ---
1097
+
1098
+ ### 13. `GET /citizen/support-votes` Yanıt Şeması
1099
+ Vatandaşın "+1 Beni de Etkiliyor" diyerek desteklediği raporları listeleyen endpoint'in örnek yanıt şeması.
1100
+
1101
+ * **Başarılı Yanıt Örneği (`200 OK`):**
1102
+ ```json
1103
+ {
1104
+ "data": [
1105
+ {
1106
+ "work_order": {
1107
+ "public_id": "work-order-uuid-1",
1108
+ "tracking_code": "KENT-34-XYZ123",
1109
+ "title": "Kırık kaldırım taşı",
1110
+ "category": "Yol ve Kaldırım",
1111
+ "status": "in_progress"
1112
+ },
1113
+ "voted_at": "2026-05-15T18:30:00Z"
1114
+ },
1115
+ {
1116
+ "work_order": {
1117
+ "public_id": "work-order-uuid-2",
1118
+ "tracking_code": "KENT-34-ABC456",
1119
+ "title": "Parktaki aydınlatma çalışmıyor",
1120
+ "category": "Park ve Bahçeler",
1121
+ "status": "resolved"
1122
+ },
1123
+ "voted_at": "2026-05-10T11:00:00Z"
1124
+ }
1125
+ ],
1126
+ "meta": {
1127
+ "page": 1,
1128
+ "limit": 20,
1129
+ "total": 2,
1130
+ "has_more": false
1131
+ }
1132
+ }
1133
+ ```
1134
+
1135
+ *Belge — KENTİM API / Endpoint Referans Belgesi (Tamamlanmış ve Sürekli Güncellenen)*
1136
+
1137
+