# =============================================================================
# EFET backend — Portainer Git stack (backend + MySQL).
#
# Portainer clones this repository itself, so the build context is simply `.`
# and the Bitbucket credential lives in Portainer's stack configuration, never
# in this file.
#
#   Portainer > Stacks > Add stack > Repository
#     Repository URL          https://bitbucket.org/eae-team/efet-public.git
#     Authentication          on — user x-token-auth, password = repo access token
#     Reference               refs/heads/staging
#     Compose path            docker-compose.staging.yml
#   Environment variables     paste deploy/backend.env.example, filled in
#
# The frontend is a separate stack, deployed the same way from efet-frontend.
# The two only talk over the public API URL, through Traefik.
#
# REDEPLOYING: "Pull and redeploy" re-clones the repo but does NOT rebuild an
# image that already exists — a known Portainer limitation. Change IMAGE_TAG
# (staging-YYYY-MM-DD-HHMM) and the missing tag forces the build. See
# deploy/README.md.
# =============================================================================
services:
  backend:
    build:
      context: .
      target: production
      args:
        STACK_LABEL: ${IMAGE_NAME:-efet-public-backend}
    image: ${IMAGE_NAME:-efet-public-backend}:${IMAGE_TAG:-staging}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      APP_NAME: ${APP_NAME:-EFET}
      APP_ENV: ${APP_ENV:-staging}
      APP_KEY: ${APP_KEY}                       # generate once, keep stable
      APP_DEBUG: ${APP_DEBUG:-false}
      APP_URL: ${APP_URL}                       # https://cms.example.org
      APP_URL_FRONTEND: ${APP_URL_FRONTEND}     # https://www.example.org
      AUTO_MIGRATE: ${AUTO_MIGRATE:-true}
      LOG_CHANNEL: ${LOG_CHANNEL:-stderr}       # stderr -> readable in Portainer
      # Behind Traefik the calling IP is a docker network address that changes
      # on every redeploy, so trust the caller and honour X-Forwarded-Proto.
      TRUSTED_PROXIES: ${TRUSTED_PROXIES:-*}
      SESSION_SECURE_COOKIE: ${SESSION_SECURE_COOKIE:-true}
      # --- database (the MySQL service below) ---
      DB_HOST: ${DB_HOST:-db}
      DB_PORT: ${DB_PORT:-3306}
      DB_DATABASE: ${DB_DATABASE}
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      # --- mail (Mailjet SMTP) ---
      MAIL_DRIVER: ${MAIL_DRIVER:-smtp}
      MAIL_HOST: ${MAIL_HOST}
      MAIL_PORT: ${MAIL_PORT}
      MAIL_USERNAME: ${MAIL_USERNAME}
      MAIL_PASSWORD: ${MAIL_PASSWORD}
      MAIL_ENCRYPTION: ${MAIL_ENCRYPTION}
      MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
      MAIL_FROM_NAME: ${MAIL_FROM_NAME}
      MAIL_CONTACT_ADDRESS: ${MAIL_CONTACT_ADDRESS}
      MAIL_CONTACT_NAME: ${MAIL_CONTACT_NAME}
      MAIL_CONTACT_BLOCK_FORM_ADDRESS: ${MAIL_CONTACT_BLOCK_FORM_ADDRESS}
      # --- Mailjet API (newsletter subscribe) ---
      MJ_APIKEY_PUBLIC: ${MJ_APIKEY_PUBLIC}
      MJ_APIKEY_PRIVATE: ${MJ_APIKEY_PRIVATE}
      MJ_CONTACT_LIST: ${MJ_CONTACT_LIST}
      # --- third-party keys ---
      EAE_ADMIN_DEFAULT_PASSWORD: ${EAE_ADMIN_DEFAULT_PASSWORD}
      EAE_RECAPTCHA_PUBLIC_KEY: ${EAE_RECAPTCHA_PUBLIC_KEY}
      EAE_RECAPTCHA_PRIVATE_KEY: ${EAE_RECAPTCHA_PRIVATE_KEY}
      EAE_GOOGLE_API_KEY: ${EAE_GOOGLE_API_KEY}
      HUBSPOT_API_KEY: ${HUBSPOT_API_KEY}
      # --- API rate limiting ---
      RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true}
      RATE_LIMIT_NUMBER: ${RATE_LIMIT_NUMBER:-60}
      # --- shared secret header; must match the frontend stack's build ---
      RATE_LIMIT_FRONTEND_HEADER_NAME: ${RATE_LIMIT_FRONTEND_HEADER_NAME}
      RATE_LIMIT_FRONTEND_HEADER_VALUE: ${RATE_LIMIT_FRONTEND_HEADER_VALUE}
      # --- single site language (EN or DE) ---
      SITE_LANGUAGE: ${SITE_LANGUAGE:-en}
      SITE_LANGUAGE_NAME: ${SITE_LANGUAGE_NAME:-English}
      SITE_LANGUAGE_NATIVE: ${SITE_LANGUAGE_NATIVE:-English}
      SITE_LANGUAGE_LOCALE: ${SITE_LANGUAGE_LOCALE:-en_GB}
    volumes:
      # Uploaded media (storage/app/public/uploads), served via public/storage.
      - backend_media:/var/www/html/storage/app/public
      # EIC form documents (company / VAT files) — private, never web-served.
      - backend_private:/var/www/html/storage/app/private
    networks:
      - internal
      - proxy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1/"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 5
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=${TRAEFIK_NETWORK:-web}"
      - "traefik.http.routers.efet-backend.rule=Host(`${BACKEND_HOST}`)"
      - "traefik.http.routers.efet-backend.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
      - "traefik.http.routers.efet-backend.tls=true"
      - "traefik.http.routers.efet-backend.tls.certresolver=${TRAEFIK_CERTRESOLVER:-letsencrypt}"
      - "traefik.http.services.efet-backend.loadbalancer.server.port=80"

  db:
    image: mysql:8.0
    restart: unless-stopped
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - internal
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${DB_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 15

  # --- optional: prune superseded images after every deploy -------------------
  # Each new IMAGE_TAG leaves the previous image behind (~1.2 GB), so this runs
  # once per stack update and keeps only the IMAGE_KEEP most recent builds.
  # Counting builds rather than days is deliberate: several deploys in one day
  # must not pile up.
  #
  # It only looks at images named IMAGE_NAME, so the frontend stack and the
  # sibling EFET stacks keep theirs. Docker also refuses to remove an image a
  # container is using, so what is running is never at risk.
  #
  # It needs the docker socket, which is root-equivalent on the host. If you
  # would rather prune by hand (Portainer > Images > Unused > Remove), delete
  # this whole service — nothing else depends on it.
  imagecleanup:
    image: docker:27-cli
    restart: "no"
    depends_on:
      - backend
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - internal          # only talks to the socket; keeps compose from adding a network
    environment:
      IMAGE: ${IMAGE_NAME:-efet-public-backend}
      KEEP: ${IMAGE_KEEP:-3}
    command:
      - sh
      - -c
      - |
        # Give the new container time to start, so the image it uses counts as
        # in-use and is never a candidate.
        sleep 45
        docker images "$$IMAGE" --format '{{.CreatedAt}}|{{.Repository}}:{{.Tag}}' \
          | sort -r | tail -n +$$((KEEP + 1)) | cut -d'|' -f2 \
          | while read -r img; do
              if docker rmi "$$img" >/dev/null 2>&1; then
                echo "pruned  $$img"
              else
                echo "in use, kept  $$img"
              fi
            done
        echo "cleanup done, keeping the $$KEEP most recent $$IMAGE builds"

networks:
  # Traefik's network, created outside this stack.
  proxy:
    external: true
    name: ${TRAEFIK_NETWORK:-web}
  internal:

volumes:
  backend_media:
  backend_private:
  db_data:
