Surgical refresh of the drifted project index: corrected the repo path, documented the internal-admin "Supe Panel", payments now Stripe-only (EFT history preserved as deprecated), CI/CD -> qa-gate + Coolify(dev)/PM2(prod), added blog/changelog/posthog modules + Sentry entry points, route count 36 -> 42, and replaced the fictional docs list with the files that exist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 KiB
Sase.tr - Project Documentation Index
Automotive parts search platform for the Turkish market with VIN decoding, subscription-based access, and multi-source parts catalog integration. URL: https://sase.tr · Staging: https://dev.sase.tr | Repo:
/home/s/sase.tr
Generated: 2026-03-02 · Last refreshed: 2026-05-24
Table of Contents
- Architecture Overview
- Tech Stack
- Project Structure
- Entry Points
- Backend API
- Frontend Web
- Shared Packages
- Testing
- Infrastructure & Deployment
- Environment Variables
- Key Commands
- Key Dependencies
- Quick Start
Architecture Overview
Monorepo powered by pnpm workspaces + Turborepo.
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Vite/React19 │────▶│ NestJS 10 │────▶│ PostgreSQL 17│
│ (port 3000) │ │ (port 4000) │ └──────────────┘
└─────────────┘ │ │────▶┌──────────────┐
│ │ │ Redis 7.4 │
└──────────────┘ └──────────────┘
│
┌──────┴──────┐
│ Worker │────▶ BullMQ Queues
│ (PM2 fork) │ (EMEX scrape, cron)
└─────────────┘
Three runtime processes managed by PM2:
sase-api— NestJS API server (fork mode)sase-web— Vite/React frontend (fork mode)sase-worker— Background job processor (fork mode)
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Vite 6.3, React 19, TanStack Router 1.120, TypeScript 5.7 |
| Styling | Tailwind CSS 4, shadcn/ui (Radix primitives) |
| State | Zustand 5, TanStack React Query 5 |
| Backend | NestJS 10.4, TypeScript |
| Database | PostgreSQL 17, Drizzle ORM 0.41 |
| Cache | Redis 7.4, ioredis |
| Auth | Better Auth 1.2 (email/password + Google OAuth) |
| Payments | Stripe (card checkout + webhook) |
| Storage | MinIO (S3-compatible) |
| Jobs | BullMQ (Redis-backed queues) |
| Postal (transactional email) | |
| Analytics | PostHog (product analytics) |
| Observability | OpenTelemetry + Sentry (API), Grafana Faro (frontend) |
| Testing | Vitest 3, Playwright 1.50 |
| Linting | Biome (2-space, double quotes, semicolons, trailing commas) |
| CI/CD | GitHub Actions qa-gate (PR tests) · Coolify (dev.sase.tr) · GitHub Actions → PM2 (prod) |
| Package Mgmt | pnpm 10.29, Turborepo 2 |
Project Structure
sase.tr/
├── apps/
│ ├── api/ # NestJS backend API
│ │ ├── src/
│ │ │ ├── main.ts # Bootstrap (global prefix /api, CORS, Helmet, rate limiting)
│ │ │ ├── instrument.ts # Sentry init (imported first in main.ts)
│ │ │ ├── app.module.ts # Root module (global guards, interceptors, filters)
│ │ │ ├── worker.ts # Standalone worker process
│ │ │ ├── instrument-worker.ts # Sentry init for the worker process
│ │ │ ├── health.controller.ts # Health check endpoint
│ │ │ ├── auth/ # Better Auth integration
│ │ │ ├── users/ # User account management
│ │ │ ├── brands/ # Brand CRUD
│ │ │ ├── plans/ # Pricing plan CRUD
│ │ │ ├── subscriptions/ # Subscription lifecycle
│ │ │ ├── payments/ # Stripe payment processing (checkout + webhook)
│ │ │ ├── referrals/ # Referral program
│ │ │ ├── vehicles/ # VIN decoding + vehicle history
│ │ │ ├── categories/ # Parts category tree
│ │ │ ├── parts/ # Auto parts catalog
│ │ │ ├── catalog/ # VIN-less catalog browser (PL24 model families)
│ │ │ ├── translations/ # Automotive term translations
│ │ │ ├── admin/ # Admin dashboard endpoints
│ │ │ ├── internal-admin/ # Founder-only "Supe Panel" (impersonation, lifecycle, billing, refunds)
│ │ │ ├── analytics/ # OEM copy-event tracking
│ │ │ ├── posthog/ # PostHog server-side event capture
│ │ │ ├── blog/ # Blog posts (public + automation webhook)
│ │ │ ├── changelog/ # Changelog entries (public + automation webhook)
│ │ │ ├── common/ # Shared guards, pipes, interceptors, filters, decorators
│ │ │ ├── config/ # Runtime configuration
│ │ │ ├── database/ # Drizzle ORM setup + schemas (core, emex, pl24, parts-catalogs, relations)
│ │ │ ├── redis/ # Redis client module
│ │ │ ├── storage/ # MinIO/S3 service
│ │ │ ├── email/ # Postal email service
│ │ │ ├── jobs/ # BullMQ queues + processors
│ │ │ ├── telemetry/ # OpenTelemetry SDK (tracing, metrics)
│ │ │ └── integrations/ # External API integrations
│ │ │ ├── corgi/ # Offline VIN WMI decoder
│ │ │ ├── pl24/ # PL24 parts catalog API + parsers
│ │ │ ├── parts-catalogs/ # PartsCatalogs API (groups, parts, auth)
│ │ │ ├── emex/ # EMEX scraper (Playwright)
│ │ │ └── vin-api/ # NHTSA VIN API fallback
│ │ ├── drizzle.config.ts
│ │ └── vitest.config.ts
│ │
│ └── web/ # Vite + React frontend
│ └── src/
│ ├── main.tsx # Entry point (RouterProvider, QueryClient, Faro, PostHog)
│ ├── routeTree.gen.ts # Auto-generated TanStack route tree (DO NOT EDIT)
│ ├── routes/ # TanStack Router file-based routes
│ │ ├── __root.tsx # Root layout (theme, toaster, PostHog tracking)
│ │ ├── index.tsx # Landing page
│ │ ├── _auth.tsx # Auth layout (login, register, etc.)
│ │ ├── dashboard.tsx # Dashboard layout (protected)
│ │ └── dashboard/ # Dashboard subroutes (catalog, vehicles, admin, etc.)
│ ├── components/ # React components
│ │ ├── admin/ # DailyChart
│ │ ├── schema/ # SchemaViewer, HotspotOverlay, PartsPanel, SchemaToolbar
│ │ ├── subscription/ # BrandSelector
│ │ ├── vehicles/ # VehicleCard, VinInput
│ │ ├── categories/ # CategoryTree, CategoryGrid
│ │ ├── payment/ # PaymentContent
│ │ └── settings/ # SettingsContent
│ ├── hooks/ # useAuth, useParts, useSchemaInteraction
│ ├── lib/ # api-client, auth-client, i18n, posthog, faro, toast, user-settings, category-icons
│ ├── stores/ # Zustand stores (auth, schema)
│ └── messages/ # i18n JSON (tr.json, en.json)
│
├── packages/
│ ├── shared/ # @sase/shared — types, Zod schemas, constants, utils
│ ├── config/ # @sase/config — Zod env validation
│ └── ui/ # @sase/ui — shadcn/Radix component library
│
├── docker/ # Docker Compose (PostgreSQL, Redis, MinIO, nginx)
├── scripts/ # Build/deployment/debug scripts
├── .github/workflows/ # CI (lint, typecheck, test, build) + Deploy (SSH, PM2)
├── ecosystem.config.js # PM2 process configuration
├── turbo.json # Turborepo task pipeline
├── biome.json # Linter/formatter config
└── pnpm-workspace.yaml # Workspace root
Entry Points
| Process | Path | Description |
|---|---|---|
| API Server | apps/api/src/main.ts |
NestJS bootstrap (Helmet, CORS, rate limiting) |
| Root Module | apps/api/src/app.module.ts |
Global guards, interceptors, filters |
| Worker | apps/api/src/worker.ts |
BullMQ background job processor |
| Sentry Init | apps/api/src/instrument.ts · instrument-worker.ts |
Sentry instrumentation (imported before app bootstrap) |
| Health | apps/api/src/health.controller.ts |
Health check endpoint |
| Frontend | apps/web/src/main.tsx |
React 19 + TanStack Router + Query + Faro + PostHog |
| Root Layout | apps/web/src/routes/__root.tsx |
Theme, Toaster, PostHog tracking |
Backend API
Modules & Services
| Module | Files | Purpose |
|---|---|---|
| AuthModule | module, service, controller, auth.ts | Better Auth (email/password + Google OAuth) |
| UsersModule | module, service, controller, spec | Profile CRUD, password change, account deletion, OAuth connections |
| BrandsModule | module, service, controller, spec | Brand CRUD (cached, admin-managed) |
| PlansModule | module, service, controller, spec | Pricing plan CRUD (cached, admin-managed) |
| SubscriptionsModule | module, service, controller, spec | Create, activate, cancel, resume, extend subscriptions |
| PaymentsModule | module, service, controller, spec | Stripe card payments (checkout session + webhook), payment history |
| ReferralsModule | module, service, controller, spec | Referral code generation, application, tier-based rewards |
| VehiclesModule | module, service, controller, spec | VIN decode (multi-source fallback), vehicle history, brand access check |
| CategoriesModule | module, service, controller, spec | Hierarchical category tree, schema pictures |
| PartsModule | module, service, controller, spec | Parts by category, OEM code search |
| CatalogModule | module, service, controller, dto | VIN-less PL24 catalog browser: brands, models, category trees, parts |
| TranslationsModule | module, service, controller, spec | Automotive term translation (Redis → DB → Dictionary fallback) |
| AdminModule | module, service, controller, spec | Dashboard stats, user management, query logs, referral & daily stats, OEM copy logs |
| InternalAdminModule | impersonation, lifecycle, billing, payments, vehicles (controller+service each) | Founder-only "Supe Panel": read-only impersonation, suspend/ban, trial/plan edits, refunds, vehicle cache ops. Gated by InternalTokenGuard (INTERNAL_API_TOKEN) |
| AnalyticsModule | module, service, controller | OEM code copy-event tracking (oemCodeCopies) |
| PostHogModule | module, service | Server-side PostHog event capture (optional) |
| BlogModule | module, service, controller | Blog posts: public list/detail + automation webhook (Bearer token) |
| ChangelogModule | module, service, controller, spec | Changelog entries: public list + admin CRUD + automation webhook |
| EmailModule | module, service | Postal transactional emails (password reset, welcome, payment confirmation) |
| StorageModule | module, service | S3/MinIO file upload/download |
| RedisModule | module, service, provider | Key-value cache operations |
Common Infrastructure
| Type | Name | Behavior |
|---|---|---|
| Guard | AuthGuard (global) |
Validates Better Auth session; skip with @Public() |
| Guard | RolesGuard (global) |
Checks @Roles("admin") metadata against user.role |
| Guard | BrandAccessGuard (per-route) |
Verifies user's subscription includes the target brand |
| Guard | ThrottlerGuard (global) |
Rate limiting (100/min default) |
| Guard | ImpersonationReadonlyGuard (global) |
Blocks mutations during a read-only impersonation session |
| Guard | InternalTokenGuard (per-route) |
Validates INTERNAL_API_TOKEN bearer for /api/internal/admin/* (Supe Panel) |
| Interceptor | TransformInterceptor (global) |
Wraps responses: {success: true, data: ...} |
| Interceptor | LoggingInterceptor (global) |
Logs method, URL, status, response time |
| Interceptor | TimeoutInterceptor (global) |
30s request timeout |
| Pipe | VinValidationPipe (per-route) |
Validates VIN: 17 chars, alphanumeric, no I/O/Q |
| Filter | HttpExceptionFilter (global) |
Returns {success: false, error: {code, message}} |
| Filter | DrizzleExceptionFilter (global) |
Catches unique constraint violations → 409 Conflict |
| Middleware | FileUploadValidation |
PNG/JPG/PDF only, max 5MB |
Custom Decorators:
@Public()— Skip authentication@CurrentUser(field?)— Inject authenticated user (or specific field)@Roles(...roles)— Require role(s)@ThrottleAuth()— 5 req/min@ThrottleVinDecode()— 20 req/min@ThrottleGeneral()— 100 req/min
Shared DTOs: ApiResponseDto, PaginationDto
Integrations
VIN Decode Fallback Chain: Corgi (offline WMI) → PartsCatalogs API → PL24 API → EMEX Scraper → NHTSA VIN API
Category Fetch Fallback Chain (per source): DB cache → PL24 → PartsCatalogs → EMEX (triggered by getCategoryTree)
| Integration | Type | Path | Notes |
|---|---|---|---|
| Corgi | Offline DB | corgi/ |
WMI database for brand identification (+ spec) |
| PL24 | REST API | pl24/ |
Multi-brand catalog API + auth + parsers (BMW, Mercedes, Generic, Ford Legacy) |
| PartsCatalogs | REST API | parts-catalogs/ |
Multi-brand catalog API. Files: service, auth-service, module, types. Supports fetchGroups, fetchParts with parameterized car queries |
| EMEX | Browser scraper | emex/ |
Playwright-based (emexdwc.ae), async via BullMQ. Files: service, browser, mapper, types |
| VIN-API | REST API | vin-api/ |
NHTSA VIN decoder (last-resort fallback) |
| Stripe | Payment API | — | Global payment processor for card payments |
| MinIO | S3 API | — | Receipt uploads, schema images |
Database Schema
ORM: Drizzle ORM 0.41 with PostgreSQL
Schema files: apps/api/src/database/schema/
core.ts— Main application tablesemex.ts— EMEX scraper cache tablespl24.ts— PL24 catalog cache tablesparts-catalogs.ts— PartsCatalogs API cache tablesrelations.ts— Drizzle ORM relationships
Core Tables
users
├── id (uuid, PK)
├── name, email (unique), emailVerified, image
├── role (default: "user"), referralCode (unique), referredBy
└── createdAt, updatedAt
sessions / accounts / verifications
└── Better Auth managed tables
brands
├── id (uuid, PK), name, slug (unique), logoUrl, isActive
└── createdAt, updatedAt
plans
├── id (uuid, PK), name, brandCount
├── priceMonthly, priceYearly, isActive
└── createdAt, updatedAt
userSubscriptions
├── id (uuid, PK), userId → users, planId → plans
├── status (pending/active/cancelled/expired)
├── billingPeriod (monthly/yearly), startDate, endDate, cancelledAt
└── Indexes: userId, status
userBrands (junction)
├── userId → users, subscriptionId → userSubscriptions, brandId → brands
└── Unique: (userId, subscriptionId, brandId)
payments
├── id (uuid, PK), userId → users, subscriptionId → userSubscriptions
├── amount, currency, method, status (pending/completed/failed/refunded)
├── stripeSessionId, stripePaymentIntentId, adminNote
├── iyzicoPaymentId, bankAccountId → bankAccounts, eftReceiptUrl (@deprecated — legacy iyzico/EFT, kept for historical rows only)
└── Indexes: userId, status, stripeSessionId
bankAccounts
└── (legacy) EFT bank-transfer accounts; retained for historical payments only
vehicles
├── id (uuid, PK), vin (unique), brandId → brands
├── brandName, model, year, engine, transmission, bodyType, market
├── rawData (jsonb), source
└── Indexes: vin
Note: Shared across users; access via userVehicles junction table
userVehicles (junction)
├── userId → users, vehicleId → vehicles
├── lastAccessedAt
└── Unique: (userId, vehicleId)
categories
├── id (uuid, PK), vehicleId → vehicles (nullable), catalogVehicleId → catalogVehicles (nullable)
├── name, nameOriginal, parentId (self-ref), externalId, source
└── Indexes: vehicleId, parentId
Note: DUAL FK — exactly one of vehicleId (VIN-based) or catalogVehicleId (VIN-less) is non-null per row
parts
├── id (uuid, PK), vehicleId → vehicles (nullable), catalogVehicleId → catalogVehicles (nullable)
├── categoryId → categories, oemCode, name, nameOriginal, description, quantity, position, hotspotIndex
└── Indexes: vehicleId, categoryId, oemCode
Note: DUAL FK — same pattern as categories
schemaPics
├── id (uuid, PK), categoryId → categories
├── imageUrl, hotspots (jsonb), source
└── Indexes: categoryId
queryLogs
├── id (uuid, PK), userId → users, vin, brandId → brands
├── source, success, errorMessage, responseTimeMs
└── Indexes: (userId, createdAt), vin
referrals
├── id (uuid, PK), referrerId → users, referredId → users (unique)
├── rewardApplied, createdAt
└── Indexes: referrerId, referredId
passwordResetTokens
└── id, userId → users, token (unique), expiresAt, usedAt
emexCategoryTranslations
└── id, originalName (unique), translatedName, isManual
oemCodeCopies
└── OEM code copy events (userId, oemCode, optional partId/vehicleId/categoryId) — admin copy-log analytics
blogPosts
└── SEO blog posts (slug, title, content, publish state)
changelogEntries
└── Product changelog entries (shown in Settings → Changelog)
catalogVehicles
├── id (uuid, PK), source (pl24), serviceName, brandName, brandId → brands
├── model, year, engine, bodyType, transmission, market
├── serviceVehicleId, catalogPath, architecture (P5_MODERN/P4_LEGACY)
├── metadata (jsonb), categoriesFetched (bool)
└── Unique: (serviceName, serviceVehicleId)
Note: categories and parts tables both have catalogVehicleId FK (nullable) for VIN-less catalog data
Integration Tables
pl24_*— PL24 catalog cache (catalogs, vehicles, VINs, part groups, parts, schemas)emex_*— EMEX scraper cache (similar structure with translations)parts_catalogs_*— PartsCatalogs API cache (defined inparts-catalogs.ts)
Migration Workflow
Strategy: Formal Drizzle SQL migrations with a custom hash-based runner.
The migration runner at apps/api/src/database/migrate.ts uses a custom hash-based runner instead of Drizzle's built-in migrate(). It tracks applied migrations by SHA256 of SQL file content rather than by timestamp comparison.
Why custom? Drizzle's built-in migrate() compares folderMillis timestamps from drizzle/meta/_journal.json. When drizzle-kit generate produces incorrect timestamps (clock skew bug), new migrations can be silently skipped. Hash-based tracking is immune to clock skew and provides deterministic idempotency.
How it works:
- Reads all previously-applied migration hashes from
drizzle.__drizzle_migrations - Reads
drizzle/meta/_journal.jsonfor the list of migration files in index order - For each migration file, computes SHA256 of the SQL content
- If the hash already exists in
__drizzle_migrations→ skip - If the hash is new → execute all statements in a single transaction, then record the hash
Idempotent baseline: 0000_brief_guardian.sql uses IF NOT EXISTS for all CREATE TABLE, CREATE INDEX, and CREATE UNIQUE INDEX statements. ALTER TABLE ADD CONSTRAINT statements are wrapped in DO $$ BEGIN ... EXCEPTION WHEN duplicate_object ... END $$ blocks. This allows the baseline to be safely re-applied on existing databases where __drizzle_migrations was bootstrapped to a different hash.
Schema change workflow:
- Edit schema files in
apps/api/src/database/schema/ - Run
pnpm db:generate— produces a timestamped.sqlfile inapps/api/drizzle/ - Review the SQL diff in PR (the migration is version-controlled alongside code)
- Commit and deploy — migration runs automatically on container startup
Container startup: The API server runs apps/api/start.sh which executes node dist/database/migrate.js BEFORE starting the NestJS server. This ensures all pending migrations are applied before any code runs.
Bootstrap note: Existing databases with no migration history (__drizzle_migrations table empty or missing) are handled automatically. The runner creates the tracking table and applies the baseline migration idempotently (all statements use IF NOT EXISTS guards, so no errors on re-application). This is safe for databases previously managed via pnpm db:push.
Manual commands:
pnpm db:migrate— Run migrations in dev (usestsxfor direct TS execution)pnpm db:migrate:dist— Run migrations against compiled JS (for production manual override)pnpm db:generate— Create a new migration from schema changespnpm db:push— Direct schema push (still available for local dev, discouraged for shared environments)
Worker process: The BullMQ worker overrides the Docker CMD in docker-compose.coolify.yml and does NOT run migrations. Only the API service handles migrations, preventing race conditions.
Job Queues
Framework: BullMQ with Redis
| Queue | Trigger | Schedule | Action |
|---|---|---|---|
EMEX_SCRAPE |
On-demand (VIN decode) | — | Scrapes EMEX via Playwright, stores results |
CATALOG_PREFETCH |
On-demand (after VIN decode) | — | Prefetches category tree + parts for all sources (depth-limited, rate-limited, cooldown-guarded). Handled by PrefetchWorkerService. |
SUBSCRIPTION_EXPIRY |
Cron | Daily 3:00 AM | Expires ended subscriptions, removes brand access |
QUERY_CLEANUP |
Cron | Weekly Sun 4:00 AM | Cleans old query log entries |
Files: apps/api/src/jobs/ — jobs.module.ts, bull.config.ts, processors/, queues/, prefetch-worker.service.ts, prefetch-utils.ts, prefetch.types.ts
Telemetry
Location: apps/api/src/telemetry/
OpenTelemetry SDK with:
sdk-factory.ts— SDK initializationtracing.ts— Distributed tracing (API)worker-tracing.ts— Worker process tracingmetrics.ts— Prometheus metrics__tests__/telemetry.spec.ts— Tests
Instrumentation: Express, HTTP, ioredis, NestJS Core, BullMQ, Drizzle ORM
API Endpoints
Global prefix: /api | Rate limit: 100 req/min (default)
Authentication
| Method | Path | Auth | Description |
|---|---|---|---|
* |
/api/auth/** |
Public | Better Auth catch-all (register, login, logout, OAuth, session) |
Health
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/health |
Public | Health check ({status: "ok", timestamp}) |
Users
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/users/me |
User | Get current user profile |
PATCH |
/api/users/me |
User | Update profile (name, image) |
POST |
/api/users/me/change-password |
User | Change password |
DELETE |
/api/users/me |
User | Delete account |
GET |
/api/users/me/connections |
User | List OAuth connections |
DELETE |
/api/users/me/connections/:provider |
User | Unlink OAuth provider |
GET |
/api/users |
Admin | List all users (paginated) |
GET |
/api/users/:id |
Admin | Get user by ID |
Brands
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/brands |
Public | List all brands (cached 30 min) |
POST |
/api/brands |
Admin | Create brand |
PATCH |
/api/brands/:id |
Admin | Update brand |
Plans
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/plans |
Public | List all plans (cached 30 min) |
POST |
/api/plans |
Admin | Create plan |
PATCH |
/api/plans/:id |
Admin | Update plan |
Subscriptions
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/subscriptions |
User | Create subscription with brand selection |
GET |
/api/subscriptions/me |
User | Get active subscription |
PATCH |
/api/subscriptions/cancel |
User | Cancel subscription |
PATCH |
/api/subscriptions/resume |
User | Resume cancelled subscription |
GET |
/api/subscriptions |
Admin | List all subscriptions (paginated) |
Payments
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/payments/stripe/checkout |
User | Create Stripe checkout session |
POST |
/api/payments/stripe/webhook |
Public | Stripe webhook callback |
GET |
/api/payments/me |
User | Payment history |
EFT (bank-transfer) endpoints were removed in the Stripe migration (FN-343). The
paymentstable keepseftReceiptUrl/bankAccountId/iyzicoPaymentIdonly for historical rows.
Referrals
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/referrals/stats |
User | Referral count + reward tier status |
GET |
/api/referrals/me |
User | Referral code + list of referees |
POST |
/api/referrals/apply |
User | Apply a referral code |
Vehicles
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/vehicles/preview/:vin |
Public | Preview vehicle by VIN (pre-decode) |
POST |
/api/vehicles/decode |
User | Decode VIN (rate limited: 20/min) |
GET |
/api/vehicles/history |
User | Vehicle history (paginated) |
POST |
/api/vehicles/report-vin |
User | Report an unrecognized VIN to admin |
GET |
/api/vehicles/:vehicleId/prefetch-status |
User | Catalog prefetch job status |
GET |
/api/vehicles/:vehicleId/categories/:categoryId |
User | Parts for a vehicle + category |
GET |
/api/vehicles/:id |
User | Get vehicle details |
DELETE |
/api/vehicles/:id |
User | Delete vehicle |
Categories & Parts
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/categories/tree/:vehicleId |
User | Category tree for vehicle (cached) |
GET |
/api/categories/:id |
User | Category with schema pictures |
GET |
/api/parts/category/:categoryId |
User | Parts by category |
GET |
/api/parts/search?oem= |
User | Search parts by OEM code |
GET |
/api/parts/:id |
User | Part by ID |
Catalog (VIN-less Browser)
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/catalog/brands |
User | List PL24 brands with access flags (hasAccess per subscription) |
GET |
/api/catalog/brands/:brandName/catalogs |
User | Service catalog list for a brand (multi-catalog brands like Ford) |
GET |
/api/catalog/brands/:brandName/models |
User | Model list for a brand (?service= optional) — DB cache → PL24 fetch |
GET |
/api/catalog/vehicles/:id |
User | Get a catalog vehicle by ID |
GET |
/api/catalog/vehicles/:id/ford-config |
User | Ford model-year/engine/gearbox options (Legacy Ford brands) |
GET |
/api/catalog/vehicles/:id/psa-bodies |
User | PSA body style options (Citroën/Peugeot) |
GET |
/api/catalog/vehicles/:id/psa-engines |
User | PSA engine options for a given body (?body=) |
GET |
/api/catalog/vehicles/:id/psa-gearboxes |
User | PSA gearbox options for a given body+engine (?body=&engine=) |
GET |
/api/catalog/vehicles/:id/categories |
User | Category tree (?body=&engine=&gearbox=); Redis cached 2h |
GET |
/api/catalog/vehicles/:id/categories/:categoryId |
User | Sub-categories or parts+schema (?body=&engine=&gearbox=); lazy PL24 fetch |
POST |
/api/catalog/explore/:serviceName |
Admin | Explore PL24 service structure (discovery tool) |
EMEX sub-catalog (/api/catalog/emex/*): brands, brands/:code/vehicles, brands/:code/wizard, brands/:code/wizard-vehicles, vehicles/:id/groups, vehicles/:id/groups/:groupId, search?oem=, match?catalogCode=&name=
PCAT sub-catalog (/api/catalog/pcat/*): catalogs, catalogs/:id/models, catalogs/:id/groups, catalogs/:id/models/:modelId/cars, cars/:carId/groups, cars/:carId/groups/:groupId/schemas, schemas/:schemaImageId
Translations
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/translations/:key |
Public | Get translation |
POST |
/api/translations/batch |
User | Batch translate items |
PUT |
/api/translations/:key |
Admin | Set/override translation |
GET |
/api/translations/search?q= |
Admin | Search translations |
Admin
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/admin/users |
Admin | Create a user |
GET |
/api/admin/dashboard |
Admin | Dashboard stats (users, revenue, queries) |
GET |
/api/admin/users |
Admin | User list with search (paginated) |
GET |
/api/admin/users/:id |
Admin | User detail with subscriptions & payments |
GET |
/api/admin/query-logs |
Admin | Query logs (paginated, filterable) |
GET |
/api/admin/referrals |
Admin | Referral stats (paginated) |
GET |
/api/admin/stats/daily |
Admin | Daily VIN decode stats (last 30 days) |
GET |
/api/admin/copy-logs |
Admin | OEM code copy logs |
GET |
/api/admin/copy-logs/top |
Admin | Top copied OEM codes (?days=) |
Analytics
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/analytics/oem-copy |
User | Track OEM code copy event (oemCode, partId?, vehicleId?, categoryId?) |
Blog
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/blog/posts |
Public | List published posts |
GET |
/api/blog/posts/:slug |
Public | Get post by slug |
POST |
/api/blog/posts/internal |
Token (Bearer) | Create/update post via automation |
Internal Admin — "Supe Panel" (founder-only, InternalTokenGuard)
| Method | Path | Description |
|---|---|---|
POST |
/api/internal/admin/users/:id/impersonate-readonly |
Mint a signed read-only impersonation consume-URL |
GET |
/api/admin/impersonate/consume?t= |
Consume token → set read-only session cookie |
POST |
/api/internal/admin/users/:id/{suspend,reactivate,ban} |
Account lifecycle |
POST |
/api/internal/admin/subscriptions/:id/{trial/extend,activate,change-plan,cancel,resume,brands} |
Billing / subscription overrides |
POST |
/api/internal/admin/payments/:id/refund |
Refund a payment |
POST |
/api/internal/admin/vehicles/:vin/cache-clear |
Clear cached catalog data for a VIN |
DELETE |
/api/internal/admin/vehicles/:vin |
Force-delete a vehicle |
Changelog
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/changelog |
Public | List changelog entries (Redis-cached, 30min) |
POST |
/api/changelog |
Admin | Create changelog entry |
POST |
/api/changelog/internal |
Token (Bearer) | Create changelog entry (Fusion automation webhook, CHANGELOG_AUTOMATION_TOKEN env) |
PATCH |
/api/changelog/:id |
Admin | Update changelog entry |
DELETE |
/api/changelog/:id |
Admin | Delete changelog entry |
Authentication
Provider: Better Auth 1.2
Flow:
1. Client sends credentials to /api/auth/sign-in/email (or /sign-up, /oauth)
2. Better Auth validates, creates session in DB
3. Returns session cookie (better-auth.session_token, 5-min cache)
4. Subsequent requests include cookie automatically
5. AuthGuard calls auth.api.getSession() to validate
6. User injected into request via @CurrentUser() decorator
User model extensions: role (default: "user"), referralCode, referredBy, account status (active/suspended/banned — non-active users are rejected by AuthGuard).
Internal/founder access: the /api/internal/admin/* "Supe Panel" routes are gated by InternalTokenGuard (INTERNAL_API_TOKEN bearer), separate from user auth. Read-only impersonation issues an HMAC-signed consume token (INTERNAL_IMPERSONATION_SECRET); the resulting session is mutation-blocked by ImpersonationReadonlyGuard.
Frontend Web
Routes & Pages
Router: TanStack Router (file-based, auto-generated route tree — 42 files)
Public
| Path | Route File | Description |
|---|---|---|
/ |
routes/index.tsx |
Landing page with VIN decode + features + pricing CTA |
/pricing |
routes/pricing.tsx |
Plan comparison (1/2/3 brand, full package) |
/about |
routes/about.tsx |
About page |
/contact |
routes/contact.tsx |
Contact page |
/blog |
routes/blog.tsx |
Blog listing page |
/blog/:slug |
routes/blog_/$slug.tsx |
Blog post detail (SEO pre-rendered) |
/demo |
routes/demo.tsx |
Demo page |
/privacy |
routes/privacy.tsx |
Privacy policy |
/terms |
routes/terms.tsx |
Terms of service |
/kvkk |
routes/kvkk.tsx |
KVKK (Turkish data protection) |
Auth (Layout: _auth.tsx)
| Path | Route File | Description |
|---|---|---|
/login |
routes/_auth/login.tsx |
Email/password + Google OAuth |
/register |
routes/_auth/register.tsx |
Account creation |
/forgot-password |
routes/_auth/forgot-password.tsx |
Email-based recovery |
/reset-password |
routes/_auth/reset-password.tsx |
Token-based password reset |
Dashboard (Protected, Layout: dashboard.tsx)
| Path | Route File | Description |
|---|---|---|
/dashboard |
routes/dashboard/index.tsx |
Dashboard home |
/dashboard/search |
routes/dashboard/search.tsx |
VIN Search — main VIN decoder input |
/dashboard/history |
routes/dashboard/history.tsx |
Past VIN decode searches |
/dashboard/subscription |
routes/dashboard/subscription/index.tsx |
Plan selection & brand picker (Stripe checkout redirect) |
/dashboard/billing |
routes/dashboard/billing.tsx |
Payment history |
/dashboard/settings |
routes/dashboard/settings.tsx |
Profile, Security, Connections, Referral, Account, Changelog tabs |
/dashboard/vehicles/$id |
routes/dashboard/vehicles_/$id/index.tsx |
Vehicle details |
/dashboard/vehicles/$id/categories/$categoryId |
routes/dashboard/vehicles_/$id/categories_/$categoryId.tsx |
Interactive schema + parts table |
Catalog Browser (VIN-less, Protected)
| Path | Route File | Description |
|---|---|---|
/dashboard/catalog |
routes/dashboard/catalog/index.tsx |
Brand grid with hasAccess flags; locked brands show upgrade CTA |
/dashboard/catalog/$brandName |
routes/dashboard/catalog_/$brandName/index.tsx |
Model list for brand (fetched from PL24) |
/dashboard/catalog/$brandName/$modelId |
routes/dashboard/catalog_/$brandName_/$modelId/index.tsx |
Vehicle details + category tree (grid/tree toggle) |
/dashboard/catalog/$brandName/$modelId/categories/$categoryId |
routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx |
Category sub-groups or schema+parts (lazy PL24 fetch) |
Source-specific sub-browsers also exist:
/dashboard/catalog/emex/$catalogCode[/$vehicleId](EMEX group navigation) and/dashboard/catalog/pcat/$catalogId(PartsCatalogs). A/dashboard/service-testpage surfaces catalog-source status.
Admin (Role-based)
| Path | Route File | Description |
|---|---|---|
/dashboard/admin |
routes/dashboard/admin/index.tsx |
Stats overview with charts |
/dashboard/admin/users |
routes/dashboard/admin/users.tsx |
User management |
/dashboard/admin/referrals |
routes/dashboard/admin/referrals.tsx |
Referral program tracking |
/dashboard/admin/analytics |
routes/dashboard/admin/analytics.tsx |
Daily query statistics |
/dashboard/admin/copy-logs |
routes/dashboard/admin/copy-logs.tsx |
OEM code copy tracking |
Components
| Component | Location | Description |
|---|---|---|
| SchemaViewer | components/schema/schema-viewer.tsx |
Interactive parts diagram with zoom/pan/fullscreen |
| HotspotOverlay | components/schema/hotspot-overlay.tsx |
SVG overlay for clickable part regions |
| PartsPanel | components/schema/parts-panel.tsx |
Parts table with OEM codes, synced with schema |
| SchemaToolbar | components/schema/schema-toolbar.tsx |
Zoom/reset/fullscreen controls |
| BrandSelector | components/subscription/brand-selector.tsx |
Brand grid with plan-enforced max selection |
| VehicleCard | components/vehicles/vehicle-card.tsx |
Vehicle info card (VIN, brand, model, year) |
| VehicleSelectModal | components/vehicles/vehicle-select-modal.tsx |
Modal for selecting a vehicle from history (used during VIN decode flow) |
| VinInput | components/vehicles/vin-input.tsx |
VIN entry input component |
| CategoryTree | components/categories/category-tree.tsx |
Recursive expandable category hierarchy |
| CategoryGrid | components/categories/category-grid.tsx |
Grid layout for category browsing |
| DailyChart | components/admin/daily-chart.tsx |
Daily VIN decode stats chart |
| PaymentContent | components/payment/payment-content.tsx |
Payment form and flow |
| SettingsContent | components/settings/settings-content.tsx |
User settings panel content |
| ChangelogTab | components/settings/changelog-tab.tsx |
Timeline + accordion changelog viewer |
| PsaVariantSelector | components/catalog/psa-variant-selector.tsx |
PSA (Citroën/Peugeot) body/engine/gearbox picker for VIN-less catalog |
| FordVariantSelector | components/catalog/ford-variant-selector.tsx |
Ford model-year/engine/gearbox picker for VIN-less catalog |
UI primitives from @sase/ui: Button, Card, Input, Label, Badge (with stage variant), Dialog, Tabs, Separator, Skeleton, Accordion
Hooks & Stores
Hooks:
| Hook | Purpose |
|---|---|
useAuth() |
Auth state + Better Auth client (user, isAdmin, signIn, signUp, signOut, session) |
useCategoryParts(vehicleId, categoryId) |
TanStack Query for schema + parts + hotspots |
useSchemaInteraction() |
Pan/zoom/pinch event handlers for schema viewer |
usePageMeta(options) |
Sets <title>, meta description, canonical, OG/Twitter tags; restores defaults on unmount |
useChangelog() |
TanStack Query for changelog entries (staleTime: 30min) |
Stores (Zustand):
| Store | State |
|---|---|
useAuthStore() |
`{ user: User |
useSchemaStore() |
{ highlightedGroup, selectedGroup, zoom, panX, panY, isFullscreen } |
Data Fetching: Custom ApiClient class (lib/api-client.ts)
- Base URL:
/api(proxied via Vite dev server) - Methods:
get<T>,post<T>,patch<T>,delete<T>,upload<T> - Credentials: cookies (automatic)
- Caching: TanStack React Query 5 (
staleTime: 60s,retry: 1, no refetch on focus)
Lib Utilities
| File | Purpose |
|---|---|
api-client.ts |
HTTP client wrapper with Faro error integration |
auth-client.ts |
Better Auth client (signIn, signUp, signOut, useSession) |
i18n.ts |
Zustand-based i18n (tr/en), useTranslation() hook |
posthog.ts |
PostHog analytics initialization and tracking |
faro.ts |
Grafana Faro frontend observability |
user-settings.ts |
Theme/settings persistence |
toast.ts |
Toast notification export |
category-icons.ts |
Category icon mappings |
Internationalization
Implementation: Custom Zustand store with JSON message files
| Locale | File | Coverage |
|---|---|---|
| Turkish (default) | messages/tr.json |
Full |
| English | messages/en.json |
Full |
Usage: const { t, locale, setLocale } = useTranslation() → t("nav.search")
Persistence: localStorage("sase-locale"), updates document.documentElement.lang
Shared Packages
@sase/shared (packages/shared/src/)
| Directory | Exports |
|---|---|
types/ |
User, UserProfile, UserSubscriptionSummary, Vehicle, VinDecodeResult, CategoryNode, VehicleSource, Brand, Plan, Subscription, UserBrand, CreateSubscriptionInput, SubscriptionStatus, Payment, PaymentMethod, PaymentStatus, Part, PartSource, PartSearchResult, Category, CategoryWithSchema, SchemaPic, Hotspot, ApiResponse, ApiError, PaginationMeta, PaginationInput, PaginatedResult, ChangelogEntry, CreateChangelogEntry, UpdateChangelogEntry, ChangelogChangeType |
schemas/ |
loginSchema, registerSchema, forgotPasswordSchema, resetPasswordSchema, vinSchema, paginationSchema, changelogEntrySchema, createChangelogEntrySchema, updateChangelogEntrySchema, changelogChangeTypeEnum (Zod) |
constants/ |
ERROR_CODES (30+, prefixed AUTH/VIN/SUB/PAY), PLANS (Single/Double/Triple/Full), REFERRAL_REWARDS (Tier 1: 3→7d, Tier 2: 5→30d), VIN_REGEX, EMAIL_REGEX, OEM_CODE_REGEX, CURRENCY |
utils/ |
VIN validator (check digit, WMI extraction, model year decode), currency (formatTRY, kurus↔lira), formatters (VIN, date, datetime, Turkish slug, referral code) |
Dependency: zod ^3.24.0
@sase/config (packages/config/src/index.ts)
Zod env schema exporting envSchema, Env type, validateEnv().
Groups: DATABASE_URL, REDIS_, BETTER_AUTH_, GOOGLE_, MINIO_, CORS_ORIGIN, STRIPE_, PL24_, EMEX_, ML_PREDICTION_ENABLED, POSTAL_, OTEL_*
@sase/ui (packages/ui/src/)
Components: Button (CVA variants), Input, Card (6 compound parts), Badge (CVA, includes stage variant: alpha/beta/prod), Label, Skeleton, Separator, Dialog (10 compound parts), Tabs (4 compound parts), Accordion (4 compound parts), cn() utility
Dependencies: Radix UI (accordion, dialog, dropdown-menu, label, popover, select, separator, slot, tabs, tooltip), class-variance-authority, clsx, tailwind-merge, lucide-react
Testing
Unit Tests (Vitest 3)
| Category | Files | Total Lines |
|---|---|---|
| Service specs | 12 (admin, brands, categories, changelog, parts, payments, plans, referrals, subscriptions, translations, users, vehicles) | 2,521 |
| Guard specs | 3 (auth, roles, brand-access) | 380 |
| Pipe specs | 1 (vin-validation) | 77 |
| Integration specs | 1 (corgi) | 121 |
| Telemetry specs | 1 | 97 |
| Total API | 18 test files | 3,196 lines |
Test Config:
- API:
apps/api/vitest.config.ts—src/**/*.spec.ts, v8 coverage (text + lcov) - Web:
apps/web/vitest.config.ts—src/**/*.{test,spec}.{ts,tsx}, jsdom,@/alias, test-setup (jest-dom)
Test Pattern: Mock Drizzle DB with chainable query builder, vi.mock() for external deps
Playwright 1.50
- Installed at root level (
package.json) - Used by EMEX integration (
emex.browser.ts) for web scraping - Used by PartsCatalogs auth service for JWT capture via Playwright
- Test scripts in
scripts/(vin-e2e-test.js)
Infrastructure & Deployment
Docker Compose (Development)
| Service | Image | Port | Volume |
|---|---|---|---|
| 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 |
Staging / prod:
docker-compose.coolify.yml(repo root) defines the deployed services —api,worker, andsase-redis(Redis 7.4). PostgreSQL and MinIO are external/managed in those environments.
SEO Infrastructure
apps/web/scripts/prerender.mjs— Pre-renders public pages to static HTML (landing, blog posts, pricing, etc.) for crawler/bot visibilityapps/web/public/robots.txt— Crawl directives with sitemap referenceapps/web/public/sitemap.xml— Static sitemap for public pagesapps/web/index.html— Contains default OG/Twitter meta tags;usePageMeta()overrides at runtime per page
Nginx (docker/nginx/sites/)
sase.tr.conf— Frontend SPA +/apiproxy +/collect/Faro telemetry CORS proxy + gzip (level 6) + 1-year asset cache + security headersapi.sase.tr.conf— NestJS proxy (60s timeout for VIN decode) + SSL + blocked paths (.git, .env, node_modules)
CI/CD
GitHub Actions — .github/workflows/qa-gate.yml (PR gate, 15min timeout):
- Triggers on PRs touching
apps/api/**orapps/web/**; cancels superseded runs for the same PR. - Detects which app changed, then runs
pnpm --filter <app> testonly for the changed app(s) (Node 22 + pnpm).
Deploy — Coolify (staging) + GitHub Actions → PM2 (production):
devbranch → Coolify → https://dev.sase.tr via Gitea/GitHub webhook. The container is built from the rootDockerfile; runtime services are defined indocker-compose.coolify.yml(api,worker,sase-redis). Migrations run on container start viaapps/api/start.sh, before the server boots.mainbranch → production (https://sase.tr) is promoted manually by the maintainer (mergedev → main), then shipped via GitHub Actions → SSH → PM2 (pnpm build+pnpm db:migrate+pm2 reload). SeeAGENTS.mdfor the dev-only deploy policy.
PM2 Configuration
| Process | Command | Port | Memory |
|---|---|---|---|
sase-api |
pnpm dev (dev) / dist/main.js (prod) |
4000 | 512MB |
sase-web |
pnpm dev (dev) / serve dist (prod) |
3000 | 512MB |
sase-worker |
dist/worker.js |
— | 256MB |
Environment Variables
Required
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
REDIS_PASSWORD |
Redis authentication |
BETTER_AUTH_SECRET |
Auth encryption key (min 32 chars) |
BETTER_AUTH_URL |
Auth service URL |
MINIO_ENDPOINT |
MinIO/S3 endpoint |
MINIO_ACCESS_KEY |
MinIO access key |
MINIO_SECRET_KEY |
MinIO secret key |
MINIO_PUBLIC_URL |
Public URL for stored files |
CORS_ORIGIN |
Allowed origins |
Optional
| Variable | Default | Description |
|---|---|---|
PORT |
4000 | API server port |
REDIS_HOST |
127.0.0.1 | Redis host |
REDIS_PORT |
6379 | Redis port |
MINIO_BUCKET_NAME |
sase-schemas | Storage bucket |
MINIO_USE_SSL |
false | HTTPS for MinIO |
GOOGLE_CLIENT_ID |
— | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
— | Google OAuth secret |
STRIPE_SECRET_KEY |
— | Stripe payment secret key |
STRIPE_PUBLISHABLE_KEY |
— | Stripe publishable key |
PL24_BASE_URL |
— | PL24 catalog API URL |
PL24_COMPANY_CODE |
— | PL24 company code |
PL24_USERNAME |
— | PL24 credentials |
PL24_PASSWORD |
— | PL24 credentials |
EMEX_USERNAME |
— | EMEX scraper credentials |
EMEX_PASSWORD |
— | EMEX scraper credentials |
ML_PREDICTION_ENABLED |
false | Enable ML predictions |
POSTAL_API_URL |
— | Postal email service URL |
POSTAL_API_KEY |
— | Postal API key |
POSTAL_FROM_ADDRESS |
noreply@sase.tr | Sender email |
POSTAL_FROM_NAME |
Sase.tr | Sender name |
OTEL_ENABLED |
false | Enable OpenTelemetry |
OTEL_EXPORTER_OTLP_ENDPOINT |
— | OTLP collector endpoint |
OTEL_EXPORTER_OTLP_HEADERS |
— | OTLP auth headers |
OTEL_SERVICE_NAME |
sase-api | OTel service name |
OTEL_TRACE_SAMPLE_RATE |
1.0 | Trace sampling rate (0-1) |
Full schema: packages/config/src/index.ts
Key Commands
# Development
pnpm dev # Start all apps (Turbo)
pnpm dev --filter=api # API only
pnpm dev --filter=web # Web only
# Build & Quality
pnpm build # Build all packages + apps
pnpm lint # Biome lint check
pnpm typecheck # TypeScript --noEmit
pnpm test # Run all tests (Vitest)
# Database (run from apps/api/)
pnpm db:push # Push schema to DB (Drizzle)
pnpm db:studio # Open Drizzle Studio
pnpm db:seed # Seed database
pnpm db:generate # Generate migration
# Route generation
pnpm --filter web exec tsr generate
# Production
pm2 start ecosystem.config.js
pm2 reload all
Key Dependencies
| Package | Version | Purpose |
|---|---|---|
| NestJS | 10.4 | Backend framework |
| Drizzle ORM | 0.41 | Database ORM |
| Better Auth | 1.2 | Cookie-based auth |
| React | 19 | Frontend UI |
| TanStack Router | 1.120 | File-based routing |
| TanStack Query | 5 | Server state management |
| Zustand | 5 | Client state management |
| Tailwind CSS | 4 | Styling |
| Vite | 6.3 | Build tool |
| Vitest | 3.x | Unit testing |
| Playwright | 1.50 | Browser automation |
| BullMQ | 5.30 | Job queues |
| Biome | latest | Linting/formatting |
| Turborepo | 2.x | Monorepo orchestration |
| OpenTelemetry | 0.212 | Backend observability |
| Grafana Faro | 2.2 | Frontend observability |
| PostHog | latest | Product analytics |
Quick Start
docker compose -f docker/docker-compose.yml up -d— Start PostgreSQL, Redis, MinIOcp apps/api/.env.example apps/api/.env— Configure env varspnpm install— Install dependenciespnpm --filter api db:push && pnpm --filter api db:seed— Setup databasepnpm dev— Start all services- Open
http://localhost:3000— Frontend - Admin login:
admin@sase.tr/Sase2026
Pricing Model
| Plan | Brands | Monthly | Yearly |
|---|---|---|---|
| 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 | Unlimited | 999 TRY | 9,990 TRY |
Referral Rewards:
- Tier 1: 3 referrals → 7-day subscription extension
- Tier 2: 5 referrals → 30-day subscription extension
Documentation
| File / Dir | Topic |
|---|---|
docs/INDEX.md |
This file — comprehensive project reference |
README.md |
Quick stack overview & commands (Turkish) |
CLAUDE.md |
Claude Code project guide |
AGENTS.md |
Fusion agent deploy policy (dev-only; prod is human-merged) |
knowledge.md |
Long-form knowledge base (Dify.ai export) |
docs/pl24-catalog/*.md |
Per-brand PL24 catalog notes (BMW, Mercedes, Ford, …) + _summary.md |
docs/product/*.md |
CRO funnel audits & product shortlists |
docs/design-specs/*.md |
UX/design specs (e.g. VIN-decode error branches) |
docs/clarification/*.md |
Open requirement clarifications |
docs/00-testing.md · docs/TASK_REGISTRY_GUIDELINES.md |
Testing guide · task-registry conventions |
docs/analytics-queries.sql |
Saved analytics SQL |