# 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ı aşı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.5x–5x) | | `usePageMeta(opts)` | `hooks/use-page-meta.ts` | `