# Coolify deployment — Build Pack: Docker Compose
# This is the production APP stack (app + workers ONLY). MariaDB and Redis are
# run as SEPARATE Coolify Database Resources, not part of this compose.
#   - Create a standalone MariaDB resource and a standalone Redis resource in Coolify.
#   - Enable "Connect to Predefined Network" on THIS app stack.
#   - Set DATABASE_URL / REDIS_URL / MESSENGER_TRANSPORT_DSN in Coolify's env tab,
#     pointing at the resources via their internal hostnames (mariadb-<uuid> / redis-<uuid>).
# The ddev local env uses its own compose.yaml; do not confuse the two.
# Select this file (docker-compose.yml) as the Compose file in the Coolify resource.
# Do NOT define custom `networks:` — Coolify manages the network; custom ones
# break Traefik routing (per Coolify docs).

# Shared application environment, reused by app + both workers.
x-app-env: &app-env
  APP_ENV: prod
  APP_DEBUG: "0"
  APP_SECRET: ${APP_SECRET}
  # Connection strings point at the SEPARATE Coolify DB/Redis resources.
  # Real values are injected from Coolify's Environment Variables tab — the
  # hostname is mariadb-<uuid> / redis-<uuid> from each resource's page, so they
  # cannot be hardcoded here (the UUID differs per installation).
  DATABASE_URL: ${DATABASE_URL}
  MESSENGER_TRANSPORT_DSN: ${MESSENGER_TRANSPORT_DSN}
  REDIS_URL: ${REDIS_URL}
  JWT_SECRET_KEY: "%kernel.project_dir%/config/jwt/private.pem"
  JWT_PUBLIC_KEY: "%kernel.project_dir%/config/jwt/public.pem"
  JWT_PASSPHRASE: ${JWT_PASSPHRASE}
  DEFAULT_URI: ${APP_BASE_URL}
  APP_BASE_URL: ${APP_BASE_URL}
  ALLOWED_FRONTEND_HOSTS: ${ALLOWED_FRONTEND_HOSTS}
  TRUSTED_PROXIES: ${TRUSTED_PROXIES}
  API_IR_BASE_URL: ${API_IR_BASE_URL}
  API_IR_TOKEN: ${API_IR_TOKEN}
  KAVENEGAR_API_KEY: ${KAVENEGAR_API_KEY}
  REFRESH_TOKEN_TTL: "2592000"
  OTP_TTL: "1200"
  MAX_FILE_SIZE_BYTES: "5242880"
  UPLOAD_DIR: var/uploads
  # ALTCHA self-hosted captcha. HMAC key is a secret → inject from Coolify's
  # Environment Variables tab. Symfony reads this via %env(bool:...)% so
  # true/false (or 1/0) both work; کپچا در prod روشن است.
  ALTCHA_ENABLED: "false"
  ALTCHA_HMAC_KEY: ${ALTCHA_HMAC_KEY}
  ALTCHA_MAX_NUMBER: "100000"
  ALTCHA_EXPIRE_SECONDS: "300"
  # لاگین سرویسی کرالر: هدر X-Service-Token با این مقدار مقایسه می‌شود و فقط کپچای
  # لاگین را دور می‌زند (رمز و rate-limit سر جای خود). مقدار = همان CLINICPRO_SERVICE_TOKEN
  # سمت کرالر. از تب Environment Variables کولیفای تزریق می‌شود؛ خالی = بایپس غیرفعال.
  CRAWLER_SERVICE_TOKEN: ${CRAWLER_SERVICE_TOKEN:-}

# Shared volume mounts for services that read/write JWT keys and uploads.
x-app-volumes: &app-volumes
  - jwt_keys:/app/config/jwt
  - uploads_public:/app/public/uploads
  - uploads_var:/app/var/uploads

services:
  # Web app (PHP-FPM + Nginx).
  # In Coolify, assign ALL serving domains to THIS service (port 80) — the backend
  # API host plus every frontend domain you want Traefik to route + issue TLS for.
  # Coolify supports a comma-separated domain list on the service.
  # CORS/payload allow-listing for those frontends is driven by ALLOWED_FRONTEND_HOSTS
  # (the CORS regex is built from it in PHP via CorsRegexEnvProcessor; see
  # docker/frontend-domains.json). CORS_ALLOW_ORIGIN is no longer used.
  app:
    # Shared image tag — app builds it once; the two workers reuse the SAME image
    # (image: below, no build:). Without this Coolify builds the Dockerfile 3×,
    # tripling apk network fetches and intermittently failing on the host.
    image: clinicpro-app:latest
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      <<: *app-env
      RUN_INIT: "1" # runs JWT keygen + migrations on start (only this service)
    volumes: *app-volumes
    networks: [coolify]
    # Hit the real /health route (nginx on 8080, non-root) via docker/healthcheck.sh.
    healthcheck:
      test: ["CMD", "/usr/local/bin/healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 60s

  # SMS / async message consumer. Reuses the image built by `app` (no build:).
  worker-async:
    image: clinicpro-app:latest
    restart: unless-stopped
    command: sh -c "while :; do php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M; sleep 1; done"
    environment:
      <<: *app-env
      RUN_INIT: "0"
    volumes: *app-volumes
    networks: [coolify]
    depends_on: [app]
    # Messenger consumers handle SIGTERM gracefully — give the in-flight message
    # time to finish before the container is killed.
    stop_grace_period: 30s
    # No port to probe — just confirm the consumer process is alive (busybox ps).
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "ps -o args 2>/dev/null | grep -q '[m]essenger:consume async' || exit 1",
        ]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

  # Scheduler consumer — expires unpaid appointments every minute. Reuses app's image.
  worker-scheduler:
    image: clinicpro-app:latest
    restart: unless-stopped
    command: sh -c "while :; do php bin/console messenger:consume scheduler_default --time-limit=3600 --memory-limit=128M; sleep 1; done"
    environment:
      <<: *app-env
      RUN_INIT: "0"
    volumes: *app-volumes
    networks: [coolify]
    depends_on: [app]
    stop_grace_period: 30s
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "ps -o args 2>/dev/null | grep -q '[m]essenger:consume scheduler_default' || exit 1",
        ]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

volumes:
  jwt_keys:
  uploads_public:
  uploads_var:

networks:
  coolify:
    name: coolify
    driver: bridge
    external: true
