# 服务器部署用 Docker Compose（no-push 本地构建模式）
# 镜像来源：CI 容器内本地 docker build（不推送到 registry）
# 基础设施：MySQL + Redis 已在服务器 Docker 中运行（不在此文件管理）
#
# 部署目录：/opt/edu/
# 部署命令（CI 自动执行）：
#   docker compose up -d --build --remove-orphans
#
# 首次部署手动步骤：
#   1. sudo mkdir -p /opt/edu && sudo chown -R $USER:$USER /opt/edu
#   2. cp infra/docker-compose.deploy.yml /opt/edu/docker-compose.yml
#   3. cp infra/deploy.env.example /opt/edu/.env && 编辑填入生产密钥
#   4. docker compose up -d --build
#
# 注意：compose 文件中的 build.context 路径相对于 /opt/edu/ 目录
# CI 在 deploy 步骤会先把仓库 checkout 到 /opt/edu/repo/，再 cp compose 文件到 /opt/edu/

name: edu

services:
  # ============================================================
  # 应用服务（10 个：api-gateway + 3 Go/Python + 6 NestJS）
  # ============================================================
  api-gateway:
    build:
      context: ./repo/services/api-gateway
      dockerfile: Dockerfile
    container_name: edu-api-gateway
    restart: unless-stopped
    environment:
      API_GATEWAY_PORT: ${API_GATEWAY_PORT:-8080}
      JWT_SECRET: ${JWT_SECRET}
      JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud}
      JWT_AUDIENCE: ${JWT_AUDIENCE:-next-edu-cloud}
      # 生产环境强制关闭 dev-token 旁路
      DEV_MODE: "false"
      CLASSES_SERVICE_URL: http://classes:3001
      IAM_SERVICE_URL: http://iam:3002
      TEACHER_BFF_URL: http://teacher-bff:3003
      CORE_EDU_SERVICE_URL: http://core-edu:3004
      CONTENT_SERVICE_URL: http://content:3005
      DATA_ANA_SERVICE_URL: http://data-ana:3006
      MSG_SERVICE_URL: http://msg:3007
      AI_SERVICE_URL: http://ai:3008
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
    ports:
      - "${API_GATEWAY_PORT:-8080}:8080"
    depends_on:
      classes:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8080/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 10s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  classes:
    build:
      context: ./repo
      dockerfile: services/classes/Dockerfile
    container_name: edu-classes
    restart: unless-stopped
    environment:
      PORT: 3001
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      KAFKA_BROKERS: ${KAFKA_BROKERS:-}
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3001/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      - edu-net
      - edu-shared

  iam:
    build:
      context: ./repo
      dockerfile: services/iam/Dockerfile
    container_name: edu-iam
    restart: unless-stopped
    environment:
      PORT: 3002
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      JWT_SECRET: ${JWT_SECRET}
      JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud}
      JWT_AUDIENCE: ${JWT_AUDIENCE:-next-edu-cloud}
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      NODE_ENV: production
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3002/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      - edu-net
      - edu-shared

  teacher-bff:
    build:
      context: ./repo
      dockerfile: services/teacher-bff/Dockerfile
    container_name: edu-teacher-bff
    restart: unless-stopped
    environment:
      PORT: 3003
      IAM_SERVICE_URL: http://iam:3002
      CLASSES_SERVICE_URL: http://classes:3001
      CORE_EDU_SERVICE_URL: http://core-edu:3004
      LOG_LEVEL: ${LOG_LEVEL:-info}
      NODE_ENV: production
    depends_on:
      iam:
        condition: service_healthy
      classes:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3003/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  core-edu:
    build:
      context: ./repo
      dockerfile: services/core-edu/Dockerfile
    container_name: edu-core-edu
    restart: unless-stopped
    environment:
      PORT: 3004
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      KAFKA_BROKERS: ${KAFKA_BROKERS:-}
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      NODE_ENV: production
      DEV_MODE: "false"
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3004/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      - edu-net
      - edu-shared

  content:
    build:
      context: ./repo
      dockerfile: services/content/Dockerfile
    container_name: edu-content
    restart: unless-stopped
    environment:
      PORT: 3005
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      NEO4J_URL: ${NEO4J_URL:-}
      NEO4J_PASSWORD: ${NEO4J_PASSWORD:-}
      ES_URL: ${ES_URL:-}
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      NODE_ENV: production
      DEV_MODE: "false"
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3005/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      - edu-net
      - edu-shared

  msg:
    build:
      context: ./repo
      dockerfile: services/msg/Dockerfile
    container_name: edu-msg
    restart: unless-stopped
    environment:
      PORT: 3007
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      KAFKA_BROKERS: ${KAFKA_BROKERS:-}
      ES_URL: ${ES_URL:-}
      PUSH_GATEWAY_URL: http://push-gateway:8081
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      NODE_ENV: production
      DEV_MODE: "false"
    depends_on:
      push-gateway:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3007/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      - edu-net
      - edu-shared

  ai:
    build:
      context: ./repo/services/ai
      dockerfile: Dockerfile
    container_name: edu-ai
    restart: unless-stopped
    environment:
      PORT: 3008
      OPENAI_API_KEY: ${OPENAI_API_KEY:-}
      OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
      OTEL_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      DEV_MODE: "false"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3008/healthz')"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  data-ana:
    build:
      context: ./repo/services/data-ana
      dockerfile: Dockerfile
    container_name: edu-data-ana
    restart: unless-stopped
    environment:
      PORT: 3006
      CLICKHOUSE_HOST: ${CLICKHOUSE_HOST:-}
      CLICKHOUSE_PORT: ${CLICKHOUSE_PORT:-8123}
      CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-edu_analytics}
      CLICKHOUSE_USER: ${CLICKHOUSE_USER:-}
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-}
      OTEL_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      DEV_MODE: "false"
      KAFKA_BROKERS: ${KAFKA_BROKERS:-}
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3006/healthz')"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  push-gateway:
    build:
      context: ./repo/services/push-gateway
      dockerfile: Dockerfile
    container_name: edu-push-gateway
    restart: unless-stopped
    environment:
      PUSH_GATEWAY_PORT: 8081
      JWT_SECRET: ${JWT_SECRET}
      REDIS_URL: ${REDIS_URL}
      KAFKA_BROKERS: ${KAFKA_BROKERS:-kafka:29092}
      KAFKA_NOTIFICATION_TOPIC: edu.notification.requested
      KAFKA_CONSUMER_GROUP: push-gateway
      INTERNAL_API_TOKEN: ${INTERNAL_API_TOKEN:-edu-internal-token}
      JWKS_URL: http://iam:3002/v1/iam/.well-known/jwks.json
      WS_ALLOWED_ORIGINS: ${WS_ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:4000,http://localhost:4001,http://localhost:4002,http://localhost:4003}
      MAX_CONNS_PER_USER: "5"
      HEARTBEAT_INTERVAL_SECONDS: "30"
      OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      DEV_MODE: "false"
    ports:
      - "${PUSH_GATEWAY_PORT:-8081}:8081"
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8081/healthz"]
      interval: 30s
      timeout: 5s
      start_period: 10s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  teacher-portal:
    build:
      context: ./repo
      dockerfile: apps/teacher-portal/Dockerfile
    container_name: edu-teacher-portal
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 3000
      API_GATEWAY_URL: http://api-gateway:8080
    ports:
      - "${TEACHER_PORTAL_PORT:-3000}:3000"
    depends_on:
      api-gateway:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3000/"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - edu-net
      - edu-shared

  admin-portal:
    build:
      context: ./repo
      dockerfile: apps/admin-portal/Dockerfile
    container_name: edu-admin-portal
    restart: unless-stopped
    environment:
      NODE_ENV: production
      PORT: 4003
      API_GATEWAY_URL: http://api-gateway:8080
      # 生产环境强制关闭 mock，使用真实后端
      NEXT_PUBLIC_API_MOCKING: "disabled"
      NEXT_PUBLIC_MF_ENABLED: "false"
      NEXT_PUBLIC_SHELL_URL: http://admin-portal:4003
      NEXT_PUBLIC_WS_URL: ws://push-gateway:8081/ws
      NEXT_PUBLIC_OTEL_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
      NEXT_PUBLIC_WEB_VITALS_ENDPOINT: /api/admin/web-vitals
    ports:
      - "${ADMIN_PORTAL_PORT:-4003}:4003"
    depends_on:
      api-gateway:
        condition: service_healthy
      teacher-bff:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:4003/api/ready"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - edu-net
      - edu-shared

networks:
  # 应用服务内部网络
  edu-net:
    driver: bridge
  # 与已有 MySQL/Redis 共享的网络
  # 需确保 MySQL/Redis 容器已加入名为 edu-shared 的网络：
  #   docker network create edu-shared  （若不存在）
  #   docker network connect edu-shared edu-mysql
  #   docker network connect edu-shared edu-redis
  edu-shared:
    external: true
