| SPA | Klasör | Dev port | Keycloak realm | Client |
|---|---|---|---|---|
| Website (vatandaş) | src/DiyanetCleanArchitecture.Presentation.Website | 3000 | diyanet-vatandas-dev-realm | diyanet-website |
| Admin (backoffice) | src/DiyanetCleanArchitecture.Presentation.Admin | 3001 | diyanet-yonetim-dev-realm | diyanet-admin |
Teknoloji yığını
- React 19 + react-dom 19, Vite 6, TypeScript 5.8
- react-router-dom 7 — routing (Admin’de
basenameimport.meta.env.BASE_URL’den okunur) - zustand 5 — global state (
store/) - @tanstack/react-query 5 — server state / cache
- axios 1.8 — HTTP client (
lib/axios.ts) - keycloak-js 26 — SSO / OAuth2 + PKCE
- Radix UI primitive’leri + Tailwind CSS 3 + class-variance-authority + tailwind-merge
- lucide-react (ikon), sonner (toast), recharts (grafik, Admin), @tanstack/react-table (tablo, Admin)
Klasör yapısı
lib/axios.ts — merkezi HTTP client
Tek bir Axios instance (baseURL = import.meta.env.VITE_API_BASE_URL, timeout: 30s) iki interceptor ile gelir.
Request interceptor — her isteğe X-App-Id header’ı (VITE_APP_ID) ve Keycloak Bearer token’ı ekler. Token 30 saniyeden kısa sürede expire olacaksa otomatik yeniler:
safeKeycloakLogin() çağrılır — bu, 10 saniyelik throttle ile redirect-loop’u önler (yanlış audience/issuer durumunda login → SSO → 401 → login döngüsüne girmeyi engeller).
Response interceptor — RFC 7807 (application/problem+json) hatalarını yakalar, status’a göre toast gösterir ve ProblemDetailsError fırlatır:
- 401 — token reddedildi. Otomatik redirect YOK (loop riski); kullanıcıya “Giriş Yap” action butonlu toast.
- 403 — ProblemDetails’taki
reasonalanınot_invitedveyaunknown_realmise (kullanıcı Keycloak’a girdi ama bu app’te davetli/tanımlı değil) “Çıkış Yap” önerili özel mesaj gösterilir. Diğer 403’ler “yetkiniz yok” toast’ı. - 422 —
errorsalanı varsa toast atılmaz (component inline gösterir). - 400 / 404 / 409 / 429 / 5xx — duruma özel toast’lar.
supportContext) kaydedilir — destek modalı snapshot’ında traceId ile görünür.
store/auth.store.ts — Keycloak oturum yönetimi
zustand store, uygulama açılırken bir kez initialize() çalıştırır:
provisionKeycloakSession(keycloak.token)— backend’e token gönderilir; kullanıcı local DB’ye eşlenir (davetli ise Draft/Pending → Active). Bu çağrı olmadan korumalı her endpoint 403 döner (ActiveAccountAuthorizationHandler).useMeStore.getState().bootstrap()—/meçekilir, tüm sayfalar (avatar, profil, role-bazlı menü) buradan okur.
accessDeniedReason ayarlanır: not_invited / account_blocked / unknown_realm / provision_error / unknown. Roller, hem realm_access.roles hem resource_access[clientId].roles’ten birleştirilir (SuperAdmin/Admin/Staff client-level gelir).
VITE_* ortam değişkenleri
Vite, VITE_ öneki ile başlayan env’leri build sırasında bundle’a gömer:
| Değişken | Açıklama |
|---|---|
VITE_API_BASE_URL | API adresi (dev: http://localhost:5005; stage/prod: boş — aynı origin /api/) |
VITE_KEYCLOAK_URL | Tarayıcının ulaşacağı Keycloak URL’i (external) |
VITE_KEYCLOAK_REALM | Realm adı |
VITE_KEYCLOAK_CLIENT_ID | Client ID |
VITE_APP_ID | X-App-Id header’ı (audit/proje izleme) |
VITE_ADMIN_ROLES | Admin erişimi için izinli roller (Admin SPA, örn. Admin,SuperAdmin) |
VITE_BASE_PATH | SPA base path (Admin) — dev /, stage/prod /admin/ |
Dockerfile (iki aşamalı)
node:22-alpine ile build, nginx:1.27-alpine ile serve:
Admin SPA prod’da
/admin/ altında serve edilir. VITE_BASE_PATH=/admin/ ile build edilir; vite.config.ts bunu base olarak okur ve nginx BASE_PATH env’iyle template’i render eder. Tüm Keycloak redirect URI’ları origin + base path kullanır — aksi halde logout sonrası kullanıcı website’a düşer ve silent-check-sso.html 404 olur.npm script’leri
Sonraki adımlar
OpenAPI / client üretimi
npm run generate-api detayları.
Controller'lar
SPA’ların çağırdığı endpoint aileleri.
Keycloak ortamları
Realm ve client yapılandırması.
Dev Docker
SPA’ları container’da çalıştırma.