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
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) |
| 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: ... }viaTransformInterceptor - Error format:
{ success: false, error: { code, message } }viaHttpExceptionFilter - Auth: Cookie-based via Better Auth. Skip with
@Public()decorator. Get user with@CurrentUser(). - Roles:
@Roles("admin")decorator + globalRolesGuard - 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 toapps/web/src/in Vite.@sase/*maps topackages/*/srcin tsconfig. - Route files: TanStack Router auto-generates
routeTree.gen.ts— never edit manually - i18n: Turkish default (
tr.json), English available (en.json). UseuseTranslation()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:
/apirequests →http://localhost:4000in 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)
GET /catalog/brands→ check user subscription access per brandGET /catalog/brands/:name/models→ PL24fetchVehicleList()→ stored incatalogVehiclestableGET /catalog/vehicles/:id/categories→ PL24fetchMainGroups()→ stored incategorieswithcatalogVehicleIdGET /catalog/vehicles/:id/categories/:categoryId→ PL24fetchSubGroupsByPath()/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
brandCountfield:0= unlimited access,N= limited to N brands - Brand access tracked in
userBrandsjunction 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 tablesemex.ts— EMEX scraper cache tablespl24.ts— PL24 catalog cache tablesparts-catalogs.ts— PartsCatalogs API cache tablesrelations.ts— Drizzle ORM relationships
Key tables in core.ts:
users,sessions,accounts,verifications— Better Auth managedbrands,plans— Catalog of available brands/plans (admin-managed)userSubscriptions— status: pending/active/trial/cancelled/expireduserBrands— junction table controlling brand access per subscriptionpayments— Stripe or EFT, status trackingvehicles— one per unique VIN, shared across users viauserVehiclesuserVehicles— junction (userId + vehicleId unique), tracks lastAccessedAtcategories— parent-child hierarchy; has bothvehicleId(VIN-based) andcatalogVehicleId(VIN-less) FKs (nullable for the other mode)parts— OEM code, quantity, hotspot index; same dual FK pattern as categoriesschemaPics— exploded view images + hotspots JSONB, linked to categoriescatalogVehicles— VIN-less catalog: one record per PL24 service vehicle (unique on source + serviceVehicleId)queryLogs— VIN decode audit trailoemCodeCopies— 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 OAuthSTRIPE_SECRET_KEY/PUBLISHABLE_KEY— Payment processingPL24_BASE_URL/COMPANY_CODE/USERNAME/PASSWORD— PL24 catalog APIEMEX_USERNAME/PASSWORD— EMEX scraperPCAT_USE_PROXY(true),PCAT_PROXY_HOST(gw.dataimpulse.com),PCAT_PROXY_USER/PASS— PartsCatalogs proxyPOSTAL_API_URL/API_KEY,POSTAL_FROM_ADDRESS(noreply@sase.tr),POSTAL_FROM_NAME(Sase.tr) — EmailOTEL_ENABLED(false),OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_SERVICE_NAME(sase-api),OTEL_TRACE_SAMPLE_RATE(1.0) — OpenTelemetryML_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) andcatalogVehicleId(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 catalogVehiclesunique key is(source, serviceVehicleId)— not by VIN (these vehicles may not have VINs)routeTree.gen.tsis 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'(notnetworkidle2);page.context().cookies()(notpage.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.mdthroughdocs/13-analytics-posthog.md - Memory:
.claude/projects/-home-s-ss/memory/MEMORY.md