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

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

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

18 KiB

SASE v2 — Claude Code Project Guide

VIN/chassis number lookup + auto parts catalog platform for the Turkish market. URL: https://sase.tr | Repo root: /home/s/ss

Tech Stack

Layer Technology
Monorepo pnpm 10.29 workspaces + Turborepo
Backend NestJS 10.4, TypeScript 5.7, Node 22
Database PostgreSQL 17 + Drizzle ORM 0.41
Cache/Queue Redis 7.4 + ioredis + BullMQ
Auth Better Auth 1.2 (cookie-based sessions)
Frontend Vite 6.3, React 19, TanStack Router 1.120, TanStack Query 5
Styling Tailwind CSS 4, shadcn/ui (Radix primitives)
State Zustand 5
Payments Stripe (card) + EFT (bank transfer)
Email Postal (transactional)
Storage MinIO (S3-compatible)
Analytics PostHog (product analytics)
Observability OpenTelemetry (API) + Grafana Faro (frontend)
Testing Vitest 3, Playwright 1.50
Linting Biome (2-space indent, double quotes, semicolons, trailing commas)
CI/CD GitHub Actions → SSH deploy → PM2

Project Structure

ss/
├── apps/
│   ├── api/                  # NestJS backend (port 4000, prefix /api)
│   │   └── src/
│   │       ├── main.ts       # Bootstrap (Helmet, CORS, rate limiting)
│   │       ├── app.module.ts # Root module (global guards/interceptors/filters)
│   │       ├── worker.ts     # BullMQ worker process
│   │       ├── database/schema/  # Drizzle ORM schemas (core.ts, emex.ts, pl24.ts, parts-catalogs.ts, relations.ts)
│   │       ├── common/       # Guards, interceptors, filters, decorators, pipes, DTOs
│   │       ├── integrations/ # corgi/, pl24/, emex/, parts-catalogs/, vin-api/
│   │       └── [modules]/    # auth, users, brands, plans, subscriptions, payments,
│   │                         # referrals, vehicles, categories, parts, catalog,
│   │                         # translations, analytics, admin, jobs, email, storage, redis,
│   │                         # telemetry
│   └── web/                  # Vite + React frontend (port 3000)
│       └── src/
│           ├── main.tsx      # Entry (RouterProvider, QueryClientProvider, Faro, PostHog)
│           ├── routes/       # TanStack Router file-based routes
│           ├── components/   # admin/, schema/, vehicles/, categories/, payment/, settings/, subscription/
│           ├── hooks/        # useAuth, useParts, useSchemaInteraction
│           ├── stores/       # Zustand: auth.store.ts, schema.store.ts
│           ├── lib/          # api-client, auth-client, i18n, posthog, faro, toast, user-settings, category-icons
│           └── messages/     # tr.json, en.json (i18n)
├── packages/
│   ├── shared/               # @sase/shared — types, Zod schemas, constants, utils
│   ├── config/               # @sase/config — Zod env validation schema
│   └── ui/                   # @sase/ui — shadcn-based React components
├── docker/                   # docker-compose.yml (PostgreSQL, Redis, MinIO) + nginx configs
├── scripts/                  # deploy.sh, test/debug scripts
├── docs/                     # INDEX.md + detailed docs (00-13)
└── ecosystem.config.js       # PM2 config (api, web, worker)

Key Commands

pnpm dev                        # Start all apps (Turbo)
pnpm build                      # Build all packages + apps
pnpm test                       # Run all tests (Vitest)
pnpm lint                       # Biome lint check
pnpm typecheck                  # TypeScript --noEmit

# 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

# Single app
pnpm dev --filter=api           # API only
pnpm dev --filter=web           # Web only

# Route generation (auto on dev/build, manual if needed)
pnpm --filter web exec tsr generate

Coding Conventions

  • Formatter: Biome — 2-space indent, 100-char line width, double quotes, semicolons, trailing commas
  • Module pattern: NestJS feature modules — each domain has module.ts, service.ts, controller.ts, *.dto.ts, *.spec.ts
  • API response format: All responses wrapped in { success: true, data: ... } via TransformInterceptor
  • Error format: { success: false, error: { code, message } } via HttpExceptionFilter
  • Auth: Cookie-based via Better Auth. Skip with @Public() decorator. Get user with @CurrentUser().
  • Roles: @Roles("admin") decorator + global RolesGuard
  • Validation: Zod schemas in @sase/shared, imported by both API and Web
  • DB naming: snake_case columns, camelCase in TypeScript (Drizzle mapping)
  • Frontend aliases: @/ maps to apps/web/src/ in Vite. @sase/* maps to packages/*/src in tsconfig.
  • Route files: TanStack Router auto-generates routeTree.gen.ts — never edit manually
  • i18n: Turkish default (tr.json), English available (en.json). Use useTranslation() hook → t("key")

Backend Modules

Module Purpose
AuthModule Better Auth (email/password + Google OAuth)
UsersModule Profile CRUD, password change, OAuth connections, account deletion
BrandsModule Brand CRUD (cached, admin-managed)
PlansModule Pricing plan CRUD (cached, admin-managed)
SubscriptionsModule Create, activate, cancel, resume, extend subscriptions
PaymentsModule Stripe card + EFT with receipt upload + admin approval
ReferralsModule Referral code generation, tier-based rewards
VehiclesModule VIN decode (multi-source fallback), vehicle history, brand access check
CategoriesModule Hierarchical category tree, schema pictures
PartsModule Parts by category, OEM code search
CatalogModule VIN-less catalog browser — PL24 brands, models, category trees, parts
TranslationsModule Automotive term translation (Redis → DB → Dictionary fallback)
AnalyticsModule OEM code copy tracking, usage analytics
AdminModule Dashboard stats, user management, payment approval
EmailModule Postal transactional emails (welcome, payment confirmation, password reset)
StorageModule S3/MinIO file upload/download
RedisModule Key-value cache operations
JobsModule BullMQ queues + processors + prefetch worker
TelemetryModule OpenTelemetry SDK (tracing, metrics)

Architecture Patterns

  • Global guards order: ThrottlerGuard → AuthGuard → RolesGuard
  • Global interceptors: TransformInterceptor → LoggingInterceptor → TimeoutInterceptor (30s)
  • Global filters: HttpExceptionFilter, DrizzleExceptionFilter (unique constraint → 409)
  • Vite proxy: /api requests → http://localhost:4000 in dev

VIN Decode Fallback Chain

Corgi (offline WMI) → PartsCatalogs API → PL24 API → EMEX scraper → NHTSA VIN API

Note: PartsCatalogs was added between Corgi and PL24 as it has broader VIN coverage. If multiple car matches return, the frontend prompts the user to select.

Category Fetch Chain (VIN-based)

DB cache → PL24 → PartsCatalogs → EMEX (lazy, source-based in getCategoryTree)

Catalog Browse Flow (VIN-less)

  1. GET /catalog/brands → check user subscription access per brand
  2. GET /catalog/brands/:name/models → PL24 fetchVehicleList() → stored in catalogVehicles table
  3. GET /catalog/vehicles/:id/categories → PL24 fetchMainGroups() → stored in categories with catalogVehicleId
  4. GET /catalog/vehicles/:id/categories/:categoryId → PL24 fetchSubGroupsByPath() / fetchPartsByPath() → stored lazily

PL24 Catalog Architectures

  • P5_MODERN (REST JSON API): VW Group, BMW, Mini, Mercedes, Porsche, Renault, Dacia, Alpine, Jaguar, Land Rover, Toyota, Lexus, MAN, Mitsubishi, Suzuki, etc.
  • LEGACY_PSA (HTML scraping): Citroën, Peugeot
  • LEGACY_FORD (HTML scraping): Ford passenger (wf0_parts) + commercial (fordt_parts)
  • LEGACY_HYUNDAI_KIA (HTML scraping): Hyundai, Kia
  • LEGACY_NISSAN (HTML scraping): Nissan, Infiniti
  • LEGACY_OPEL (HTML scraping): Opel, Vauxhall
  • LEGACY_VOLVO (HTML scraping): Volvo, Polestar

Our PL24 account (tr-903645) supports VAG group only for VIN-less catalog. Other brands may return errors on model listing. All P4 Legacy VIN decodes route through PL24FordLegacyService.decodeVinForService(vin, serviceName).

Caching Strategy

  • Redis: VIN decode results (24h), category trees (2h for catalog browser), parts fetches (1h), translations
  • HTTP Cache: brands, plans (30min Cache-Control)
  • Sessions: Better Auth (5min Redis)

Job Queues (BullMQ)

Queue Trigger Schedule
EMEX_SCRAPE On-demand (VIN decode)
CATALOG_PREFETCH After VIN decode — (depth-limited, rate-limited, cooldown-guarded)
SUBSCRIPTION_EXPIRY Cron Daily 3:00 AM
QUERY_CLEANUP Cron Weekly Sunday 4:00 AM

Subscription & Access Control

  • Plans have brandCount field: 0 = unlimited access, N = limited to N brands
  • Brand access tracked in userBrands junction table (userId + subscriptionId + brandId)
  • Full plan (brandCount=0) auto-adds all active brands on activation
  • BrandAccessGuard (per-route) verifies user's subscription includes the requested brand

Database Schema Summary

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

Key tables in core.ts:

  • users, sessions, accounts, verifications — Better Auth managed
  • brands, plans — Catalog of available brands/plans (admin-managed)
  • userSubscriptions — status: pending/active/trial/cancelled/expired
  • userBrands — junction table controlling brand access per subscription
  • payments — Stripe or EFT, status tracking
  • vehicles — one per unique VIN, shared across users via userVehicles
  • userVehicles — junction (userId + vehicleId unique), tracks lastAccessedAt
  • categories — parent-child hierarchy; has both vehicleId (VIN-based) and catalogVehicleId (VIN-less) FKs (nullable for the other mode)
  • parts — OEM code, quantity, hotspot index; same dual FK pattern as categories
  • schemaPics — exploded view images + hotspots JSONB, linked to categories
  • catalogVehicles — VIN-less catalog: one record per PL24 service vehicle (unique on source + serviceVehicleId)
  • queryLogs — VIN decode audit trail
  • oemCodeCopies — OEM code copy events (analytics)
  • referrals, passwordResetTokens, emexCategoryTranslations

Integrations

Integration Type Path Notes
Corgi Offline DB integrations/corgi/ WMI database for brand ID
PL24 REST API + HTML scraper integrations/pl24/ Multi-brand catalog; P5 (REST) + P4 Legacy (HTML). Services: pl24.service.ts, pl24-ford-legacy.service.ts
PartsCatalogs REST API + Playwright JWT integrations/parts-catalogs/ Broad VIN coverage; JWT captured via Playwright from partner sites; IP-bound via DataImpulse proxy
EMEX Browser scraper integrations/emex/ Playwright-based (emexdwc.ae), async via BullMQ
VIN-API REST API integrations/vin-api/ NHTSA VIN decoder (last-resort fallback)

Frontend Routes

Public: /, /pricing, /about, /contact, /blog, /demo, /privacy, /terms, /kvkk

Auth (layout _auth.tsx): /login, /register, /forgot-password, /reset-password

Dashboard (protected, layout dashboard.tsx):

  • /dashboard — Home
  • /dashboard/search — VIN decode input
  • /dashboard/history — Past VIN searches
  • /dashboard/subscription — Plan/brand selection
  • /dashboard/subscription/pay — Payment (Stripe or EFT)
  • /dashboard/billing — Payment history
  • /dashboard/settings — Profile, Security, Connections, Referral tabs
  • /dashboard/vehicles/$id — Vehicle details
  • /dashboard/vehicles/$id/categories/$categoryId — Interactive schema + parts

Catalog Browser (VIN-less, protected):

  • /dashboard/catalog — Brand grid with access flags
  • /dashboard/catalog/$brandName — Model list (from PL24)
  • /dashboard/catalog/$brandName/$modelId — Category tree/grid view
  • /dashboard/catalog/$brandName/$modelId/categories/$categoryId — Sub-categories or schema+parts

Admin (role-based):

  • /dashboard/admin — Stats + charts
  • /dashboard/admin/users — User management
  • /dashboard/admin/payments — EFT approval workflow
  • /dashboard/admin/referrals — Referral tracking
  • /dashboard/admin/analytics — Daily query stats
  • /dashboard/admin/copy-logs — OEM code copy tracking

Auth & Test Credentials

  • Admin: admin@sase.tr / Sase2026
  • Login: POST /api/auth/sign-in/email (returns session cookie)
  • Cookie: better-auth.session_token (or __Secure- prefix with HTTPS)
  • Test VIN: VW WVWZZZ1JZ3W597935

Environment Variables

Required: DATABASE_URL, REDIS_PASSWORD, BETTER_AUTH_SECRET (min 32 chars), BETTER_AUTH_URL, MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_PUBLIC_URL, CORS_ORIGIN

Optional (grouped):

  • PORT (4000), REDIS_HOST (127.0.0.1), REDIS_PORT (6379), MINIO_BUCKET_NAME (sase-schemas), MINIO_USE_SSL (false)
  • GOOGLE_CLIENT_ID/SECRET — Google OAuth
  • STRIPE_SECRET_KEY/PUBLISHABLE_KEY — Payment processing
  • PL24_BASE_URL/COMPANY_CODE/USERNAME/PASSWORD — PL24 catalog API
  • EMEX_USERNAME/PASSWORD — EMEX scraper
  • PCAT_USE_PROXY (true), PCAT_PROXY_HOST (gw.dataimpulse.com), PCAT_PROXY_USER/PASS — PartsCatalogs proxy
  • POSTAL_API_URL/API_KEY, POSTAL_FROM_ADDRESS (noreply@sase.tr), POSTAL_FROM_NAME (Sase.tr) — Email
  • OTEL_ENABLED (false), OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME (sase-api), OTEL_TRACE_SAMPLE_RATE (1.0) — OpenTelemetry
  • ML_PREDICTION_ENABLED (false)

Full schema: packages/config/src/index.ts

Common Gotchas

  • Frontend is Vite + TanStack Router — NOT Next.js
  • Categories & Parts tables have dual FKs: vehicleId (VIN-based, nullable) and catalogVehicleId (VIN-less, nullable) — always check which context you're in
  • PartsCatalogs JWT is IP-bound via DataImpulse proxy; the auth service manages a warm pool of JWTs using Playwright
  • P4 Legacy brands have no REST API — Ford, PSA, Hyundai/Kia, Nissan, Opel, Volvo all use HTML scraping through PL24FordLegacyService
  • catalogVehicles unique key is (source, serviceVehicleId) — not by VIN (these vehicles may not have VINs)
  • routeTree.gen.ts is auto-generated by TanStack Router — never edit manually
  • DrizzleExceptionFilter catches unique constraint violations → 409 Conflict
  • File uploads: PNG/JPG/PDF only, max 5MB (middleware in main.ts)
  • Env validation uses Zod from @sase/config — app won't start if env vars invalid
  • Playwright (EMEX/PartsCatalogs): waitUntil: 'networkidle' (not networkidle2); page.context().cookies() (not page.cookies())
  • VIN decode may return candidates if PartsCatalogs finds multiple matches — frontend shows selection modal

ast-grep — Structural Code Search & Refactoring

ast-grep (sg) does AST-aware pattern matching — finds code by structure, not text. Unlike grep, it understands syntax so foo( bar ) and foo(bar) both match the pattern foo($X).

Install: npm i -g @ast-grep/cli (already installed globally)

Pattern syntax

Syntax Meaning
$VAR Matches any single AST node (expression, identifier, etc.)
$$$ARGS Matches zero or more nodes (variadic — use for argument lists, statements)
Literal code Matches exact syntax structure

Common commands

# Search by pattern in TypeScript files
ast-grep -p 'console.log($$$)' -l ts apps/

# Search and preview rewrite (no changes yet)
ast-grep -p '$A && $A()' --rewrite '$A?.()' -l ts apps/

# Interactive rewrite — confirm each change
ast-grep -p '$A && $A()' --rewrite '$A?.()' --interactive -l ts apps/

# Apply all rewrites without confirmation
ast-grep -p '$A && $A()' --rewrite '$A?.()' --update-all -l ts apps/

# Output matches as JSON (useful for scripting)
ast-grep -p 'useQuery($$$)' -l tsx --json apps/web/src/

# Show surrounding context lines
ast-grep -p 'db.select()' -l ts -C 3 apps/api/src/

# Limit to specific file globs
ast-grep -p '@Public()' -l ts --globs 'apps/api/src/**/*.controller.ts' .

Language flags for this project

Flag Use for
-l ts API services, guards, modules, DTOs
-l tsx React components, route files
-l json i18n message files

Useful patterns for this codebase

# Find all @Public() decorated endpoints
ast-grep -p '@Public()' -l ts apps/api/src/

# Find all useQuery calls (TanStack Query)
ast-grep -p 'useQuery({$$$})' -l tsx apps/web/src/

# Find Drizzle inserts
ast-grep -p 'db.insert($TABLE).values($$$)' -l ts apps/api/src/

# Find all Redis cache sets
ast-grep -p 'this.redis.set($$$)' -l ts apps/api/src/

# Find t() translation calls missing a key
ast-grep -p 't($KEY)' -l tsx apps/web/src/

# Find all BullMQ queue.add() calls
ast-grep -p '$QUEUE.add($$$)' -l ts apps/api/src/jobs/

Documentation

  • Project index: docs/INDEX.md (comprehensive — routes, API endpoints, DB schema, components)
  • Marketing context: .claude/product-marketing-context.md
  • Detailed docs: docs/00-overview.md through docs/13-analytics-posthog.md
  • Memory: .claude/projects/-home-s-ss/memory/MEMORY.md