Files
sase.tr/knowledge.md
Fusion 561b02b3d2 feat(FN-343): remove lingering iyzico references after Stripe migration (+1 more)
Commits merged:
- chore(FN-343): remove lingering iyzico references from docs, config, and scripts
- feat(FN-343): remove lingering iyzico references after Stripe migration

Files changed:
CLAUDE.md                                    | 10 +++++-----
 README.md                                    |  2 +-
 apps/api/src/database/schema/core.ts         |  1 +
 apps/web/src/messages/en.json                |  1 -
 apps/web/src/messages/tr.json                |  1 -
 apps/web/src/routes/dashboard/billing.tsx    |  6 +++---
 docker-compose.coolify.yml                   |  5 ++---
 docs/INDEX.md                                | 27 ++++++++++++-------------
 knowledge.md                                 | 30 ++++++++++++++--------------
 packages/shared/src/constants/error-codes.ts |  1 -
 packages/shared/src/index.ts                 |  1 -
 packages/shared/src/types/payment.ts         | 12 +----------
 scripts/fn342-pw-verify.mjs                  |  2 +-
 scripts/validate-env.sh                      |  3 +--
 14 files changed, 43 insertions(+), 59 deletions(-)

Fusion-Task-Id: FN-343
2026-05-14 02:31:42 +00:00

1511 lines
58 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SASE v2 — Dify.ai Knowledge Base
> **Platform:** sase.tr — Türkiye otomotiv pazarı için VIN/şasi numarası sorgulama + oto parça katalog platformu.
> **URL:** https://sase.tr | **Repo:** `/home/s/ss` | **Tarih:** 2026-02-24
---
## 1. Proje Özeti
SASE v2, araç sahiplerinin VIN numarasını girerek aracın markasını, modelini ve parça kataloglarını görüntüleyebildiği abonelik tabanlı bir SaaS platformdur. Kullanıcılar aboneliklerine göre belirli markaların parça kataloglarına erişebilir; ayrıca VIN girmeden doğrudan marka → model → parça kategorisi şeklinde katalog gezinebilir (VIN-less catalog browser). Platform Türkçe araçlara ve Türkiye pazarına odaklanmıştır.
**Temel Özellikler:**
- VIN decode: 17 karakterlik VIN numarasından araç bilgisi ve parça kategorileri
- İnteraktif şema görüntüleyici: hotspot'lı parça diyagramları
- OEM kod arama ve kopyalama
- VIN-less katalog tarayıcısı (marka → model → kategori)
- Abonelik yönetimi: marka bazlı erişim kontrolü
- Stripe kart + EFT ödeme yöntemleri
- Referral sistemi
---
## 2. Tech Stack
### Backend
| Katman | Teknoloji | Versiyon |
|--------|-----------|----------|
| Framework | NestJS | 10.4 |
| Dil | TypeScript | 5.7 |
| Runtime | Node.js | 22 |
| Veritabanı | PostgreSQL | 17 |
| ORM | Drizzle ORM | 0.41 |
| Cache | Redis | 7.4 |
| Redis İstemci | ioredis | 5.4 |
| Job Queue | BullMQ | 5.30 |
| Auth | Better Auth | 1.2 |
| Dosya Depolama | MinIO (S3) | — |
| E-posta | Postal | — |
| Ödeme | Stripe | — |
| Browser Otomasyon | Playwright | 1.50 |
| HTTP İstemci | undici | 7.22 |
| Rate Limiting | @nestjs/throttler | 6.3 |
| Güvenlik Header | Helmet | 8.1 |
| Observability | OpenTelemetry | 0.212 |
### Frontend
| Katman | Teknoloji | Versiyon |
|--------|-----------|----------|
| Build Tool | Vite | 6.3 |
| UI Kütüphanesi | React | 19 |
| Router | TanStack Router | 1.120 |
| Server State | TanStack Query | 5 |
| Client State | Zustand | 5 |
| Stil | Tailwind CSS | 4 |
| Bileşen Kütüphanesi | shadcn/ui (Radix) | — |
| İkonlar | lucide-react | 0.468 |
| Toast | sileo | 0.0.7 |
| Video | Remotion | 4.x |
| Analytics | PostHog | latest |
| RUM | Grafana Faro | 2.2 |
### Genel
| Araç | Teknoloji |
|------|-----------|
| Monorepo | pnpm 10.29 + Turborepo 2 |
| Linting/Format | Biome (2-space, double quotes, semicolons, trailing commas, 100-char) |
| Test | Vitest 3, Playwright 1.50 |
| CI/CD | GitHub Actions → SSH deploy → PM2 |
| Doğrulama | Zod 3.24 |
---
## 3. Proje Yapısı
```
ss/ (repo root)
├── apps/
│ ├── api/ # NestJS backend (port 4000, prefix /api)
│ │ └── src/
│ │ ├── main.ts # Bootstrap: Helmet, CORS, rate limiting, prefix /api
│ │ ├── app.module.ts # Root modül: global guards/interceptors/filters
│ │ ├── worker.ts # BullMQ standalone worker process
│ │ ├── health.controller.ts
│ │ ├── auth/ # Better Auth entegrasyonu
│ │ ├── users/ # Kullanıcı hesap yönetimi
│ │ ├── brands/ # Marka CRUD
│ │ ├── plans/ # Fiyat planları CRUD
│ │ ├── subscriptions/ # Abonelik yaşam döngüsü
│ │ ├── payments/ # Stripe + EFT ödeme
│ │ ├── referrals/ # Referral programı
│ │ ├── vehicles/ # VIN decode + araç geçmişi
│ │ ├── categories/ # Parça kategorisi ağacı
│ │ ├── parts/ # Oto parça kataloğu
│ │ ├── catalog/ # VIN-less katalog tarayıcı (PL24 model aileleri)
│ │ ├── translations/ # Otomotiv terim çevirisi
│ │ ├── admin/ # Admin dashboard endpoint'leri
│ │ ├── analytics/ # Kullanım analitik takibi
│ │ ├── common/ # Guard, pipe, interceptor, filter, decorator
│ │ ├── config/ # Runtime konfigürasyon
│ │ ├── database/ # Drizzle ORM kurulum + şemalar
│ │ │ └── schema/ # core.ts, emex.ts, pl24.ts, relations.ts
│ │ ├── redis/ # Redis modül + servis
│ │ ├── storage/ # MinIO/S3 servisi
│ │ ├── email/ # Postal e-posta servisi
│ │ ├── jobs/ # BullMQ kuyrukleri + işleyiciler
│ │ ├── telemetry/ # OpenTelemetry SDK
│ │ └── integrations/
│ │ ├── corgi/ # Offline VIN WMI decoder
│ │ ├── pl24/ # PL24 katalog API + parser'lar
│ │ ├── parts-catalogs/ # PartsCatalogs API
│ │ ├── emex/ # EMEX scraper (Playwright)
│ │ └── vin-api/ # NHTSA VIN API fallback
│ │
│ └── web/ # Vite + React frontend (port 3000)
│ └── src/
│ ├── main.tsx # Giriş: RouterProvider, QueryClient, Faro, PostHog
│ ├── routeTree.gen.ts # Otomatik üretilen route ağacı (ELLE DÜZENLEME)
│ ├── routes/ # TanStack Router dosya tabanlı route'lar
│ ├── components/ # React bileşenleri
│ ├── hooks/ # Custom hook'lar
│ ├── stores/ # Zustand store'ları
│ ├── lib/ # Yardımcı kütüphaneler
│ └── messages/ # i18n: tr.json, en.json
├── packages/
│ ├── shared/ (@sase/shared) # Tipler, Zod şemaları, sabitler, util'ler
│ ├── config/ (@sase/config) # Zod env doğrulama şeması
│ └── ui/ (@sase/ui) # shadcn tabanlı React bileşen kütüphanesi
├── docker/ # Docker Compose (PostgreSQL, Redis, MinIO, nginx)
├── scripts/ # Deploy, debug, keşif script'leri
├── .github/workflows/ # CI (lint+typecheck+test+build) + Deploy
├── ecosystem.config.js # PM2 process konfigürasyonu
├── turbo.json # Turborepo pipeline
├── biome.json # Linter/formatter konfigürasyonu
└── pnpm-workspace.yaml # Workspace kök
```
---
## 4. Veritabanı Şeması
**ORM:** Drizzle ORM 0.41 + PostgreSQL 17
**Kural:** Tablo/kolon isimleri snake_case, TypeScript'te camelCase.
### 4.1 Core Tablolar (`apps/api/src/database/schema/core.ts`)
#### `users`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK, default random |
| name | varchar(255) | NOT NULL |
| email | varchar(255) | NOT NULL, UNIQUE |
| emailVerified | boolean | default false |
| image | text | nullable |
| role | varchar(20) | default "user" → "user" \| "admin" |
| referralCode | varchar(20) | UNIQUE, nullable |
| referredBy | uuid | FK → users.id, nullable |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `users_email_idx` (unique), `users_referral_code_idx` (unique)
---
#### `sessions` (Better Auth)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | text | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| token | text | NOT NULL, UNIQUE |
| expiresAt | timestamptz | NOT NULL |
| ipAddress | varchar(45) | nullable |
| userAgent | text | nullable |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `sessions_token_idx` (unique)
---
#### `accounts` (Better Auth OAuth)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | text | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| accountId | text | NOT NULL |
| providerId | varchar(50) | NOT NULL → "google", "email", vb. |
| accessToken | text | nullable |
| refreshToken | text | nullable |
| accessTokenExpiresAt | timestamptz | nullable |
| refreshTokenExpiresAt | timestamptz | nullable |
| scope | text | nullable |
| idToken | text | nullable |
| password | text | nullable |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `accounts_user_id_idx`
---
#### `verifications` (Better Auth e-posta doğrulama)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | text | PK |
| identifier | text | NOT NULL |
| value | text | NOT NULL |
| expiresAt | timestamptz | NOT NULL |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
---
#### `brands`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| name | varchar(100) | NOT NULL |
| slug | varchar(100) | NOT NULL, UNIQUE |
| logoUrl | text | nullable |
| isActive | boolean | default true |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
---
#### `plans`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| name | varchar(100) | NOT NULL → "1 Marka", "2 Marka", "3 Marka", "Full Paket" |
| brandCount | integer | NOT NULL → 0 = sınırsız |
| priceMonthly | integer | NOT NULL (kuruş cinsinden: 20000 = 200 TRY) |
| priceYearly | integer | NOT NULL (kuruş cinsinden) |
| isActive | boolean | default true |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
---
#### `userSubscriptions`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| planId | uuid | NOT NULL, FK → plans.id |
| status | varchar(20) | default "pending" → "pending" \| "active" \| "cancelled" \| "expired" |
| billingPeriod | varchar(10) | default "monthly" → "monthly" \| "yearly" |
| startDate | timestamptz | nullable |
| endDate | timestamptz | nullable |
| cancelledAt | timestamptz | nullable |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `user_subscriptions_user_id_idx`, `user_subscriptions_status_idx`
---
#### `userBrands` (Abonelik ↔ Marka junction tablosu)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| subscriptionId | uuid | NOT NULL, FK → userSubscriptions.id CASCADE DELETE |
| brandId | uuid | NOT NULL, FK → brands.id |
| createdAt | timestamptz | default now() |
*Index'ler:* `user_brands_user_id_idx`, `user_brands_unique_idx` (userId, subscriptionId, brandId — UNIQUE)
---
#### `payments`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| subscriptionId | uuid | NOT NULL, FK → userSubscriptions.id |
| amount | integer | NOT NULL (kuruş) |
| currency | varchar(3) | default "TRY" |
| method | varchar(20) | NOT NULL → "stripe" \| "eft" |
| status | varchar(20) | default "pending" → "pending" \| "completed" \| "failed" \| "refunded" |
| stripePaymentIntentId | text | nullable |
| eftReceiptUrl | text | nullable |
| adminNote | text | nullable |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `payments_user_id_idx`, `payments_status_idx`
---
#### `vehicles` (VIN tabanlı araç konfigürasyonu)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| vin | varchar(17) | NOT NULL, UNIQUE |
| brandId | uuid | FK → brands.id, nullable |
| brandName | varchar(100) | nullable |
| model | varchar(255) | nullable |
| year | integer | nullable |
| engine | varchar(255) | nullable |
| transmission | varchar(100) | nullable |
| bodyType | varchar(100) | nullable |
| market | varchar(100) | nullable |
| rawData | jsonb | nullable |
| source | varchar(20) | default "pl24" → "pl24" \| "emex" \| "corgi" \| "vin-api" |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `vehicles_vin_unique_idx` (unique)
---
#### `userVehicles` (Kullanıcı ↔ Araç junction)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| vehicleId | uuid | NOT NULL, FK → vehicles.id CASCADE DELETE |
| createdAt | timestamptz | default now() |
| lastAccessedAt | timestamptz | default now() |
*Index'ler:* `user_vehicles_user_vehicle_idx` (unique), `user_vehicles_user_id_idx`
---
#### `catalogVehicles` (VIN-less katalog tarama — PL24 araç kaydı)
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| source | varchar(20) | default "pl24" |
| serviceName | varchar(100) | NOT NULL (pl24 servis adı, ör. "vw_parts") |
| brandName | varchar(100) | NOT NULL |
| brandId | uuid | FK → brands.id, nullable |
| model | varchar(255) | NOT NULL |
| year | varchar(50) | nullable |
| engine | varchar(255) | nullable |
| bodyType | varchar(100) | nullable |
| transmission | varchar(100) | nullable |
| market | varchar(100) | nullable |
| serviceVehicleId | varchar(255) | NOT NULL (PL24'teki araç ID'si) |
| catalogPath | text | nullable (PL24 catalog URL yolu) |
| architecture | varchar(30) | nullable → "P5_MODERN" \| "P4_LEGACY" |
| metadata | jsonb | nullable |
| categoriesFetched | boolean | default false |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
*Index'ler:* `catalog_vehicles_source_vid_idx` (serviceName, serviceVehicleId — UNIQUE), `catalog_vehicles_brand_name_idx`, `catalog_vehicles_service_name_idx`
---
#### `categories`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| vehicleId | uuid | FK → vehicles.id CASCADE DELETE, nullable (VIN tabanlı) |
| catalogVehicleId | uuid | FK → catalogVehicles.id CASCADE DELETE, nullable (VIN-less) |
| name | varchar(500) | NOT NULL |
| nameOriginal | varchar(500) | nullable |
| parentId | uuid | nullable (self-referencing hiyerarşi için) |
| externalId | varchar(500) | nullable (PL24/EMEX'teki ID) |
| linkPath | text | nullable (PL24 API yolu) |
| linkWid | varchar(100) | nullable |
| unavailable | boolean | default false |
| source | varchar(20) | default "pl24" → "pl24" \| "emex" \| "parts-catalogs" |
| createdAt | timestamptz | default now() |
*Index'ler:* `categories_vehicle_id_idx`, `categories_catalog_vehicle_id_idx`, `categories_parent_id_idx`, `categories_vehicle_name_source_idx` (vehicleId, catalogVehicleId, name, source — UNIQUE)
---
#### `parts`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| vehicleId | uuid | FK → vehicles.id CASCADE DELETE, nullable |
| catalogVehicleId | uuid | FK → catalogVehicles.id CASCADE DELETE, nullable |
| categoryId | uuid | NOT NULL, FK → categories.id CASCADE DELETE |
| oemCode | varchar(100) | NOT NULL |
| name | varchar(500) | NOT NULL |
| nameOriginal | varchar(500) | nullable |
| description | text | nullable |
| quantity | integer | nullable |
| position | varchar(100) | nullable |
| hotspotIndex | integer | nullable |
| unavailable | boolean | default false |
| remark | text | nullable |
| modelCodes | varchar(500) | nullable |
| presel | boolean | default false |
| source | varchar(20) | default "pl24" → "pl24" \| "emex" |
| createdAt | timestamptz | default now() |
*Index'ler:* `parts_vehicle_id_idx`, `parts_catalog_vehicle_id_idx`, `parts_category_id_idx`, `parts_oem_code_idx`
---
#### `schemaPics`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| categoryId | uuid | NOT NULL, FK → categories.id CASCADE DELETE |
| imageUrl | text | NOT NULL |
| hotspots | jsonb | default "[]" |
| source | varchar(20) | default "pl24" |
| createdAt | timestamptz | default now() |
*Index'ler:* `schema_pics_category_id_idx`
---
#### `queryLogs`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| vin | varchar(17) | NOT NULL |
| brandId | uuid | FK → brands.id, nullable |
| source | varchar(20) | nullable |
| success | boolean | default true |
| errorMessage | text | nullable |
| responseTimeMs | integer | nullable |
| createdAt | timestamptz | default now() |
*Index'ler:* `query_logs_user_id_created_at_idx`, `query_logs_vin_idx`
---
#### `oemCodeCopies`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| oemCode | varchar(100) | NOT NULL |
| partId | uuid | nullable |
| vehicleId | uuid | nullable |
| categoryId | uuid | nullable |
| createdAt | timestamptz | default now() |
*Index'ler:* `oem_code_copies_user_id_created_at_idx`, `oem_code_copies_oem_code_idx`, `oem_code_copies_created_at_idx`
---
#### `passwordResetTokens`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| userId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| token | text | NOT NULL, UNIQUE |
| expiresAt | timestamptz | NOT NULL |
| usedAt | timestamptz | nullable |
| createdAt | timestamptz | default now() |
---
#### `referrals`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| referrerId | uuid | NOT NULL, FK → users.id CASCADE DELETE |
| referredId | uuid | NOT NULL, FK → users.id CASCADE DELETE, UNIQUE |
| rewardApplied | boolean | default false |
| createdAt | timestamptz | default now() |
*Index'ler:* `referrals_referrer_id_idx`, `referrals_referred_id_idx` (unique)
---
#### `emexCategoryTranslations`
| Kolon | Tip | Kısıt |
|-------|-----|-------|
| id | uuid | PK |
| originalName | varchar(500) | NOT NULL, UNIQUE |
| translatedName | varchar(500) | NOT NULL |
| isManual | boolean | default false |
| createdAt | timestamptz | default now() |
| updatedAt | timestamptz | default now() |
---
### 4.2 PL24 Tablolar (`apps/api/src/database/schema/pl24.ts`)
PL24 katalog verileri için önbellek tabloları.
| Tablo | Amaç | Önemli Kolonlar |
|-------|------|-----------------|
| `pl24Catalogs` | PL24 katalog kaydı | catalogId (unique), brandName |
| `pl24Vehicles` | Araç konfigürasyonu | vehicleId (unique), catalogId, modelCode, engine, yearFrom, yearTo |
| `pl24VehicleVins` | VIN ↔ pl24Vehicle eşleştirme | pl24VehicleId, vin |
| `pl24PartGroups` | Parça grupları | pl24VehicleId, groupId, name, parentGroupId, sortOrder |
| `pl24Parts` | Parçalar | pl24VehicleId, groupId, partId, name, quantity, hotspotIndex |
| `pl24PartNumbers` | OEM kodları | pl24PartId, oemCode, isMain |
| `pl24VehicleGroups` | Araç grupları | catalogId, groupId, name, parentGroupId |
| `pl24VehicleParts` | Araç-parça junction | pl24VehicleId, pl24PartId, fitmentInfo |
| `pl24SchemaPics` | Şema görselleri | groupId, imageUrl, hotspots (jsonb), width, height |
---
### 4.3 EMEX Tablolar (`apps/api/src/database/schema/emex.ts`)
EMEX scraper verileri için önbellek tabloları.
| Tablo | Amaç | Önemli Kolonlar |
|-------|------|-----------------|
| `emexCatalogs` | EMEX katalog kaydı | catalogId (unique), brandName |
| `emexVehicles` | Araç konfigürasyonu | vehicleId (unique), catalogId, engine, yearFrom, yearTo |
| `emexVehicleVins` | VIN ↔ emexVehicle | emexVehicleId, vin |
| `emexPartGroups` | Parça grupları | emexVehicleId, groupId, name, nameOriginal, parentGroupId |
| `emexParts` | Parçalar | emexVehicleId, groupId, name, nameOriginal, quantity, hotspotIndex |
| `emexPartNumbers` | OEM kodları | emexPartId, oemCode, isMain |
| `emexVehicleGroups` | Araç grupları | catalogId, groupId, name, parentGroupId |
| `emexVehicleParts` | Araç-parça junction | emexVehicleId, emexPartId, fitmentInfo |
| `emexSchemaPics` | Şema görselleri | groupId, imageUrl, hotspots (jsonb), width, height |
| `emexPartImages` | Parça görselleri | emexPartId, imageUrl |
| `emexScrapeSessions` | Scrape oturumları | vin, status (pending\|completed\|failed), jobId, result, errorMessage |
---
### 4.4 Drizzle İlişkiler (`relations.ts`)
```
users → sessions (1:N)
users → accounts (1:N)
users → userSubscriptions (1:N)
users → userBrands (1:N)
users → payments (1:N)
users → queryLogs (1:N)
users → userVehicles (1:N)
users → referrals [referrer] (1:N)
users → referrals [referred] (1:1)
plans → userSubscriptions (1:N)
userSubscriptions → plans (N:1)
userSubscriptions → users (N:1)
userSubscriptions → userBrands (1:N)
userSubscriptions → payments (1:N)
userBrands → users, userSubscriptions, brands (N:1)
brands → userBrands (1:N)
brands → catalogVehicles (1:N)
catalogVehicles → brands (N:1)
catalogVehicles → categories (1:N)
catalogVehicles → parts (1:N)
vehicles → brands (N:1)
vehicles → userVehicles (1:N)
vehicles → categories (1:N)
vehicles → parts (1:N)
categories → vehicles (N:1)
categories → catalogVehicles (N:1)
categories → categories [parentId] (N:1, hiyerarşi)
categories → parts (1:N)
categories → schemaPics (1:N)
parts → vehicles, catalogVehicles, categories (N:1)
schemaPics → categories (N:1)
referrals → users [referrer, referred] (N:1)
```
---
## 5. API Endpoint Listesi
**Global prefix:** `/api` | **Auth:** Better Auth cookie session | **Rate limit:** 100 req/15dk varsayılan
### 5.1 Auth (`/api/auth/**`)
| Method | Path | Auth | Açıklama |
|--------|------|------|---------|
| `*` | `/api/auth/**` | Public | Better Auth catch-all (sign-in, sign-up, sign-out, OAuth, session, forgot-password, reset-password) |
Better Auth dahili route'ları: `POST /api/auth/sign-in/email`, `POST /api/auth/sign-up/email`, `POST /api/auth/sign-out`, `GET /api/auth/session`, `GET /api/auth/sign-in/google`, `POST /api/auth/reset-password`, `POST /api/auth/forget-password`
---
### 5.2 Users (`/api/users`)
| Method | Path | Auth | Açıklama | Body / Query |
|--------|------|------|---------|--------------|
| `POST` | `/users/check-email` | Public | E-posta var mı kontrol | `{ email }` |
| `GET` | `/users/me` | Protected | Profil bilgisi | — |
| `PATCH` | `/users/me` | Protected | Profil güncelle | `{ name?, image? }` |
| `GET` | `/users/me/connections` | Protected | OAuth bağlantıları | — |
| `DELETE` | `/users/me/connections/:provider` | Protected | OAuth bağlantısı kaldır | — |
| `POST` | `/users/me/change-password` | Protected | Şifre değiştir | `{ currentPassword, newPassword }` |
| `DELETE` | `/users/me` | Protected | Hesap sil | — |
| `GET` | `/users` | Admin | Kullanıcı listesi | `?page, ?limit` |
| `GET` | `/users/:id` | Admin | Kullanıcı detayı | — |
---
### 5.3 Brands (`/api/brands`)
| Method | Path | Auth | Açıklama | Body |
|--------|------|------|---------|------|
| `GET` | `/brands` | Public | Marka listesi (30dk cache) | — |
| `POST` | `/brands` | Admin | Marka oluştur | `{ name, slug, logoUrl? }` |
| `PATCH` | `/brands/:id` | Admin | Marka güncelle | `{ name?, slug?, logoUrl?, isActive? }` |
---
### 5.4 Plans (`/api/plans`)
| Method | Path | Auth | Açıklama | Body |
|--------|------|------|---------|------|
| `GET` | `/plans` | Public | Plan listesi (cache'li) | — |
| `POST` | `/plans` | Admin | Plan oluştur | `{ name, brandCount, priceMonthly, priceYearly }` |
| `PATCH` | `/plans/:id` | Admin | Plan güncelle | `{ name?, brandCount?, priceMonthly?, priceYearly?, isActive? }` |
---
### 5.5 Subscriptions (`/api/subscriptions`)
| Method | Path | Auth | Açıklama | Body / Query |
|--------|------|------|---------|--------------|
| `POST` | `/subscriptions` | Protected | Abonelik oluştur | `{ planId, brandIds[], billingPeriod }` |
| `GET` | `/subscriptions/me` | Protected | Aktif abonelik | — |
| `POST` | `/subscriptions/trial` | Protected | Trial başlat | — |
| `PATCH` | `/subscriptions/cancel` | Protected | Abonelik iptal | — |
| `PATCH` | `/subscriptions/resume` | Protected | Abonelik yeniden başlat | — |
| `GET` | `/subscriptions` | Admin | Tüm abonelikler | `?page, ?limit` |
---
### 5.6 Payments (`/api/payments`)
| Method | Path | Auth | Açıklama | Body |
|--------|------|------|---------|------|
| `POST` | `/payments/stripe/checkout` | Protected | Stripe ödeme başlat | `{ planKey, billingPeriod, brandIds[] }` |
| `POST` | `/payments/stripe/webhook` | Public | Stripe webhook | `{ type, data }` |
| `POST` | `/payments/eft` | Protected | EFT ödeme oluştur | `{ planKey, billingPeriod, brandIds[] }` |
| `POST` | `/payments/eft/:id/receipt` | Protected | EFT makbuz yükle (multipart, PNG/JPG/PDF max 5MB) | `file` |
| `PATCH` | `/payments/eft/:id/approve` | Admin | EFT ödeme onayla | `{ adminNote? }` |
| `PATCH` | `/payments/eft/:id/reject` | Admin | EFT ödeme reddet | `{ adminNote? }` |
| `GET` | `/payments/me` | Protected | Ödeme geçmişi | — |
| `GET` | `/payments/pending` | Admin | Bekleyen EFT ödemeleri | — |
---
### 5.7 Referrals (`/api/referrals`)
| Method | Path | Auth | Açıklama | Body |
|--------|------|------|---------|------|
| `GET` | `/referrals/stats` | Protected | Referral istatistikleri | — |
| `GET` | `/referrals/me` | Protected | Referral kodumu + yönlendirdiklerim | — |
| `POST` | `/referrals/apply` | Protected | Referral kodu uygula | `{ code }` |
---
### 5.8 Vehicles (`/api/vehicles`)
| Method | Path | Auth | Açıklama | Body / Query |
|--------|------|------|---------|--------------|
| `GET` | `/vehicles/preview/:vin` | Public | VIN önizleme (hızlı) | — |
| `POST` | `/vehicles/decode` | Protected | Tam VIN decode | `{ vin, pcatCarId? }` |
| `GET` | `/vehicles/history` | Protected | VIN arama geçmişi | `?page, ?limit` |
| `POST` | `/vehicles/report-vin` | Protected | Bilinmeyen VIN bildir | `{ vin }` |
| `GET` | `/vehicles/:vehicleId/prefetch-status` | Protected | Prefetch job durumu | — |
| `GET` | `/vehicles/:vehicleId/categories/:categoryId` | Protected | Araç kategorisi parçaları | — |
| `GET` | `/vehicles/:id` | Protected | Araç detayı | — |
| `DELETE` | `/vehicles/:id` | Protected | Araç sil | — |
---
### 5.9 Categories (`/api/categories`)
| Method | Path | Auth | Açıklama |
|--------|------|------|---------|
| `GET` | `/categories/tree/:vehicleId` | Protected | Araç için kategori ağacı (cache'li) |
| `GET` | `/categories/:id/children` | Protected | Alt kategoriler |
| `GET` | `/categories/:id` | Protected | Kategori detayı |
---
### 5.10 Parts (`/api/parts`)
| Method | Path | Auth | Açıklama | Query |
|--------|------|------|---------|-------|
| `GET` | `/parts/category/:categoryId` | Protected | Kategoriye göre parçalar | — |
| `GET` | `/parts/search` | Protected | OEM koduna göre ara | `?oem` |
| `GET` | `/parts/:id` | Protected | Parça detayı | — |
---
### 5.11 Catalog — VIN-less Tarayıcı (`/api/catalog`)
| Method | Path | Auth | Açıklama |
|--------|------|------|---------|
| `GET` | `/catalog/brands` | Protected | Katalog marka listesi (hasAccess flag'li) |
| `GET` | `/catalog/brands/:brandName/models` | Protected | Marka model listesi (DB önbellekten, yoksa PL24'ten) |
| `GET` | `/catalog/vehicles/:id` | Protected | Katalog araç detayı |
| `GET` | `/catalog/vehicles/:id/categories` | Protected | Katalog araç kategori ağacı (Redis 2h cache) |
| `GET` | `/catalog/vehicles/:id/categories/:categoryId` | Protected | Alt kategori veya parça+şema (lazy PL24 fetch) |
| `POST` | `/catalog/explore/:serviceName` | Admin | PL24 servis yapısı keşfi |
---
### 5.12 Translations (`/api/translations`)
| Method | Path | Auth | Açıklama | Body / Query |
|--------|------|------|---------|--------------|
| `GET` | `/translations/:key` | Public | Tek çeviri | — |
| `POST` | `/translations/batch` | Protected | Toplu çeviri | `{ items: [{ key, sourceText }] }` |
| `PUT` | `/translations/:key` | Admin | Çeviri ayarla/güncelle | `{ sourceText, translatedText }` |
| `GET` | `/translations/search` | Admin | Çeviri ara | `?q` |
---
### 5.13 Analytics (`/api/analytics`)
| Method | Path | Auth | Açıklama | Body |
|--------|------|------|---------|------|
| `POST` | `/analytics/oem-copy` | Protected | OEM kod kopyalama takibi | `{ oemCode, partId?, vehicleId?, categoryId? }` |
---
### 5.14 Admin (`/api/admin`) — Tüm endpoint'ler Admin gerektirir
| Method | Path | Açıklama | Query |
|--------|------|---------|-------|
| `POST` | `/admin/users` | Kullanıcı oluştur | Body: `{ name, email, password, role? }` |
| `GET` | `/admin/dashboard` | Dashboard istatistikleri | — |
| `GET` | `/admin/users` | Kullanıcı listesi + arama | `?search, ?page, ?limit` |
| `GET` | `/admin/users/:id` | Kullanıcı detayı (abonelik + ödeme dahil) | — |
| `GET` | `/admin/payments/pending` | Bekleyen EFT ödemeleri | — |
| `GET` | `/admin/query-logs` | Query aktivite logları | `?page, ?limit, ?userId` |
| `GET` | `/admin/referrals` | Referral listesi | `?search, ?page, ?limit` |
| `GET` | `/admin/stats/daily` | Günlük istatistikler | — |
| `GET` | `/admin/copy-logs` | OEM kopyalama logları | `?page, ?limit, ?userId` |
| `GET` | `/admin/copy-logs/top` | En çok kopyalanan OEM kodları | `?days (def 30), ?limit (def 20)` |
---
### 5.15 Health (`/api/health`)
| Method | Path | Auth | Açıklama |
|--------|------|------|---------|
| `GET` | `/health` | Public | Sistem sağlık kontrolü: DB + Redis + telemetry |
---
## 6. Auth & Güvenlik Mimarisi
### Better Auth Akışı
```
1. İstemci → POST /api/auth/sign-in/email { email, password }
2. Better Auth doğrular, DB'de session oluşturur
3. Cookie döner: better-auth.session_token (HTTPS'de: __Secure-better-auth.session_token)
4. Sonraki istekler cookie'yi otomatik gönderir
5. AuthGuard → auth.api.getSession() ile doğrular
6. @CurrentUser() decorator'ı kullanıcıyı request'e enjekte eder
```
### Guard Sırası (Global)
`ThrottlerGuard``AuthGuard``RolesGuard`
### Decorator'lar
| Decorator | Kullanım | Etki |
|-----------|---------|------|
| `@Public()` | Endpoint üstüne | AuthGuard'ı atlar, anonim erişime izin verir |
| `@CurrentUser(field?)` | Parametre üstüne | Kimliği doğrulanmış kullanıcıyı (veya alanını) enjekte eder |
| `@Roles(...roles)` | Endpoint üstüne | RolesGuard tetikler, belirtilen rolü gerektirir |
| `@ThrottleAuth()` | Endpoint üstüne | 5 istek/dakika (giriş işlemleri için) |
| `@ThrottleVinDecode()` | Endpoint üstüne | 20 istek/dakika (VIN decode) |
| `@ThrottleGeneral()` | Endpoint üstüne | 100 istek/dakika (varsayılan) |
### Interceptor'lar (Global)
| Interceptor | Etki |
|-------------|------|
| `TransformInterceptor` | Tüm başarılı yanıtları sarar: `{ success: true, data: T, meta?: PaginationMeta }` |
| `LoggingInterceptor` | İstek/yanıt loglar (method, path, status, süre) |
| `TimeoutInterceptor` | Tüm isteklerde 30 saniye zaman aşımı |
### Filtreler (Global)
| Filtre | Etki |
|--------|------|
| `HttpExceptionFilter` | `{ success: false, error: { code, message } }` döner |
| `DrizzleExceptionFilter` | Unique constraint ihlalini → 409 Conflict'e dönüştürür |
### Response Format
**Başarılı:**
```json
{ "success": true, "data": { ... } }
{ "success": true, "data": [...], "meta": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 } }
```
**Hata:**
```json
{ "success": false, "error": { "code": "AUTH_001", "message": "Geçersiz kimlik bilgileri" } }
```
### Hata Kodları
| Kod | Prefix | Açıklama |
|-----|--------|---------|
| AUTH_001 | Auth | Geçersiz kimlik bilgileri |
| AUTH_002 | Auth | E-posta zaten mevcut |
| AUTH_003 | Auth | Oturum süresi dolmuş |
| AUTH_004 | Auth | Yetkisiz |
| AUTH_005 | Auth | Yasak |
| VIN_001 | VIN | Geçersiz VIN |
| VIN_002 | VIN | VIN decode başarısız |
| VIN_003 | VIN | Marka desteklenmiyor |
| SUB_001 | Abonelik | Aktif abonelik yok |
| SUB_002 | Abonelik | Marka erişimi reddedildi |
| SUB_003 | Abonelik | Geçersiz marka sayısı |
| SUB_004 | Abonelik | Abonelik zaten aktif |
| PAY_001 | Ödeme | Ödeme başarısız |
| PAY_002 | Ödeme | Stripe hatası |
| PAY_003 | Ödeme | EFT makbuzu gerekli |
| PAY_004 | Ödeme | Ödeme zaten işlenmiş |
| GEN_001 | Genel | Bulunamadı |
| GEN_002 | Genel | Doğrulama hatası |
| GEN_003 | Genel | Dahili hata |
| GEN_004 | Genel | Hız sınırııldı |
| GEN_005 | Genel | Çakışma |
---
## 7. İş Mantığı & Abonelik Modeli
### Planlar
| Plan | Marka Sayısı | Aylık | Yıllık |
|------|-------------|-------|--------|
| 1 Marka | 1 | 200 TRY | 2.000 TRY |
| 2 Marka | 2 | 350 TRY | 3.500 TRY |
| 3 Marka | 3 | 500 TRY | 5.000 TRY |
| Full Paket | Sınırsız (brandCount=0) | 999 TRY | 9.990 TRY |
*Not: Fiyatlar veritabanında kuruş cinsinden saklanır (200 TRY = 20000 kuruş).*
### Abonelik Durum Akışı
```
pending → (ödeme onayı) → active → (iptal) → cancelled
active → (süre dolumu) → expired
cancelled → (yeniden başlatma, süre içinde) → active
```
### Marka Erişim Kontrolü
- `brandCount = 0` → Full Paket, tüm markalara erişim
- `brandCount > 0``userBrands` tablosunda kayıtlı markalara erişim
- `BrandAccessGuard`: VIN decode sırasında kullanıcının o markaya erişimi olup olmadığını kontrol eder
### Referral Sistemi
| Tier | Gerekli Referral | Ödül |
|------|-----------------|------|
| Tier 1 | 3 referral | 7 gün abonelik uzatması |
| Tier 2 | 5 referral | 30 gün abonelik uzatması |
### Ödeme Akışı
**Stripe (Kart):**
1. `POST /payments/stripe/checkout` → Stripe Checkout Session oluşturulur
2. Kullanıcı Stripe ödeme sayfasına yönlendirilir
3. `POST /payments/stripe/webhook` webhook'u aboneliği aktifleştirir
**EFT (Havale):**
1. `POST /payments/eft` → EFT kaydı oluşturulur, banka bilgileri gösterilir
2. Kullanıcı makbuz yükler: `POST /payments/eft/:id/receipt`
3. Admin onaylar: `PATCH /payments/eft/:id/approve` → abonelik aktifleşir
---
## 8. VIN Decode Zinciri
### Fallback Zinciri
```
VIN Girişi (17 karakter)
1. Corgi (Offline WMI DB) → Marka + yıl tanımla
2. PL24 API → Tam araç bilgisi + kategori ağacı
↓ (başarısız veya marka desteklenmiyor)
3. PartsCatalogs API → Araç bilgisi + kategoriler
↓ (başarısız)
4. EMEX Scraper (Playwright, BullMQ) → Orta Doğu odaklı
↓ (başarısız)
5. NHTSA VIN API → ABD VIN veritabanı (son çare)
```
### VIN Yapısı
```
Pozisyon 1-3: WMI (World Manufacturer Identifier) → Marka tanımı
Pozisyon 4-8: VDS (Vehicle Descriptor Section) → Model, tip
Pozisyon 9: Check digit
Pozisyon 10: Model yılı (A=2010, L=2020, R=2024, S=2025, T=2026)
Pozisyon 11-17: VIS (Vehicle Identifier Section) → Seri no
```
### WMI → Servis Eşleşmesi (Örnekler)
| WMI Prefix | Marka | PL24 Servis |
|-----------|-------|------------|
| WVW, 3VW, 9BW, 1VW | Volkswagen | vw_parts (P5_MODERN) |
| WAU, TRU | Audi | audi_parts (P5_MODERN) |
| WBA, WBS | BMW | bmw_parts (P5_MODERN) |
| WDB, WDC | Mercedes-Benz | mercedes_parts (P5_MODERN) |
| WF0 | Ford (Avrupa) | fordp_parts (P4_LEGACY) |
| NM0 | Ford (Ticari) | fordt_parts (P4_LEGACY) |
| VF7 | Citroën | citroen_parts (P4_LEGACY) |
| VF3 | Peugeot | peugeot_parts (P4_LEGACY) |
| KMH | Hyundai | hyundai_parts (P4_LEGACY) |
| KNA, U5Y | Kia | kia_parts (P4_LEGACY) |
| W0L | Opel | opel_parts (P4_LEGACY) |
| YV1, YV4 | Volvo | volvo_parts (P4_LEGACY) |
| JN1, JN6, JN8, VNK | Nissan | nissan_parts (P4_LEGACY) |
---
## 9. Katalog Entegrasyonları
### 9.1 Corgi (Offline WMI Decoder)
- **Dosya:** `apps/api/src/integrations/corgi/`
- **Tip:** Statik lookup tablosu — harici API çağrısı yok
- **Amaç:** VIN'in ilk 3 karakterinden (WMI) marka ve üretim yılı tespiti
- **60+ üretici:** BMW, Mercedes, VW, Toyota, Ford, Nissan, Hyundai, vb.
- **Metotlar:** `decodeVin(vin)`, `getBrandFromWmi(wmi)`, `getModelYear(vin)`
### 9.2 PL24 (Partslink24)
- **Dosya:** `apps/api/src/integrations/pl24/`
- **Amaç:** Ana katalog API kaynağı
- **Hesabımız:** tr-903645 — yalnızca VAG grubu (VW, Audi, Skoda, SEAT, Porsche, Bentley, Cupra)
**P5 Modern (JSON API):**
- VAG grubu, BMW, Mercedes, Renault, Toyota, JLR, MAN, Mitsubishi, Suzuki
- Endpoint: `/{service}/extern/vehicle/modelfamilies?lang=tr&serviceName={service}`
- Auth: JWT token via `PL24AuthService`
- Cache: Redis 24s araç decode, 1s parçalar
**P4 Legacy (HTML/JSP scraping):**
- Ford, Citroën, Peugeot, Hyundai, Kia, Nissan, Infiniti, Opel, Volvo, Polestar
- REST API yok → HTML sayfaları regex ile parse edilir
- `PL24FordLegacyService.decodeVinForService(vin, serviceName)`
- VIN decode URL: `{basePath}/{service}/vin-group.action?vin={vin}&lang=tr`
**Servis Sınıfları:**
- `PL24Service` — P5 Modern handler
- `PL24FordLegacyService` — P4 Legacy HTML scraper
- `PL24AuthService` — JWT yönetimi + oturum çerezleri
### 9.3 PartsCatalogs
- **Dosya:** `apps/api/src/integrations/parts-catalogs/`
- **Amaç:** Evrensel kapsama (tüm markalar)
- **Tip:** REST API v1
- **Auth:** DataImpulse proxy üzerinden JWT (IP bound)
- **Proxy:** undici ProxyAgent (PCAT_PROXY_HOST=gw.dataimpulse.com)
- **Metotlar:** `decodeVin(vin)` (tek veya çoklu eşleşme), `fetchGroups(catalogId, carId, groupId?)`, `fetchParts(catalogId, carId, groupId)`
### 9.4 EMEX
- **Dosya:** `apps/api/src/integrations/emex/`
- **Amaç:** Orta Doğu odaklı (emexdwc.ae), Playwright tabanlı web scraper
- **Tip:** Browser otomasyon (BullMQ kuyruğu üzerinden asenkron)
- **Browser pool:** Tekil browser örneği, sayfa havuzu — her istekte yeni browser başlatılmaz
- **Cache:** Redis 5dk TTL (activity touch)
- **Timeout:** 60 saniye
- **Sınıflar:** `EmexService`, `EmexBrowserService`, `EmexMapper`
### 9.5 NHTSA VIN API
- **Dosya:** `apps/api/src/integrations/vin-api/`
- **Amaç:** Son çare — ABD VIN veritabanı
- **API:** `https://vpic.nhtsa.dot.gov/api/vehicles/decodevinvalues/{vin}`
- **Zaman aşımı:** 10 saniye
- **Dönen:** make, model, modelYear, bodyClass, engineModel, transmissionStyle, plantCountry
---
## 10. Job Queues (BullMQ + Redis)
### Kuyruk Listesi
| Kuyruk Adı | Tetikleyici | Zamanlama | İşlem |
|-----------|------------|----------|-------|
| `EMEX_SCRAPE` | On-demand (manuel/admin) | — | EMEX araç verisi scrape et |
| `CATALOG_PREFETCH` | VIN decode sonrası otomatik | — | Tüm kaynaklardan kategori+parça prefetch (derinlik sınırlı, cooldown korumalı) |
| `SUBSCRIPTION_EXPIRY` | Cron | Günlük 03:00 | Süresi dolmuş abonelikleri expire et, marka erişimini kaldır |
| `QUERY_CLEANUP` | Cron | Haftalık Pazar 04:00 | Eski query log kayıtlarını sil (90 gün saklama) |
### İşleyiciler
**CATALOG_PREFETCH:**
- `PrefetchWorkerService` tarafından işlenir
- İş verisi: `{ vehicleId, serviceName?, depth? }`
- Deneme: 3, geri çekilme: 30s eksponansiyel
- Aktivite penceresinde (5dk) EMEX veya PartsCatalogs kullanımdaysa erteliyor
**SUBSCRIPTION_EXPIRY:**
- `status: "active"` + `endDate < now()` olan abonelikleri bulur
- `status: "expired"` olarak günceller
- İlişkili `userBrands` kayıtlarını siler
**QUERY_CLEANUP:**
- `queryLogs` tablosundan 90 günden eski kayıtları siler
**EMEX_SCRAPE:**
- İş verisi: `{ vin, userId, type, emexVehicleId?, groupId? }`
- `emexScrapeSessions` tablosunu günceller
---
## 11. Frontend Mimarisi
### Router
- **Tip:** TanStack Router 1.120 (file-based)
- `routeTree.gen.ts` otomatik üretilir → ELLE DÜZENLEME YOK
- Manuel yeniden üretme: `pnpm --filter web exec tsr generate`
- Layout grupları: `_auth.tsx` (public), `dashboard.tsx` (protected)
### Tüm Route'lar (37 dosya)
#### Public Route'lar
| Path | Dosya | Açıklama |
|------|-------|---------|
| `/` | `routes/index.tsx` | Ana sayfa: hero, özellikler, video demo (Remotion), fiyatlandırma CTA, marka carousel |
| `/pricing` | `routes/pricing.tsx` | Plan karşılaştırma: aylık/yıllık toggle |
| `/about` | `routes/about.tsx` | Hakkımızda sayfası |
| `/contact` | `routes/contact.tsx` | İletişim sayfası |
| `/kvkk` | `routes/kvkk.tsx` | KVKK sayfası |
| `/terms` | `routes/terms.tsx` | Kullanım şartları |
| `/privacy` | `routes/privacy.tsx` | Gizlilik politikası |
| `/blog` | `routes/blog.tsx` | Blog listesi |
| `/blog/$slug` | `routes/blog_/$slug.tsx` | Blog yazısı |
| `/demo` | `routes/demo.tsx` | Demo sayfası |
#### Auth Route'ları (Layout: `_auth.tsx`)
| Path | Dosya | Açıklama |
|------|-------|---------|
| `/login` | `routes/_auth/login.tsx` | E-posta/şifre + Google OAuth |
| `/register` | `routes/_auth/register.tsx` | Hesap oluşturma |
| `/forgot-password` | `routes/_auth/forgot-password.tsx` | Şifre sıfırlama isteği |
| `/reset-password` | `routes/_auth/reset-password.tsx` | Token tabanlı şifre sıfırlama |
#### Dashboard Route'ları (Protected, Layout: `dashboard.tsx`)
| Path | Dosya | Açıklama |
|------|-------|---------|
| `/dashboard` | `routes/dashboard/index.tsx` | Dashboard ana sayfası |
| `/dashboard/search` | `routes/dashboard/search.tsx` | VIN arama sayfası |
| `/dashboard/history` | `routes/dashboard/history.tsx` | VIN arama geçmişi |
| `/dashboard/subscription` | `routes/dashboard/subscription/index.tsx` | Plan + marka seçimi |
| `/dashboard/subscription/pay` | `routes/dashboard/subscription/pay.tsx` | Ödeme (kart/EFT) |
| `/dashboard/billing` | `routes/dashboard/billing.tsx` | Ödeme geçmişi |
| `/dashboard/settings` | `routes/dashboard/settings.tsx` | Kullanıcı ayarları (5 sekme) |
| `/dashboard/vehicles/$id` | `routes/dashboard/vehicles_/$id/index.tsx` | Araç detayı |
| `/dashboard/vehicles/$id/categories/$categoryId` | `routes/dashboard/vehicles_/$id/categories_/$categoryId.tsx` | İnteraktif şema + parça tablosu |
#### Katalog Route'ları (VIN-less, Protected)
| Path | Dosya | Açıklama |
|------|-------|---------|
| `/dashboard/catalog` | `routes/dashboard/catalog/index.tsx` | Marka grid'i (hasAccess flag'li) |
| `/dashboard/catalog/$brandName` | `routes/dashboard/catalog_/$brandName/index.tsx` | Marka model listesi |
| `/dashboard/catalog/$brandName/$modelId` | `routes/dashboard/catalog_/$brandName_/$modelId/index.tsx` | Araç detayı + kategori ağacı |
| `/dashboard/catalog/$brandName/$modelId/categories/$categoryId` | `routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx` | Kategori + parça/şema |
#### Admin Route'ları (role: "admin")
| Path | Dosya | Açıklama |
|------|-------|---------|
| `/dashboard/admin` | `routes/dashboard/admin/index.tsx` | Admin genel bakış |
| `/dashboard/admin/users` | `routes/dashboard/admin/users.tsx` | Kullanıcı yönetimi |
| `/dashboard/admin/payments` | `routes/dashboard/admin/payments.tsx` | EFT onay iş akışı |
| `/dashboard/admin/analytics` | `routes/dashboard/admin/analytics.tsx` | Günlük sorgu istatistikleri |
| `/dashboard/admin/copy-logs` | `routes/dashboard/admin/copy-logs.tsx` | OEM kod kopyalama takibi |
| `/dashboard/admin/referrals` | `routes/dashboard/admin/referrals.tsx` | Referral programı takibi |
---
### Bileşenler (13 dosya)
| Bileşen | Dosya | Açıklama |
|---------|-------|---------|
| `SchemaViewer` | `components/schema/schema-viewer.tsx` | İnteraktif parça diyagramı: zoom/pan/fullscreen, hotspot overlay, parça listesi (60/40 split) |
| `HotspotOverlay` | `components/schema/hotspot-overlay.tsx` | Şema görüntüsü üzerinde SVG tıklanabilir bölgeler |
| `PartsPanel` | `components/schema/parts-panel.tsx` | OEM kodlu parça listesi, şema ile senkron |
| `SchemaToolbar` | `components/schema/schema-toolbar.tsx` | Zoom sıfırlama, fullscreen kontrolleri |
| `BrandSelector` | `components/subscription/brand-selector.tsx` | Plan limitli marka çoklu seçimi |
| `VehicleCard` | `components/vehicles/vehicle-card.tsx` | VIN, marka, model, yıl bilgi kartı |
| `VehicleSelectModal` | `components/vehicles/vehicle-select-modal.tsx` | PartsCatalogs çoklu eşleşme için araç seçim modalı |
| `VinInput` | `components/vehicles/vin-input.tsx` | VIN girişi + doğrulama |
| `CategoryTree` | `components/categories/category-tree.tsx` | Ağaç görünümü (genişletilebilir kategori hiyerarşisi) |
| `CategoryGrid` | `components/categories/category-grid.tsx` | Grid görünümü (ikon + şema önizlemeli) |
| `DailyChart` | `components/admin/daily-chart.tsx` | Günlük VIN decode istatistik grafiği |
| `PaymentContent` | `components/payment/payment-content.tsx` | Ödeme formu (kart/EFT), makbuz yükleme, durum takibi |
| `SettingsContent` | `components/settings/settings-content.tsx` | Ayarlar paneli (5 sekme) |
---
### Hooks (4 dosya)
| Hook | Dosya | Açıklama |
|------|-------|---------|
| `useAuth()` | `hooks/use-auth.ts` | `{ user, isLoading, isAuthenticated, isAdmin, signIn, signUp, signOut, session }` |
| `useCategoryParts(vehicleId, categoryId)` | `hooks/use-parts.ts` | TanStack Query: şema + parçalar + hotspot'lar |
| `useSchemaInteraction()` | `hooks/use-schema-interaction.ts` | Şema görüntüleyici için zoom/pan/pinch event handler'ları (0.5x5x) |
| `usePageMeta(opts)` | `hooks/use-page-meta.ts` | `<title>`, meta description, canonical, OG/Twitter tag'lerini set eder |
---
### Zustand Store'ları (2 dosya)
#### `useAuthStore` (`stores/auth.store.ts`)
```typescript
{
user: { id, name, email, image, role, referralCode } | null,
isLoading: boolean,
setUser(user), setLoading(loading)
}
```
#### `useSchemaStore` (`stores/schema.store.ts`)
```typescript
{
highlightedGroup: number | null, // hover edilmiş hotspot grubu
selectedGroup: number | null, // tıklanan hotspot grubu
zoom: number, // 0.55 arasında
panX: number, panY: number, // kaydırma offset
isFullscreen: boolean,
setHighlightedGroup, setSelectedGroup, setZoom, setPan, resetView, toggleFullscreen
}
```
---
### Lib Yardımcıları (8 dosya)
| Dosya | Açıklama |
|-------|---------|
| `lib/api-client.ts` | `ApiClient` sınıfı: `get/post/patch/delete/upload``/api` base URL, cookie credentials, TanStack Query uyumlu |
| `lib/auth-client.ts` | Better Auth istemcisi: `signIn, signUp, signOut, useSession` |
| `lib/i18n.ts` | Zustand tabanlı i18n: `t(key)`, `useTranslation()`, localStorage kalıcılığı |
| `lib/user-settings.ts` | localStorage util: `getUserSettings()`, `setUserSetting(key, value)` — categoryViewMode, sidebarCollapsed, theme |
| `lib/posthog.ts` | PostHog: `initPostHog()`, `identifyUser()`, `resetUser()`, `capture(event, props)` |
| `lib/faro.ts` | Grafana Faro RUM başlatma ve hata raporlama |
| `lib/toast.ts` | Sileo toast wrapper: `toast.success/error/info/warning(title, opts)` |
| `lib/category-icons.ts` | Kategori adına göre ikon eşleşmesi |
---
### API İstemcisi Kullanımı
```typescript
import { api } from "@/lib/api-client";
// GET isteği
const brands = await api.get<Brand[]>("/brands");
// POST isteği
const result = await api.post<Vehicle>("/vehicles/decode", { vin });
// Dosya yükleme
await api.upload("/payments/eft/:id/receipt", formData);
```
Response format: `{ success: true, data: T }` veya hata durumunda `ApiError` fırlatır.
---
### i18n Namespace Yapısı
| Namespace | Açıklama | Örnekler |
|-----------|---------|---------|
| `common` | Genel UI | save, cancel, loading, copy, search, monthly, yearly |
| `nav` | Navigasyon | search, history, catalog, subscription, billing, settings, admin, logout |
| `catalog` | Katalog tarayıcı | title, brands, models, locked, upgradeCta, loadingModels, categories |
| `auth` | Auth sayfaları | login, register, email, password, forgotPassword, loginWithGoogle |
| `subscription` | Abonelik | choosePlan, subscribe, currentPlan, cancelSubscription, selectBrands, features, plans |
| `payment` | Ödeme | creditCard, eftTransfer, bankDetails, uploadReceipt, waitingApproval |
| `billing` | Fatura | payments, date, plan, amount, method, status, downloadReceipt |
| `settings` | Ayarlar | tabs: profile, security, connections, referral, account |
| `errors` | Hata mesajları | generic, network, unauthorized, forbidden, notFound |
| `language` | Dil seçimi | tr, en, switchLanguage |
---
## 12. Shared Packages
### @sase/shared (`packages/shared/src/`)
#### TypeScript Tipleri
```typescript
// Kullanıcı
User { id, name, email, emailVerified, image, role, referralCode, referredBy, createdAt, updatedAt }
UserProfile { id, name, email, image, role, referralCode, subscription: UserSubscriptionSummary | null }
UserSubscriptionSummary { planName, status, brands: string[], expiresAt }
// Araç
Vehicle { id, userId, vin, brandId, brandName, model, year, engine, transmission, bodyType, market, rawData, source, createdAt }
VehicleSource = "pl24" | "emex" | "corgi" | "vin-api"
VinDecodeResult { vehicle: Vehicle, categories: CategoryNode[] }
CategoryNode { id, name, nameOriginal, parentId, children: CategoryNode[], hasSchema }
// Katalog
Category { id, vehicleId, name, nameOriginal, parentId, externalId, source, createdAt }
CategoryWithSchema extends Category { schemaPics: SchemaPic[] }
SchemaPic { id, categoryId, imageUrl, hotspots: Hotspot[], source, createdAt }
Hotspot { index, x, y, width, height, partId, shape, points? }
Part { id, vehicleId, categoryId, oemCode, name, nameOriginal, description, quantity, position, hotspotIndex, source, createdAt }
PartSource = "pl24" | "emex"
PartSearchResult { part: Part, vehicleInfo: { vin, brandName, model, year } }
// Abonelik & Ödeme
Plan { id, name, brandCount, priceMonthly, priceYearly, isActive, createdAt }
Subscription { id, userId, planId, status, startDate, endDate, cancelledAt, createdAt }
SubscriptionStatus = "pending" | "active" | "cancelled" | "expired"
UserBrand { id, userId, subscriptionId, brandId, createdAt }
Payment { id, userId, subscriptionId, amount, currency, method, status, stripePaymentIntentId, eftReceiptUrl, adminNote, createdAt }
PaymentMethod = "stripe" | "eft"
PaymentStatus = "pending" | "completed" | "failed" | "refunded"
CreateSubscriptionInput { planId, brandIds: string[], billingPeriod }
// API Yanıtları
ApiResponse<T> { success: boolean, data: T, meta?: PaginationMeta }
ApiError { success: false, error: { code, message, details? } }
PaginationMeta { page, limit, total, totalPages }
PaginatedResult<T> { items: T[], meta: PaginationMeta }
```
#### Zod Şemaları
```typescript
loginSchema // email + password (min 8)
registerSchema // name (min 2) + email + password (uppercase+lowercase+number)
forgotPasswordSchema // email
resetPasswordSchema // token + password
vinSchema // 17 karakter, /^[A-HJ-NPR-Z0-9]{17}$/ (I,O,Q yasak), uppercase transform
paginationSchema // page (default 1) + limit (default 20, max 100)
```
#### Sabitler
```typescript
PLANS = {
SINGLE: { name: "1 Marka", brandCount: 1, priceMonthly: 20000, priceYearly: 200000 },
DOUBLE: { name: "2 Marka", brandCount: 2, priceMonthly: 35000, priceYearly: 350000 },
TRIPLE: { name: "3 Marka", brandCount: 3, priceMonthly: 50000, priceYearly: 500000 },
FULL: { name: "Full Paket", brandCount: 0, priceMonthly: 99900, priceYearly: 999000 }
}
REFERRAL_REWARDS = {
TIER_1: { count: 3, extensionDays: 7 },
TIER_2: { count: 5, extensionDays: 30 }
}
VIN_REGEX = /^[A-HJ-NPR-Z0-9]{17}$/
EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
OEM_CODE_REGEX = /^[A-Z0-9\-.\s]{3,30}$/i
CURRENCY = "TRY"
```
#### Util Fonksiyonları
```typescript
isValidVin(vin: string): boolean // VIN format + check digit
extractWmi(vin: string): string // İlk 3 karakter
decodeModelYear(vin: string): number | null // Pozisyon 10 → yıl
formatTRY(kurus: number): string // 20000 → "200,00 ₺"
kurusToLira(kurus: number): number // 20000 → 200
liraToKurus(lira: number): number // 200 → 20000
formatVin(vin: string): string // Büyük harf
toTurkishSlug(str: string): string // URL slug
generateReferralCode(): string // 8 karakter
```
---
### @sase/config (`packages/config/src/index.ts`)
Zod tabanlı env doğrulama. Uygulama geçersiz env ile başlamaz.
Export'lar: `envSchema`, `Env` tipi, `validateEnv()`
---
### @sase/ui (`packages/ui/src/`)
shadcn/Radix tabanlı bileşen kütüphanesi.
| Bileşen | Açıklama |
|---------|---------|
| `Button` | CVA varyantları: default, destructive, outline, secondary, ghost, link |
| `Input` | Temel input alanı |
| `Card`, `CardHeader`, `CardTitle`, `CardContent`, `CardDescription`, `CardFooter` | Kart compound bileşeni |
| `Badge` | CVA varyantları: default, secondary, destructive, outline |
| `Label` | Form etiketi |
| `Skeleton` | Yükleme placeholder |
| `Separator` | Yatay/dikey ayırıcı |
| `Dialog`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose`, `DialogPortal`, `DialogOverlay`, `DialogTrigger` | Modal compound bileşeni |
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | Sekme compound bileşeni |
| `cn(...classes)` | `clsx` + `tailwind-merge` utility |
---
## 13. Ortam Değişkenleri
### Zorunlu
| Değişken | Açıklama |
|---------|---------|
| `DATABASE_URL` | PostgreSQL bağlantı string'i |
| `REDIS_PASSWORD` | Redis kimlik doğrulama |
| `BETTER_AUTH_SECRET` | Auth şifreleme anahtarı (min 32 karakter) |
| `BETTER_AUTH_URL` | Auth servis URL'i (ör. `http://localhost:4000`) |
| `MINIO_ENDPOINT` | MinIO/S3 endpoint |
| `MINIO_ACCESS_KEY` | MinIO erişim anahtarı |
| `MINIO_SECRET_KEY` | MinIO gizli anahtar |
| `MINIO_PUBLIC_URL` | Depolanan dosyalar için public URL |
### İsteğe Bağlı (Varsayılan Değerleriyle)
| Değişken | Varsayılan | Açıklama |
|---------|-----------|---------|
| `NODE_ENV` | development | development \| production \| test |
| `PORT` | 4000 | API sunucu portu |
| `REDIS_HOST` | 127.0.0.1 | Redis host |
| `REDIS_PORT` | 6379 | Redis port |
| `MINIO_BUCKET_NAME` | sase-schemas | Storage bucket adı |
| `MINIO_USE_SSL` | false | MinIO için HTTPS |
| `CORS_ORIGIN` | http://localhost:3000 | İzin verilen origin'ler |
| `POSTAL_FROM_ADDRESS` | noreply@sase.tr | Gönderici e-posta |
| `POSTAL_FROM_NAME` | Sase.tr | Gönderici adı |
| `OTEL_ENABLED` | false | OpenTelemetry etkinleştir |
| `OTEL_SERVICE_NAME` | sase-api | OTel servis adı |
| `OTEL_TRACE_SAMPLE_RATE` | 1.0 | Trace örnekleme oranı (01) |
| `ML_PREDICTION_ENABLED` | false | ML tahminleri etkinleştir |
| `PCAT_USE_PROXY` | true | PartsCatalogs proxy kullan |
| `PCAT_PROXY_HOST` | gw.dataimpulse.com | Proxy host |
### Koşullu / Servis Bağımlı
| Değişken | Grup | Açıklama |
|---------|------|---------|
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | OAuth | Google OAuth |
| `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY` | Ödeme | Stripe entegrasyonu |
| `PL24_BASE_URL`, `PL24_COMPANY_CODE`, `PL24_USERNAME`, `PL24_PASSWORD` | PL24 | PL24 katalog API |
| `EMEX_USERNAME`, `EMEX_PASSWORD` | EMEX | EMEX scraper |
| `PCAT_PROXY_USER`, `PCAT_PROXY_PASS` | PartsCatalogs | Playwright proxy |
| `POSTAL_API_URL`, `POSTAL_API_KEY` | E-posta | Postal email servisi |
| `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS` | Observability | OpenTelemetry exporter |
---
## 14. Test & Geliştirme
### Test Kimlik Bilgileri
```
Admin e-posta: admin@sase.tr
Admin şifre: Sase2026
Test VIN (VW): WVWZZZ1JZ3W597935
API URL: http://localhost:4000/api
Web URL: http://localhost:3000
```
### Test Pattern (Backend)
```typescript
// Drizzle mock → zincirleme sorgu builder simüle edilir
// vi.mock() harici bağımlılıklar için (Redis, PL24, EMEX)
// Test dosyaları: apps/api/src/**/*.spec.ts
```
### Vitest Konfigürasyonu
- API: `apps/api/vitest.config.ts``src/**/*.spec.ts`, v8 coverage
- Web: `apps/web/vitest.config.ts``src/**/*.{test,spec}.{ts,tsx}`, jsdom, `@/` alias
### Test Dosyaları
| Dosya | Test Edilen | Satır |
|-------|------------|-------|
| `admin.service.spec.ts` | AdminService | ~350 |
| `brands.service.spec.ts` | BrandsService | ~200 |
| `categories.service.spec.ts` | CategoriesService | ~250 |
| `parts.service.spec.ts` | PartsService | ~200 |
| `payments.service.spec.ts` | PaymentsService | ~350 |
| `plans.service.spec.ts` | PlansService | ~150 |
| `referrals.service.spec.ts` | ReferralsService | ~200 |
| `subscriptions.service.spec.ts` | SubscriptionsService | ~400 |
| `translations.service.spec.ts` | TranslationsService | ~200 |
| `users.service.spec.ts` | UsersService | ~250 |
| `vehicles.service.spec.ts` | VehiclesService | ~250 |
| `auth.guard.spec.ts` | AuthGuard | ~150 |
| `roles.guard.spec.ts` | RolesGuard | ~120 |
| `brand-access.guard.spec.ts` | BrandAccessGuard | ~110 |
| `vin-validation.pipe.spec.ts` | VinValidationPipe | ~77 |
| `corgi.service.spec.ts` | CorgiService | ~121 |
| `telemetry.spec.ts` | OpenTelemetry | ~97 |
---
## 15. Deployment & Altyapı
### Docker Compose (Geliştirme)
| Servis | Image | Port | Kalıcı Depolama |
|--------|-------|------|----------------|
| PostgreSQL 17 | postgres:17-alpine | 127.0.0.1:5432 | pg_data |
| Redis 7.4 | redis:7.4-alpine | 127.0.0.1:6379 | redis_data |
| MinIO | minio/minio | 9000 (API), 9001 (Console) | minio_data |
### Nginx (`docker/nginx/sites/`)
| Dosya | Amaç |
|-------|------|
| `sase.tr.conf` | Frontend SPA proxy + `/api` yönlendirme + `/collect/` Faro CORS proxy + gzip (seviye 6) + 1 yıl asset cache + güvenlik header'ları |
| `api.sase.tr.conf` | NestJS proxy (60s zaman aşımı VIN decode için) + SSL + `.git`/`.env`/`node_modules` engelleme |
### PM2 Süreçleri (`ecosystem.config.js`)
| Süreç | Komut | Port | Max Bellek |
|-------|-------|------|------------|
| `sase-api` | `dist/main.js` | 4000 | 512MB |
| `sase-web` | serve dist | 3000 | 512MB |
| `sase-worker` | `dist/worker.js` | — | 256MB |
### CI/CD (GitHub Actions)
**`ci.yml`** — Tüm branch'ler + main PR'ları (15 dk limit):
1. Biome lint
2. TypeScript typecheck
3. Vitest unit testler
4. Build
**`deploy.yml`** — `main` branch'e push (10 dk limit):
1. SSH ile production'a bağlan
2. `git pull origin main`
3. `pnpm install`
4. `pnpm build`
5. `pnpm db:push` (migration)
6. `pm2 reload all`
---
## 16. Temel Komutlar
```bash
# Geliştirme
pnpm dev # Tüm uygulamaları başlat (Turbo)
pnpm dev --filter=api # Yalnızca API
pnpm dev --filter=web # Yalnızca Web
# Kalite
pnpm build # Tüm paketleri + uygulamaları derle
pnpm lint # Biome lint kontrolü
pnpm typecheck # TypeScript --noEmit
pnpm test # Tüm testler (Vitest)
# Veritabanı (apps/api/ içinden)
pnpm db:push # Şemayı DB'ye push et (Drizzle)
pnpm db:studio # Drizzle Studio aç
pnpm db:seed # Veritabanını seed et
pnpm db:generate # Migration üret
# Route üretimi
pnpm --filter web exec tsr generate # TanStack Router route ağacını yeniden üret
# Üretim
pm2 start ecosystem.config.js
pm2 reload all
pm2 logs sase-api
```
---
## Özet: Sayılarla SASE v2
| Metrik | Değer |
|--------|-------|
| Core DB tabloları | 20 |
| PL24 cache tabloları | 9 |
| EMEX cache tabloları | 11 |
| Toplam DB tabloları | ~40 |
| API endpoint'leri | 70+ |
| Frontend route'ları | 37 |
| React bileşenleri | 13 |
| Custom hook'lar | 4 |
| Zustand store'ları | 2 |
| Shared TypeScript tipleri | 25+ |
| Error kodları | 30+ |
| Desteklenen WMI/marka | 60+ |
| PL24 servis sayısı | 50+ |
| BullMQ kuyrukları | 4 |
| Dil desteği | 2 (tr, en) |
| Fiyat planları | 4 |