services:
  traefik-proxy:
    image: traefik:2.11.29
    ports:
      - target: 80
        published: 8680
        protocol: tcp
        mode: host
    networks:
      - kbh-traefik-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command: --log.level=INFO
      --providers.docker.swarmMode=true
      --providers.docker=true
      --providers.docker.exposedByDefault=false
      --entryPoints.http.address=:80
      --accesslog=true
      --entryPoints.http.forwardedHeaders.trustedIPs=0.0.0.0/0
      --entryPoints.http.proxyProtocol.trustedIPs=0.0.0.0/0
      --providers.docker.network=kbh-traefik-proxy
      --serversTransport.forwardingTimeouts.dialTimeout=30s
    deploy:
      placement:
        constraints: [node.role == manager]

  cron:
    image: crazymax/swarm-cronjob:1.13.0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      LOG_LEVEL: info
      TZ: Europe/Kyiv
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - app-network

  app:
    image: ${REGISTRY}/app-nginx:${IMAGE_TAG}
    networks:
      - kbh-traefik-proxy
      - app-network
    depends_on:
      - app-node
    deploy:
      labels:
        - traefik.enable=true
        - traefik.docker.network=kbh-traefik-proxy
        - traefik.http.routers.app.rule=Host(`${SERVER_DOMAIN}`)
        - traefik.http.routers.app.entryPoints=http
        - traefik.http.routers.app.service=app
        - traefik.http.services.app.loadBalancer.server.port=80
        #- traefik.http.middlewares.basicauth.basicauth.users=${BASIC_AUTH}
        - traefik.http.middlewares.realip.headers.customrequestheaders.X-Forwarded-Proto=https
        - traefik.http.routers.app.middlewares=realip #,basicauth
        # /api
        - traefik.http.routers.app-public.rule=Host(`${SERVER_DOMAIN}`) && PathPrefix(`/api/`)
        - traefik.http.routers.app-public.entryPoints=http
        - traefik.http.routers.app-public.service=app
        - traefik.http.routers.app-public.priority=10
        - traefik.http.routers.app-public.middlewares=realip
      mode: replicated
      replicas: 1
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 5
        window: 120s

  app-node:
    image: ${REGISTRY}/app-node:${IMAGE_TAG}
    environment: &app-env
      NODE_ENV: ${NODE_ENV}
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:5432/${POSTGRES_DB}
      COOKIES_DOMAIN: ${COOKIES_DOMAIN}
      NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
      BETTER_AUTH_URL: ${BETTER_AUTH_URL}
      BETTER_AUTH_EMAIL: ${BETTER_AUTH_EMAIL}
      S3_ENDPOINT: ${AWS_S3_ENDPOINT}
      S3_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      S3_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
      S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME}
      S3_REGION: ${AWS_REGION}
      S3_PUBLIC_URL: ${AWS_S3_PUBLIC_URL}
      S3_INTERNAL_URL: ${AWS_S3_ENDPOINT}
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_SECURE: ${SMTP_SECURE}
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
      STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
      NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
      NEXT_PUBLIC_CITYCHANGE_API_URL: ${NEXT_PUBLIC_CITYCHANGE_API_URL}
      MOBILE_PAY_BASE_URL: ${MOBILE_PAY_BASE_URL}
      MOBILE_PAY_CLIENT_ID: ${MOBILE_PAY_CLIENT_ID}
      MOBILE_PAY_CLIENT_SECRET: ${MOBILE_PAY_CLIENT_SECRET}
      MOBILE_PAY_SUBSCRIPTION_KEY: ${MOBILE_PAY_SUBSCRIPTION_KEY}
      MOBILE_PAY_MERCHANT_SERIAL_NUMBER: ${MOBILE_PAY_MERCHANT_SERIAL_NUMBER}
      MOBILE_PAY_WEBHOOK_ID: ${MOBILE_PAY_WEBHOOK_ID}
      MOBILE_PAY_WEBHOOK_SECRET: ${MOBILE_PAY_WEBHOOK_SECRET}
      MAILCHIMP_API_KEY: ${MAILCHIMP_API_KEY}
      MAILCHIMP_NEWSLETTER_LIST_ID: ${MAILCHIMP_NEWSLETTER_LIST_ID}
      GOOGLE_ANALYTICS_TAG_ID: ${GOOGLE_ANALYTICS_TAG_ID}
      GOOGLE_ANALYTICS_PROPERTY_ID: ${GOOGLE_ANALYTICS_PROPERTY_ID}
      GOOGLE_APPLICATION_CREDENTIALS: ${GOOGLE_APPLICATION_CREDENTIALS}
    depends_on:
      - postgres
      - minio
    networks:
      - app-network
    healthcheck:
      test: curl -f "http://localhost:3000/api/health"
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      mode: replicated
      replicas: 1
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 5
        window: 120s

  app-node-mobilepay-job:
    image: ${REGISTRY}/app-node:${IMAGE_TAG}
    command: pnpm run mobilepay:process
    environment: *app-env
    depends_on:
      - postgres
      - minio
    networks:
      - app-network
    deploy:
      labels:
        - "swarm.cronjob.enable=true"
        - "swarm.cronjob.schedule=0 0 * * *"
      mode: replicated
      replicas: 0

  app-node-update-stats-job:
    image: ${REGISTRY}/app-node:${IMAGE_TAG}
    command: pnpm run site-config:update-stats
    environment: *app-env
    depends_on:
      - postgres
      - minio
    networks:
      - app-network
    deploy:
      labels:
        - "swarm.cronjob.enable=true"
        - "swarm.cronjob.schedule=0 0 * * *"
      mode: replicated
      replicas: 0

  app-node-update-ga-page-views-job:
    image: ${REGISTRY}/app-node:${IMAGE_TAG}
    command: pnpm run analytic:update-ga-page-views
    environment: *app-env
    depends_on:
      - postgres
      - minio
    networks:
      - app-network
    deploy:
      labels:
        - 'swarm.cronjob.enable=true'
        - 'swarm.cronjob.schedule=0 0 * * *'
      mode: replicated
      replicas: 0

  app-node-post-publish-job:
    image: ${REGISTRY}/app-node:${IMAGE_TAG}
    command: pnpm run post:publish-scheduled
    environment: *app-env
    depends_on:
      - postgres
      - minio
    networks:
      - app-network
    deploy:
      labels:
        - "swarm.cronjob.enable=true"
        - "swarm.cronjob.schedule=*/5 * * * *"
      mode: replicated
      replicas: 0

#  app-node-analytic-calc-post-stats-job:
#    image: ${REGISTRY}/app-node:${IMAGE_TAG}
#    command: pnpm run analytic:calc-post-stats
#    environment: *app-env
#    depends_on:
#      - postgres
#      - minio
#    networks:
#      - app-network
#    deploy:
#      labels:
#        - swarm.cronjob.enable=true
#        - swarm.cronjob.schedule=0 0 * * *
#      mode: replicated
#      replicas: 0

#  app-node-analytic-archive-events-job:
#    image: ${REGISTRY}/app-node:${IMAGE_TAG}
#    command: pnpm run analytic:archive-events
#    environment: *app-env
#    depends_on:
#      - postgres
#      - minio
#    networks:
#      - app-network
#    deploy:
#      labels:
#        - swarm.cronjob.enable=true
#        - swarm.cronjob.schedule=0 0 1 * *
#      mode: replicated
#      replicas: 0

  postgres:
    image: postgres:16-alpine3.20
    restart: unless-stopped
    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} -d ${POSTGRES_DB}']
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    deploy:
      placement:
        constraints: [node.labels.db == db]
      endpoint_mode: vip
    ports:
      - target: 5432
        published: 25432
        protocol: tcp
        mode: host

  minio:
    image: minio/minio:RELEASE.2025-09-07T16-13-09Z
    restart: unless-stopped
    environment:
      MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID}
      MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY}
    volumes:
      - minio_data:/data
    command: server /data --console-address ":80"
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
      interval: 30s
      timeout: 20s
      retries: 3
    networks:
      - app-network
      - kbh-traefik-proxy
    deploy:
      labels:
        - traefik.enable=true
        - traefik.docker.network=kbh-traefik-proxy

        - traefik.http.routers.minio.rule=Host(`storage-console.${SERVER_DOMAIN}`)
        - traefik.http.routers.minio.entryPoints=http
        - traefik.http.routers.minio.service=minio
        - traefik.http.services.minio.loadBalancer.server.port=80

        - traefik.http.routers.minio-public.rule=Host(`storage.${SERVER_DOMAIN}`)
        - traefik.http.routers.minio-public.entryPoints=http
        - traefik.http.routers.minio-public.service=minio-public
        - traefik.http.services.minio-public.loadBalancer.server.port=9000
      placement:
        constraints: [node.labels.storage == storage]
      endpoint_mode: dnsrr

  minio-setup:
    image: minio/mc:RELEASE.2025-09-07T16-13-09Z
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      until (mc alias set myminio http://minio:9000 ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY}  &> /dev/null)
          do echo '.....Waiting .... ' && sleep 1;
      done;
      mc mb myminio/${AWS_S3_BUCKET_NAME} --ignore-existing;
      mc anonymous set public myminio/${AWS_S3_BUCKET_NAME};
      exit 0;
      "
    networks:
      - app-network

  mailpit:
    image: axllent/mailpit:v1.27.9
    ports:
      - "8325:8025"
      - "1325:1025"
    environment:
      MP_SMTP_AUTH_ACCEPT_ANY: 1
      MP_SMTP_AUTH_ALLOW_INSECURE: 1
    networks:
      - kbh-traefik-proxy
      - app-network
    deploy:
      labels:
        - traefik.enable=true
        - traefik.docker.network=kbh-traefik-proxy
        - traefik.http.routers.mailpit.rule=Host(mailer.`${SERVER_DOMAIN}`)
        - traefik.http.routers.mailpit.service=mailpit
        - traefik.http.routers.mailpit.entryPoints=http
        - traefik.http.services.mailpit.loadBalancer.server.port=8025

volumes:
  postgres_data:
  minio_data:

networks:
  app-network:
  kbh-traefik-proxy:
    name: kbh-traefik-proxy
    external: true
