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
1511 lines
58 KiB
Markdown
1511 lines
58 KiB
Markdown
# 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` | `<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.5–5 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ı (0–1) |
|
||
| `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 |
|