# Requires a `.env` next to this file (or exported vars).
# Validates: scripts/verify-prod-env.sh
#
# Images are pinned instead of `:latest` for reproducible builds and supply-chain hygiene.
services:
  minio:
    image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
    container_name: minio
    ports:
      - "${MINIO_API_PORT}:9000"      # S3 API → http://${PUBLIC_HOST}:${MINIO_API_PORT}
      - "127.0.0.1:${MINIO_CONSOLE_PORT}:9001"  # Console admin-only via SSH tunnel
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
      # Public URL browsers use for the S3 API (match reverse-proxy TLS scheme/host when applicable).
      MINIO_SERVER_URL: ${MINIO_SERVER_URL:-http://${PUBLIC_HOST}:${MINIO_API_PORT}}
      MINIO_BROWSER_REDIRECT_URL: ${MINIO_BROWSER_REDIRECT_URL:-http://${PUBLIC_HOST}:${MINIO_CONSOLE_PORT}}
      # Community MinIO has no per-bucket PutBucketCors; set explicit SPA origin(s) in `.env`.
      MINIO_API_CORS_ALLOW_ORIGIN: ${MINIO_API_CORS_ALLOW_ORIGIN:?Set MINIO_API_CORS_ALLOW_ORIGIN to your HTTPS SPA origin}
    volumes:
      - ./assets/minio-data:/data
    command: server /data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    restart: unless-stopped

  # One-shot: ensure buckets. Browser CORS is MINIO_API_CORS_ALLOW_ORIGIN on the minio service.
  minio-cors:
    image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z
    container_name: minio-cors
    depends_on:
      minio:
        condition: service_healthy
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD"
        for b in initiative-attachments initiative-exports initiative-quarantine imagehub-blobs; do
          mc mb -p "local/$$b" 2>/dev/null || true
        done
        echo "MinIO buckets ensured."

  # Auth + roles: POSTGRES_* apply only on first volume init — see docs/deploy-production-docker.md
  postgres:
    image: postgres:16-alpine
    container_name: initiative-postgres
    # Bind to localhost only — DB is not for the public internet.
    ports:
      - "127.0.0.1:15432:5432"
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - initiative_pg_data:/var/lib/postgresql/data
      - ./be0/migrations/001_initiative_schema.sql:/docker-entrypoint-initdb.d/01_initiative_schema.sql:ro
      - ./be0/migrations/002_application_storage_extensions.sql:/docker-entrypoint-initdb.d/02_application_storage_extensions.sql:ro
      - ./be0/migrations/003_review_documents.sql:/docker-entrypoint-initdb.d/03_review_documents.sql:ro
      - ./be0/migrations/004_evidence_artifact_review.sql:/docker-entrypoint-initdb.d/04_evidence_artifact_review.sql:ro
      - ./be0/migrations/004_application_admin_results.sql:/docker-entrypoint-initdb.d/05_application_admin_results.sql:ro
      - ./be0/migrations/006_user_notifications.sql:/docker-entrypoint-initdb.d/06_user_notifications.sql:ro
      - ./be0/migrations/007_user_roles_email_policy_admin.sql:/docker-entrypoint-initdb.d/07_user_roles_email_policy_admin.sql:ro
      - ./be0/migrations/008_audit_events.sql:/docker-entrypoint-initdb.d/08_audit_events.sql:ro
      - ./be0/migrations/009_backup_artifact_roles_storage_kind.sql:/docker-entrypoint-initdb.d/09_backup_artifact_roles_storage_kind.sql:ro
      - ./be0/migrations/010_user_staff_profiles.sql:/docker-entrypoint-initdb.d/10_user_staff_profiles.sql:ro
      - ./be0/migrations/011_academic_titles_vn.sql:/docker-entrypoint-initdb.d/11_academic_titles_vn.sql:ro
      - ./be0/migrations/012_password_reset.sql:/docker-entrypoint-initdb.d/12_password_reset.sql:ro
      - ./be0/migrations/013_email_verification.sql:/docker-entrypoint-initdb.d/13_email_verification.sql:ro
      - ./be0/migrations/014_registration_otp.sql:/docker-entrypoint-initdb.d/14_registration_otp.sql:ro
      - ./be0/migrations/015_document_templates.sql:/docker-entrypoint-initdb.d/15_document_templates.sql:ro
      - ./be0/migrations/016_research_projects.sql:/docker-entrypoint-initdb.d/16_research_projects.sql:ro
      - ./be0/migrations/017_imagehub_datasets.sql:/docker-entrypoint-initdb.d/17_imagehub_datasets.sql:ro
      - ./be0/migrations/018_imagehub_segmentation_links.sql:/docker-entrypoint-initdb.d/18_imagehub_segmentation_links.sql:ro
      - ./be0/migrations/019_imagehub_cloud_import.sql:/docker-entrypoint-initdb.d/19_imagehub_cloud_import.sql:ro
      - ./be0/migrations/020_imagehub_dataset_stages.sql:/docker-entrypoint-initdb.d/20_imagehub_dataset_stages.sql:ro
      - ./be0/migrations/021_imagehub_task_pipeline.sql:/docker-entrypoint-initdb.d/21_imagehub_task_pipeline.sql:ro
      - ./be0/migrations/022_imagehub_task_annotations.sql:/docker-entrypoint-initdb.d/22_imagehub_task_annotations.sql:ro
      - ./be0/migrations/023_imagehub_dataset_members.sql:/docker-entrypoint-initdb.d/23_imagehub_dataset_members.sql:ro
      - ./be0/migrations/024_imagehub_dataset_project_link.sql:/docker-entrypoint-initdb.d/24_imagehub_dataset_project_link.sql:ro
      - ./be0/migrations/025_imagehub_task_review_events.sql:/docker-entrypoint-initdb.d/25_imagehub_task_review_events.sql:ro
      - ./be0/migrations/026_imagehub_file_folder_path.sql:/docker-entrypoint-initdb.d/26_imagehub_file_folder_path.sql:ro
      - ./be0/migrations/027_imagehub_dataset_label_map.sql:/docker-entrypoint-initdb.d/27_imagehub_dataset_label_map.sql:ro
    # Evaluate user/db inside the container ($$…) so Compose .env substitution stays in sync at runtime.
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 30s
    restart: unless-stopped

  # API — must become healthy (Postgres + MinIO + successful startup) before fe0 starts.
  be0:
    build:
      context: ./be0
      dockerfile: Dockerfile
    container_name: be0
    ipc: host
    ports:
      - "127.0.0.1:4402:4402"
    healthcheck:
      test: ["CMD-SHELL", "curl -sf http://127.0.0.1:4402/health >/dev/null"]
      interval: 10s
      timeout: 10s
      retries: 15
      start_period: 180s
    environment:
      - GENERIC_TIMEZONE=UTC
      - ENVIRONMENT=production
      - JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env — openssl rand -base64 48}
      - INITIATIVE_DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      - APPLICATION_DRAFT_DIR=/app/assets/application-drafts
      - SUBMITTED_INITIATIVES_DIR=/app/submitted-initiatives
      - S3_ENDPOINT_URL=http://minio:9000
      - S3_ACCESS_KEY=${MINIO_ROOT_USER}
      - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD}
      - S3_BUCKET_ATTACHMENTS=initiative-attachments
      - S3_BUCKET_EXPORTS=initiative-exports
      - S3_BUCKET_QUARANTINE=initiative-quarantine
      # Presigned GET/PUT host the browser opens — must be HTTPS when the SPA is HTTPS (see docs/minio-behind-https.md).
      - S3_PUBLIC_ENDPOINT_URL=${S3_PUBLIC_ENDPOINT_URL:-http://${PUBLIC_HOST}:${MINIO_API_PORT}}
      - CORS_ORIGINS=http://${PUBLIC_HOST}:${FE_PORT},${CORS_ORIGINS_EXTRA:-}
      - AUTH_ADMIN_EMAILS=${AUTH_ADMIN_EMAILS:-}
      # SMTP — registration OTP + password reset (same vars as docs; set in `.env`).
      - SMTP_HOST=${SMTP_HOST:-}
      - SMTP_PORT=${SMTP_PORT:-587}
      - SMTP_USER=${SMTP_USER:-}
      - SMTP_PASSWORD=${SMTP_PASSWORD:-}
      - AUTH_MAIL_FROM=${AUTH_MAIL_FROM:-}
      - SMTP_USE_TLS=${SMTP_USE_TLS:-1}
      - AUTH_PUBLIC_WEB_ORIGIN=${AUTH_PUBLIC_WEB_ORIGIN:-}
      - AUTH_MAIL_LOG_ONLY=${AUTH_MAIL_LOG_ONLY:-}
      - TEMPLATE_APPLICATION_FORM_DOCX=/app/template_application_form.docx
    volumes:
      - ./be0:/app
      - ./assets:/app/assets
      - ./assets/submitted-initiatives:/app/submitted-initiatives
      - ./fe0/public/assets/template_application_form.docx:/app/template_application_form.docx:ro
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy
    # Dockerfile entrypoint: NLTK download + pip install + uvicorn (no --reload in prod).
    restart: unless-stopped

  # Public applicant SPA — minified static build served by nginx (NOT the Vite dev server).
  # Build context is the repo root (npm workspace); see frontend_user/Dockerfile.prod.
  frontend_user:
    build:
      context: .
      dockerfile: frontend_user/Dockerfile.prod
    container_name: frontend_user
    ports:
      - "${FE_PORT}:8080"
    depends_on:
      be0:
        condition: service_healthy
    restart: unless-stopped

  # Admin / council SPA — also a hardened static build, but bound to LOCALHOST only.
  # Reach it via an SSH tunnel or a separate authenticated reverse-proxy vhost.
  # NOTE: the council review UI is still in progress — keep it off the public internet for now.
  frontend_admin:
    build:
      context: .
      dockerfile: frontend_admin/Dockerfile.prod
    container_name: frontend_admin
    ports:
      - "127.0.0.1:${FE_ADMIN_PORT:-8082}:8080"
    depends_on:
      be0:
        condition: service_healthy
    restart: unless-stopped

  # Principal-investigator SPA (research proposals + project cockpit) — hardened static build.
  frontend_investigator:
    build:
      context: .
      dockerfile: frontend_investigator/Dockerfile.prod
    container_name: frontend_investigator
    ports:
      - "${FE_INV_PORT:-8083}:8080"
    depends_on:
      be0:
        condition: service_healthy
    restart: unless-stopped

  frontend_publisher:
    build:
      context: .
      dockerfile: frontend_publisher/Dockerfile.prod
    container_name: frontend_publisher
    ports:
      - "${FE_PUB_PORT:-8084}:8080"
    depends_on:
      be0:
        condition: service_healthy
    restart: unless-stopped

volumes:
  initiative_pg_data:

# All services join Compose’s default project network; DNS names postgres, be0, minio work.
# Do not set your public VPS IP here — use PUBLIC_HOST + ports in `.env` / `ports:`.
