services:
  postgres:
    image: postgres:17-alpine
    restart: always
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: always
    volumes:
      - redis_data:/data
    command: redis-server --save 60 1 --requirepass ${REDIS_PASSWORD}

  minio:
    image: minio/minio:latest
    restart: always
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
      # The browser PUTs photo uploads directly to MinIO using a presigned URL
      # (STORAGE_PUBLIC_URL) — without CORS allowed for that origin, the browser
      # blocks the request. Restricted to the app's own origin, not "*".
      MINIO_API_CORS_ALLOW_ORIGIN: ${BETTER_AUTH_URL}
    ports:
      # Published so the browser can reach MinIO directly for presigned
      # up/downloads — same pattern as `web`'s ${WEB_PORT}: your Traefik LXC
      # routes a (sub)domain to <this-lxc-ip>:${STORAGE_PORT}, and that public
      # URL is what STORAGE_PUBLIC_URL must be set to.
      - "${STORAGE_PORT:-9000}:9000"
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 5s
      timeout: 5s
      retries: 5

  minio-init:
    image: minio/mc:latest
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
        mc alias set local http://minio:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD};
        mc mb --ignore-existing local/epicure-uploads;
        mc anonymous set download local/epicure-uploads;
        exit 0;
      "

  migrate:
    build:
      context: .
      dockerfile: Dockerfile
      target: migrator
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"

  web:
    build:
      context: .
      dockerfile: Dockerfile
      # Explicit target: the Dockerfile's last stage is no longer `runner` now
      # that `cron` was appended after it, and the default build target is
      # whatever stage comes last — pin it so this doesn't silently change.
      target: runner
      args:
        NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${NEXT_PUBLIC_VAPID_PUBLIC_KEY}
        NEXT_PUBLIC_GITHUB_ENABLED: ${NEXT_PUBLIC_GITHUB_ENABLED}
        NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
        NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
        STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
        NEXT_PUBLIC_STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
    restart: always
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
      STORAGE_ENDPOINT: http://minio:9000
      STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
      STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
      STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
      STORAGE_BUCKET: epicure-uploads
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
      BETTER_AUTH_URL: ${BETTER_AUTH_URL}
      ENCRYPTION_SECRET: ${ENCRYPTION_SECRET}
      NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${NEXT_PUBLIC_VAPID_PUBLIC_KEY}
      VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
      GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
      GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
      NEXT_PUBLIC_GITHUB_ENABLED: ${NEXT_PUBLIC_GITHUB_ENABLED}
      DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID}
      DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET}
      NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
      AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
      AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
      AUTHENTIK_BASE_URL: ${AUTHENTIK_BASE_URL}
      NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_SECURE: ${SMTP_SECURE}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASS: ${SMTP_PASS}
      SMTP_FROM: ${SMTP_FROM}
      STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
      CRON_SECRET: ${CRON_SECRET}
      OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
      OPENROUTER_DEFAULT_MODEL: ${OPENROUTER_DEFAULT_MODEL}
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
      OLLAMA_BASE_URL: ${OLLAMA_BASE_URL}
    ports:
      # published on portainer LXC host; traefik (other LXC) routes here via <this-lxc-ip>:${WEB_PORT}
      - "${WEB_PORT:-3000}:3000"
    depends_on:
      migrate:
        condition: service_completed_successfully
      redis:
        condition: service_started
      minio:
        condition: service_healthy
      minio-init:
        condition: service_completed_successfully

  # Fires the weekly digest email send by POSTing to `web`'s internal cron
  # route on a schedule (see cron/crontab). Tiny alpine+curl+crond
  # image — not part of the app build, just a periodic HTTP trigger.
  digest-cron:
    build:
      context: .
      dockerfile: Dockerfile
      target: cron
    restart: always
    environment:
      CRON_SECRET: ${CRON_SECRET}
      WEB_INTERNAL_URL: http://web:3000
    depends_on:
      - web

volumes:
  postgres_data:
  redis_data:
  minio_data:
