Files
sase.tr/docker-compose.coolify.yml
semih 3cfa3d588a
Some checks are pending
QA Gate (P0/P1) / Test affected app (pull_request) Waiting to run
feat(pl24): PL24 girişi 1 saat başarısızsa Telegram uyarısı
İki hesap banı da günler sonra, DB satır sayılarından fark edildi (tr 07-24 →
08-18'de, de 09-04 → 09-16'da). Üçüncüsünde bir saat içinde haber olsun.

- Yeni global `TelegramService` (aynı bot: TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID;
  yapılandırılmamışsa sessizce devre dışı, istek yolunu asla etkilemez).
- `PL24AuthService` kesinti saatini Redis'te tutuyor (`pl24:auth:fail-since:<hesap>`),
  böylece api+worker ve yeniden başlatmalar aynı başlangıcı görür — crash-loop
  saati sıfırlayamaz. Başarılı login saati siler.
- Kesinti 60 dakikayı geçince tek sefer uyarı (`pl24:auth:alerted:<hesap>` NX,
  6 saat) — hesap kodu, süre, son hata ve "banlanmış olabilir" notu ile.
  Düzelince sessiz "🟢 düzeldi" mesajı.
- Devre kesici açıkken de saat işler: kesici login denemesini bastırdığı için
  aksi halde hesap en ölü olduğu anda saat duruyordu.
- compose'a eksik env referansları: TELEGRAM_*, PL24_HTTP_DAILY_MAX,
  PL24_HTTP_USER_RESERVE, PREFETCH_PL24_FAST_DEPTH, PREFETCH_PL24_DELAY_MS
  (Coolify env'i ancak compose'da referanslıysa container'a ulaşıyor).

Test: 3 yeni test (1 saat dolmadan susar, dolunca tek sefer uyarır, düzelince
kurtarma mesajı + saat sıfırlanır). Redis stub'ı gerçek SET NX semantiğine
çekildi (alarm tekrarının bastırılması buna dayanıyor). 80 PL24 testi geçti.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 13:53:15 +03:00

301 lines
15 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:-}
- VITE_GOOGLE_ADS_ID=${VITE_GOOGLE_ADS_ID:-}
- VITE_GOOGLE_ADS_SIGNUP_LABEL=${VITE_GOOGLE_ADS_SIGNUP_LABEL:-}
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:-}
- PL24_TR_DISABLED=${PL24_TR_DISABLED:-}
- 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:-}
# Vinpin ePER VIN-decode fallback (flag-gated, off by default)
- VINPIN_ENABLED=${VINPIN_ENABLED:-false}
- VINPIN_URL=${VINPIN_URL:-https://login.tr.vinpin.online}
- VINPIN_USER=${VINPIN_USER:-}
- VINPIN_PASS=${VINPIN_PASS:-}
- VINPIN_WARM_DAEMON=${VINPIN_WARM_DAEMON:-false}
# Set to "false" to skip opening Rpartstore entirely during a known upstream
# Rpartstore outage → Renault decodes go straight to Dialogys (drops the
# per-decode launch-error probe + "Loading application..." stray + budget burn).
- VINPIN_RPARTSTORE_ENABLED=${VINPIN_RPARTSTORE_ENABLED:-true}
- 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:-}
# Unsubscribe links (List-Unsubscribe header + mail footer); empty secret = mailto-only
- UNSUBSCRIBE_SECRET=${UNSUBSCRIBE_SECRET:-}
- UNSUBSCRIBE_URL_BASE=${UNSUBSCRIBE_URL_BASE:-}
- UNSUBSCRIBE_EMAIL=${UNSUBSCRIBE_EMAIL:-}
# PostHog server-side analytics (payments, subscription lifecycle, $revenue).
# Empty key → PostHogService no-ops (server-side analytics disabled).
- POSTHOG_API_KEY=${POSTHOG_API_KEY:-}
# Personal/secure key → LOCAL feature-flag evaluation (source kill switches,
# guarded decode rollout, remote config). Empty → flags fail open (inert).
- POSTHOG_PERSONAL_API_KEY=${POSTHOG_PERSONAL_API_KEY:-}
# "false" on non-prod → evaluate flags but DON'T emit analytics to prod project.
- POSTHOG_CAPTURE_ENABLED=${POSTHOG_CAPTURE_ENABLED:-}
- POSTHOG_HOST=${POSTHOG_HOST:-https://eu.i.posthog.com}
# Meta Conversions API — server-side signup tracking. Empty → CAPI no-ops.
- META_CAPI_PIXEL_ID=${META_CAPI_PIXEL_ID:-}
- META_CAPI_ACCESS_TOKEN=${META_CAPI_ACCESS_TOKEN:-}
- META_CAPI_TEST_EVENT_CODE=${META_CAPI_TEST_EVENT_CODE:-}
- 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:-}
# P snapshot lookup (db `td`) backing the OEM detail page. Off until
# both vars are set in Coolify; see sase-prod-db-api-access memory.
- P_DB_ENABLED=${P_DB_ENABLED:-false}
- P_DB_URL=${P_DB_URL:-}
# Tedarikçi fiyat geçmişi (takip MySQL, Tailscale 100.82.193.79). Boş →
# part-prices uçları matched:false döner, UI fiyat bölümü çizmez.
- SUPPLIER_PRICE_DB_ENABLED=${SUPPLIER_PRICE_DB_ENABLED:-false}
- SUPPLIER_PRICE_DB_URL=${SUPPLIER_PRICE_DB_URL:-}
# Central Directus CMS (Süper Panel project on Coolify). Prod and staging
# point at the SAME instance so blog content is shared across envs.
# Internal docker-network URL — both stacks join the `coolify` network.
- DIRECTUS_URL=${DIRECTUS_URL:-}
- DIRECTUS_TOKEN=${DIRECTUS_TOKEN:-}
- DIRECTUS_PROJECT=${DIRECTUS_PROJECT:-sase}
# Bearer token for POST /blog/posts/internal (Süper Panel n8n pipeline)
- BLOG_AUTOMATION_TOKEN=${BLOG_AUTOMATION_TOKEN:-}
# Catalog prefetch / backfill tuning (in-process worker lives in THIS api
# container, not worker.js). All empty → compiled-in defaults; set in Coolify
# to ramp without a code change. PL24 rate stays conservative unless rotating
# residential proxies are confirmed (upstream ban risk). See
# sase-coolify-compose-env-injection + sase-catalog-backfill memories.
- CATALOG_BACKFILL_ENABLED=${CATALOG_BACKFILL_ENABLED:-}
- PREFETCH_CONCURRENCY=${PREFETCH_CONCURRENCY:-}
- PREFETCH_RATE_MAX=${PREFETCH_RATE_MAX:-}
- PREFETCH_MAX_DEPTH=${PREFETCH_MAX_DEPTH:-}
- PREFETCH_RATE_PL24=${PREFETCH_RATE_PL24:-}
- PREFETCH_DAILY_PL24=${PREFETCH_DAILY_PL24:-}
- PREFETCH_PL24_FAST_DEPTH=${PREFETCH_PL24_FAST_DEPTH:-}
- PREFETCH_PL24_DELAY_MS=${PREFETCH_PL24_DELAY_MS:-}
- PL24_HTTP_DAILY_MAX=${PL24_HTTP_DAILY_MAX:-}
- PL24_HTTP_USER_RESERVE=${PL24_HTTP_USER_RESERVE:-}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
- PREFETCH_RATE_EMEX=${PREFETCH_RATE_EMEX:-}
- PREFETCH_RATE_PCAT=${PREFETCH_RATE_PCAT:-}
- PREFETCH_PCAT_DELAY_MS=${PREFETCH_PCAT_DELAY_MS:-}
- PREFETCH_PL24_START=${PREFETCH_PL24_START:-}
- PREFETCH_PL24_END=${PREFETCH_PL24_END:-}
- PREFETCH_NORESULT_MAX=${PREFETCH_NORESULT_MAX:-}
- PREFETCH_NORESULT_TTL_DAYS=${PREFETCH_NORESULT_TTL_DAYS:-}
- PREFETCH_COMPLETE_TTL_DAYS=${PREFETCH_COMPLETE_TTL_DAYS:-}
# VIN foto-OCR (POST /vehicles/decode/ocr): OpenRouter üzerinden vision
# modeli. Model env'leri boşsa koddaki varsayılanlar geçerli
# (google/gemini-2.5-flash + openai/gpt-5.4-nano yedek).
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
- VIN_OCR_MODEL=${VIN_OCR_MODEL:-}
- VIN_OCR_FALLBACK_MODEL=${VIN_OCR_FALLBACK_MODEL:-}
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:-}
- PL24_TR_DISABLED=${PL24_TR_DISABLED:-}
- 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:-}
# Vinpin ePER VIN-decode fallback (worker drives the headless browser)
- VINPIN_ENABLED=${VINPIN_ENABLED:-false}
- VINPIN_URL=${VINPIN_URL:-https://login.tr.vinpin.online}
- VINPIN_USER=${VINPIN_USER:-}
- VINPIN_PASS=${VINPIN_PASS:-}
- VINPIN_WARM_DAEMON=${VINPIN_WARM_DAEMON:-false}
# Skip Rpartstore during a known upstream outage (Renault → straight to Dialogys).
- VINPIN_RPARTSTORE_ENABLED=${VINPIN_RPARTSTORE_ENABLED:-true}
# 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:-}
# Unsubscribe links (List-Unsubscribe header + mail footer); empty secret = mailto-only
- UNSUBSCRIBE_SECRET=${UNSUBSCRIBE_SECRET:-}
- UNSUBSCRIBE_URL_BASE=${UNSUBSCRIBE_URL_BASE:-}
- UNSUBSCRIBE_EMAIL=${UNSUBSCRIBE_EMAIL:-}
# PostHog server-side analytics (payments, subscription lifecycle, $revenue).
# Empty key → PostHogService no-ops (server-side analytics disabled).
- POSTHOG_API_KEY=${POSTHOG_API_KEY:-}
# Personal/secure key → LOCAL feature-flag evaluation (source kill switches,
# guarded decode rollout, remote config). Empty → flags fail open (inert).
- POSTHOG_PERSONAL_API_KEY=${POSTHOG_PERSONAL_API_KEY:-}
# "false" on non-prod → evaluate flags but DON'T emit analytics to prod project.
- POSTHOG_CAPTURE_ENABLED=${POSTHOG_CAPTURE_ENABLED:-}
- POSTHOG_HOST=${POSTHOG_HOST:-https://eu.i.posthog.com}
# Meta Conversions API — server-side signup tracking. Empty → CAPI no-ops.
- META_CAPI_PIXEL_ID=${META_CAPI_PIXEL_ID:-}
- META_CAPI_ACCESS_TOKEN=${META_CAPI_ACCESS_TOKEN:-}
- META_CAPI_TEST_EVENT_CODE=${META_CAPI_TEST_EVENT_CODE:-}
- 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:-}
# Tedarikçi fiyat tazeleme cron'u (19:30 İstanbul) bu env'lerle çalışır;
# boşsa processor sessiz no-op.
- SUPPLIER_PRICE_DB_ENABLED=${SUPPLIER_PRICE_DB_ENABLED:-false}
- SUPPLIER_PRICE_DB_URL=${SUPPLIER_PRICE_DB_URL:-}
# Çeviri worker'ı bu anahtarı kullanıyordu ama compose'a hiç eklenmemişti
# (sase-coolify-compose-env-injection gotcha'sı): Coolify'da tanımlansa
# bile container'a ulaşmıyordu. VIN foto-OCR işiyle birlikte düzeltildi.
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
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