version: "3.9"

# ---------------------------------------------------------------------------
# Bulk Gmail Mailer — production-style compose stack.
#
# Services:
#   postgres  - primary datastore (18-table schema, migrated via alembic)
#   redis     - celery broker/result backend + rate-limit/DLQ/pubsub storage
#   migrate   - one-shot: runs `alembic upgrade head`, then exits
#   api       - FastAPI app served by gunicorn+uvicorn workers (port 8000)
#   worker    - celery worker consuming the `send` queue (real Gmail sending)
#
# Usage:
#   cp .env.example .env        # fill in real secrets first
#   docker compose up -d --build
#   docker compose logs -f api worker
#
# The API is reachable at http://localhost:8000 (see README for HTTPS/reverse
# proxy notes — this compose file does not terminate TLS itself).
# ---------------------------------------------------------------------------

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: mailer
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mailer_prod_pw}
      POSTGRES_DB: bulkmailer
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mailer -d bulkmailer"]
      interval: 5s
      timeout: 5s
      retries: 10
    ports:
      - "127.0.0.1:5432:5432"   # remove/comment in real production; internal-only is safer

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10
    ports:
      - "127.0.0.1:6379:6379"   # remove/comment in real production; internal-only is safer

  migrate:
    build: .
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
    entrypoint: ["alembic", "upgrade", "head"]
    restart: "no"

  api:
    build: .
    restart: unless-stopped
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
    ports:
      - "8000:8000"
    volumes:
      - uploads_data:/app/uploads
    environment:
      DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://mailer:mailer_prod_pw@postgres:5432/bulkmailer}
      REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
    # NOTE: set ENCRYPTION_KEY explicitly in .env for production. If left
    # blank, each container auto-generates its OWN key file at
    # /app/.encryption_key on first boot — since api/worker are separate
    # containers without a shared volume for it, that would make tokens
    # encrypted by one unreadable by the other. Always set ENCRYPTION_KEY.

  worker:
    build: .
    restart: unless-stopped
    env_file: .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      migrate:
        condition: service_completed_successfully
    volumes:
      - uploads_data:/app/uploads
    environment:
      DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://mailer:mailer_prod_pw@postgres:5432/bulkmailer}
      REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
    # worker_concurrency is set in app/worker/celery_app.py; scale replicas
    # (docker compose up -d --scale worker=3) for higher throughput instead
    # of raising in-process concurrency, to keep per-account rate limiting
    # (enforced in Redis) predictable across workers.
    entrypoint: ["celery", "-A", "app.worker.celery_app", "worker",
                 "--loglevel=INFO", "-Q", "send,control"]

volumes:
  pg_data:
  redis_data:
  uploads_data:
