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 | Repo: /home/s/ss
Generated: 2026-03-02
Table of Contents
Architecture Overview
Monorepo powered by pnpm workspaces + Turborepo.
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 |
Iyzico (card), EFT (bank transfer with receipt upload) |
| Storage |
MinIO (S3-compatible) |
| Jobs |
BullMQ (Redis-backed queues) |
| Email |
Postal (transactional email) |
| Analytics |
PostHog (product analytics) |
| Observability |
OpenTelemetry (API), Grafana Faro (frontend) |
| Testing |
Vitest 3, Playwright 1.50 |
| Linting |
Biome (2-space, double quotes, semicolons, trailing commas) |
| CI/CD |
GitHub Actions → SSH deploy → PM2 |
| 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)
│ │ │ ├── app.module.ts # Root module (global guards, interceptors, filters)
│ │ │ ├── worker.ts # Standalone 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/ # Iyzico + EFT payment processing
│ │ │ ├── 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
│ │ │ ├── analytics/ # Usage analytics tracking
│ │ │ ├── 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 |
| 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 |
Iyzico card payments, EFT with receipt upload, admin approval |
| 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, payment approval, analytics |
| AnalyticsModule |
module, service, controller |
Usage analytics tracking |
| 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) |
| 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) |
| Iyzico |
Payment API |
— |
Turkish 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 tables
emex.ts — EMEX scraper cache tables
pl24.ts — PL24 catalog cache tables
parts-catalogs.ts — PartsCatalogs API cache tables
relations.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 (iyzico/eft), status (pending/completed/failed/refunded)
├── iyzicoPaymentId, eftReceiptUrl, adminNote
└── Indexes: userId, status
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
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 in parts-catalogs.ts)
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 initialization
tracing.ts — Distributed tracing (API)
worker-tracing.ts — Worker process tracing
metrics.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/iyzico/initialize |
User |
Start Iyzico card payment |
POST |
/api/payments/iyzico/callback |
Public |
Iyzico webhook callback |
POST |
/api/payments/eft |
User |
Create EFT payment |
POST |
/api/payments/eft/:id/receipt |
User |
Upload EFT receipt (PNG/JPG/PDF, 5MB max) |
PATCH |
/api/payments/eft/:id/approve |
Admin |
Approve EFT payment |
PATCH |
/api/payments/eft/:id/reject |
Admin |
Reject EFT payment |
GET |
/api/payments/me |
User |
Payment history |
GET |
/api/payments/pending |
Admin |
Pending EFT payments |
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 |
POST |
/api/vehicles/decode |
User |
Decode VIN (rate limited: 20/min) |
GET |
/api/vehicles/history |
User |
Vehicle history (paginated) |
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) |
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 |
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/payments/pending |
Admin |
Pending EFT 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) |
Analytics
| Method |
Path |
Auth |
Description |
POST |
/api/analytics/oem-copy |
User |
Track OEM code copy event (oemCode, partId?, vehicleId?, categoryId?) |
Authentication
Provider: Better Auth 1.2
User model extensions: role (default: "user"), referralCode, referredBy
Frontend Web
Routes & Pages
Router: TanStack Router (file-based, auto-generated route tree — 36 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 |
/dashboard/subscription/pay |
routes/dashboard/subscription/pay.tsx |
Card (Iyzico) or EFT payment |
/dashboard/billing |
routes/dashboard/billing.tsx |
Payment history & receipts |
/dashboard/settings |
routes/dashboard/settings.tsx |
Profile, Security, Connections, Referral, Account 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) |
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/payments |
routes/dashboard/admin/payments.tsx |
EFT approval workflow |
/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 |
| 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, Dialog, Tabs, Separator, Skeleton
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 |
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, IyzicoInitializeInput, EftPaymentInput, Part, PartSource, PartSearchResult, Category, CategoryWithSchema, SchemaPic, Hotspot, ApiResponse, ApiError, PaginationMeta, PaginationInput, PaginatedResult |
schemas/ |
loginSchema, registerSchema, forgotPasswordSchema, resetPasswordSchema, vinSchema, paginationSchema (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, IYZICO_, PL24_, EMEX_, ML_PREDICTION_ENABLED, POSTAL_, OTEL_*
@sase/ui (packages/ui/src/)
Components: Button (CVA variants), Input, Card (6 compound parts), Badge (CVA), Label, Skeleton, Separator, Dialog (10 compound parts), Tabs (4 compound parts), cn() utility
Dependencies: Radix UI (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 |
11 (admin, brands, categories, 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 |
17 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 |
SEO Infrastructure
apps/web/scripts/prerender.mjs — Pre-renders public pages to static HTML (landing, blog posts, pricing, etc.) for crawler/bot visibility
apps/web/public/robots.txt — Crawl directives with sitemap reference
apps/web/public/sitemap.xml — Static sitemap for public pages
apps/web/index.html — Contains default OG/Twitter meta tags; usePageMeta() overrides at runtime per page
Nginx (docker/nginx/sites/)
sase.tr.conf — Frontend SPA + /api proxy + /collect/ Faro telemetry CORS proxy + gzip (level 6) + 1-year asset cache + security headers
api.sase.tr.conf — NestJS proxy (60s timeout for VIN decode) + SSL + blocked paths (.git, .env, node_modules)
CI/CD (GitHub Actions)
ci.yml — Runs on all branches & PRs to main (15min timeout):
- Biome lint
- TypeScript type check
- Vitest unit tests
- Full build
deploy.yml — Runs on push to main (10min timeout):
- SSH into production
git pull origin main
pnpm install
pnpm build
pnpm db:push (migrations)
- PM2 reload all
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 |
IYZICO_API_KEY |
— |
Iyzico payment API key |
IYZICO_SECRET_KEY |
— |
Iyzico payment secret |
IYZICO_BASE_URL |
— |
Iyzico API base URL |
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
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, MinIO
cp apps/api/.env.example apps/api/.env — Configure env vars
pnpm install — Install dependencies
pnpm --filter api db:push && pnpm --filter api db:seed — Setup database
pnpm 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 |
Topic |
docs/INDEX.md |
This file — comprehensive project reference |
CLAUDE.md |
Claude Code project guide |
.claude/product-marketing-context.md |
Marketing context |
docs/00-overview.md — docs/13-analytics-posthog.md |
Detailed topic docs |