Files
sase.tr/docker-compose.coolify.yml
Semih Yesilyurt 3613caa072
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
fix(catalog-source): gate emex parts behind allowlist (safety)
The catalog-wide bridge in EmexSourceDbService.fetchCategoryParts was
measured against vehicle_parts on 2026-06-01 and found to return 7-114x
more parts than belong to the requesting vehicle, with 49-98 wrong OEM
codes per 100 served. That directly violates the project rule that the
user must never see a wrong OEM.

Per-catalog noiseRatio sample (catalog-wide / per-vehicle):
  RENAULT201910 51x | FFIAT84 45x | VOLVO201410 24x | MB201810 14x
  AU1587 8x | BMW202501 70x (+ gid namespace mismatch ETK vs numeric)
  GM_C201809 114x | MINI202501 12x | LRE201412 7x | MAZDA2020 54x
  GM_OP201809 dump has only 1 wildcard vehicle (unique_key="_") so the
  single Crossland X "owns" all 47k Opel parts — same firehose served
  to any Opel sub-model in sase prod.

All alternative bridges were proven dead:
  SSD eşleştirme        - session-bound, 0/91 sase SSDs match dump
  scrape_queue_v2.vehicle_ssd - same session SSD format
  api_cache replay      - table empty (0 rows)
  wizard_parameters     - table empty (0 rows)
  VIN direct            - no VIN column in dump
The only viable per-vehicle bridge is vehicles.unique_key reconstruction
from raw_data.parsedOptions, but sase currently stores the required 4
wizard fields on just 5/103 emex vehicles (all Renault). That work is
follow-up; this patch only stops the bleeding.

Change:
- Add EMEX_SOURCE_DB_ALLOWED_CATALOGS env (comma-separated, default "")
- EmexSourceDbService.fetchCategoryParts returns null unless catalogCode
  is in the allowlist. Empty allowlist = service is effectively off for
  parts, full fallthrough to live emex.
- Connection pool stays alive so the follow-up per-vehicle bridge /
  schema-only path can use it without flipping env.
- Boot logs warn loudly when connected with an empty allowlist.

Prod was never affected — CATALOG_SOURCE_DB_ENABLED was unset there. This
fixes dev branch behaviour (default-on since commit 3a3a7d3) and keeps
prod safe by default once main is promoted.

Files:
- packages/config/src/index.ts        env schema + audit notes
- apps/api/src/config/configuration.ts parse allowlist into string[]
- apps/api/src/integrations/catalog-source-db/emex-source-db.service.ts
  allowlist field, init logging, fetchCategoryParts gate, class doc
- docker-compose.coolify.yml          env injection for api + worker

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 18:39:59 +03:00

190 lines
8.2 KiB
YAML

services:
api:
build:
context: .
dockerfile: Dockerfile
args:
- VITE_FARO_ENABLED=${VITE_FARO_ENABLED:-false}
- VITE_FARO_COLLECTOR_URL=${VITE_FARO_COLLECTOR_URL:-}
- VITE_POSTHOG_KEY=${VITE_POSTHOG_KEY:-}
- VITE_META_PIXEL_ID=${VITE_META_PIXEL_ID:-}
- VITE_CHATWOOT_BASE_URL=${VITE_CHATWOOT_BASE_URL:-}
- VITE_CHATWOOT_WEBSITE_TOKEN=${VITE_CHATWOOT_WEBSITE_TOKEN:-}
# Sentry browser SDK — gated on DSN presence (unset = skip init).
# VITE_* args must be build-time; runtime env won't reach the bundle.
- VITE_SENTRY_DSN=${VITE_SENTRY_DSN:-}
- VITE_SENTRY_ENVIRONMENT=${VITE_SENTRY_ENVIRONMENT:-production}
- VITE_SENTRY_RELEASE=${VITE_SENTRY_RELEASE:-}
environment:
- NODE_ENV=production
- PORT=4000
- DATABASE_URL=${DATABASE_URL}
- REDIS_HOST=sase-redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- BETTER_AUTH_URL=${BETTER_AUTH_URL}
# Süper Panel ↔ Sase.tr internal admin token (X-Internal-Token header)
- INTERNAL_API_TOKEN=${INTERNAL_API_TOKEN:-}
- INTERNAL_IMPERSONATION_SECRET=${INTERNAL_IMPERSONATION_SECRET:-}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
- MINIO_ENDPOINT=http://minio-global:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-sase-schemas}
- MINIO_PUBLIC_URL=${MINIO_PUBLIC_URL}
- MINIO_USE_SSL=false
- CORS_ORIGIN=${CORS_ORIGIN}
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY:-}
- STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY:-}
- PL24_BASE_URL=${PL24_BASE_URL:-https://www.partslink24.com}
- PL24_COMPANY_CODE=${PL24_COMPANY_CODE:-}
- PL24_USERNAME=${PL24_USERNAME:-}
- PL24_PASSWORD=${PL24_PASSWORD:-}
- PL24_COMPANY_CODE_2=${PL24_COMPANY_CODE_2:-}
- PL24_USERNAME_2=${PL24_USERNAME_2:-}
- PL24_PASSWORD_2=${PL24_PASSWORD_2:-}
- PL24_PROXY_DE=${PL24_PROXY_DE:-}
- EMEX_USERNAME=${EMEX_USERNAME:-}
- EMEX_PASSWORD=${EMEX_PASSWORD:-}
- EMEX_USE_PROXY=${EMEX_USE_PROXY:-false}
- EMEX_PROXY_HOST=${EMEX_PROXY_HOST:-74.81.81.81}
- EMEX_PROXY_USER=${EMEX_PROXY_USER:-1726bbe361918676d44e}
- EMEX_PROXY_PASS=${EMEX_PROXY_PASS:-f11c7b6128cc86c6}
- PCAT_USE_PROXY=${PCAT_USE_PROXY:-true}
- PCAT_PROXY_HOST=${PCAT_PROXY_HOST:-gw.dataimpulse.com}
- PCAT_PROXY_USER=${PCAT_PROXY_USER:-}
- PCAT_PROXY_PASS=${PCAT_PROXY_PASS:-}
- POSTAL_API_URL=${POSTAL_API_URL:-}
- POSTAL_API_KEY=${POSTAL_API_KEY:-}
- POSTAL_FROM_ADDRESS=${POSTAL_FROM_ADDRESS:-noreply@sase.tr}
- POSTAL_FROM_NAME=${POSTAL_FROM_NAME:-Sase.tr}
# Novu lifecycle e-mail automation (Tailscale-only; sends via Postal)
- NOVU_API_URL=${NOVU_API_URL:-https://api.bildirim.semih.ai}
- NOVU_API_KEY=${NOVU_API_KEY:-}
- APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://sase.tr}
- MAILTRACK_SECRET=${MAILTRACK_SECRET:-}
- OTEL_ENABLED=${OTEL_ENABLED:-false}
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
- OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-sase-api}
- OTEL_TRACE_SAMPLE_RATE=${OTEL_TRACE_SAMPLE_RATE:-1.0}
- ML_PREDICTION_ENABLED=${ML_PREDICTION_ENABLED:-false}
# Chatwoot live-chat widget — HMAC secret for verified user identity
- CHATWOOT_HMAC_TOKEN=${CHATWOOT_HMAC_TOKEN:-}
# Local catalog-dump lookup (CategoriesService → PcatSourceDb / EmexSourceDb).
# Defaults off; when CATALOG_SOURCE_DB_ENABLED=true + URLs set, parts fetches
# hit the dump first and only fall through to live upstream on miss.
- CATALOG_SOURCE_DB_ENABLED=${CATALOG_SOURCE_DB_ENABLED:-false}
- PCAT_SOURCE_DB_URL=${PCAT_SOURCE_DB_URL:-}
- EMEX_SOURCE_DB_URL=${EMEX_SOURCE_DB_URL:-}
# Per-source kill switches. pcat default off (dump doesn't cover TR vehicles).
- EMEX_SOURCE_DB_ENABLED=${EMEX_SOURCE_DB_ENABLED:-true}
- PCAT_SOURCE_DB_ENABLED=${PCAT_SOURCE_DB_ENABLED:-false}
# Per-catalog parts-lookup allowlist (comma-separated). Empty → all
# fetchCategoryParts calls return null. See sase-emex-source-db-safety
# memory: catalog-wide bridge had 7-114x noise; only add a catalog once
# its per-vehicle bridge is wired & OEM-verified.
- EMEX_SOURCE_DB_ALLOWED_CATALOGS=${EMEX_SOURCE_DB_ALLOWED_CATALOGS:-}
depends_on:
sase-redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4000/api/health"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
labels:
- "traefik.docker.network=coolify"
expose:
- "4000"
networks:
- default
- coolify
worker:
build:
context: .
dockerfile: Dockerfile
command: ["node", "--enable-source-maps", "apps/api/dist/worker.js"]
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_HOST=sase-redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- MINIO_ENDPOINT=http://minio-global:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-sase-schemas}
- MINIO_PUBLIC_URL=${MINIO_PUBLIC_URL}
- MINIO_USE_SSL=false
- PL24_BASE_URL=${PL24_BASE_URL:-https://www.partslink24.com}
- PL24_COMPANY_CODE=${PL24_COMPANY_CODE:-}
- PL24_USERNAME=${PL24_USERNAME:-}
- PL24_PASSWORD=${PL24_PASSWORD:-}
- PL24_COMPANY_CODE_2=${PL24_COMPANY_CODE_2:-}
- PL24_USERNAME_2=${PL24_USERNAME_2:-}
- PL24_PASSWORD_2=${PL24_PASSWORD_2:-}
- PL24_PROXY_DE=${PL24_PROXY_DE:-}
- EMEX_USERNAME=${EMEX_USERNAME:-}
- EMEX_PASSWORD=${EMEX_PASSWORD:-}
- EMEX_USE_PROXY=${EMEX_USE_PROXY:-false}
- EMEX_PROXY_HOST=${EMEX_PROXY_HOST:-74.81.81.81}
- EMEX_PROXY_USER=${EMEX_PROXY_USER:-1726bbe361918676d44e}
- EMEX_PROXY_PASS=${EMEX_PROXY_PASS:-f11c7b6128cc86c6}
- PCAT_USE_PROXY=${PCAT_USE_PROXY:-true}
- PCAT_PROXY_HOST=${PCAT_PROXY_HOST:-gw.dataimpulse.com}
- PCAT_PROXY_USER=${PCAT_PROXY_USER:-}
- PCAT_PROXY_PASS=${PCAT_PROXY_PASS:-}
# Novu lifecycle e-mail automation — the worker fires trial-ending + win-back
- NOVU_API_URL=${NOVU_API_URL:-https://api.bildirim.semih.ai}
- NOVU_API_KEY=${NOVU_API_KEY:-}
- APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://sase.tr}
- MAILTRACK_SECRET=${MAILTRACK_SECRET:-}
- OTEL_ENABLED=${OTEL_ENABLED:-false}
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
- OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-sase-worker}
- OTEL_TRACE_SAMPLE_RATE=${OTEL_TRACE_SAMPLE_RATE:-1.0}
# Local catalog-dump lookup (worker drives prefetch which uses CategoriesService).
- CATALOG_SOURCE_DB_ENABLED=${CATALOG_SOURCE_DB_ENABLED:-false}
- PCAT_SOURCE_DB_URL=${PCAT_SOURCE_DB_URL:-}
- EMEX_SOURCE_DB_URL=${EMEX_SOURCE_DB_URL:-}
# Per-source kill switches. pcat default off (dump doesn't cover TR vehicles).
- EMEX_SOURCE_DB_ENABLED=${EMEX_SOURCE_DB_ENABLED:-true}
- PCAT_SOURCE_DB_ENABLED=${PCAT_SOURCE_DB_ENABLED:-false}
# Per-catalog parts-lookup allowlist (comma-separated). Empty → all
# fetchCategoryParts calls return null. See sase-emex-source-db-safety
# memory: catalog-wide bridge had 7-114x noise; only add a catalog once
# its per-vehicle bridge is wired & OEM-verified.
- EMEX_SOURCE_DB_ALLOWED_CATALOGS=${EMEX_SOURCE_DB_ALLOWED_CATALOGS:-}
depends_on:
sase-redis:
condition: service_healthy
networks:
- default
- coolify
sase-redis:
image: redis:7.4-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- default
volumes:
redis_data:
networks:
coolify:
external: true