upstream app {
    server app:10500;
}

# A non-upgrade request maps to an EMPTY Connection header, not "close", so the
# catch-all can use this map and still keep the upstream connection alive.
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      '';
}

map $uri $upload_disposition {
    default                                                        "attachment";
    "~*\.(jpe?g|png|gif|webp|bmp|tiff|mp4|webm|ogv|mov|m4v|mp3)$"  "inline";
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=${NGINX_CACHE_MAX_SIZE} inactive=60m use_temp_path=off;

server {
    listen 80;
    listen [::]:80;
    server_name _;

    client_max_body_size ${NGINX_MAX_BODY_SIZE};

    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy strict-origin-when-cross-origin;

    gzip on;
    gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml;
    gzip_min_length 1000;
    gzip_vary on;
    gzip_proxied any;

    location /static/uploads/ {
        alias /data/uploads/;
        add_header X-Content-Type-Options nosniff;
        add_header Content-Disposition $upload_disposition;
        add_header Cache-Control "public, max-age=604800";
        access_log off;
        log_not_found off;
    }

    location ~ ^/static/v\d+/(?<asset>.+)$ {
        alias /app/static/$asset;
        add_header X-Content-Type-Options nosniff;
        expires 1y;
        add_header Cache-Control "public, immutable, max-age=31536000";
        access_log off;
        log_not_found off;
    }

    location /static/ {
        alias /app/static/;
        expires 1h;
        add_header Cache-Control "public, max-age=3600";
        access_log off;
        log_not_found off;
    }

    location /devii/ws {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # SEO Diagnostics live progress websocket (/tools/seo/<uid>/ws).
    location ~ ^/tools/seo/[^/]+/ws$ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Database API async query progress websocket (/dbapi/query/<uid>/ws).
    location ~ ^/dbapi/query/[^/]+/ws$ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Pub/Sub bus websocket (/pubsub/ws).
    location = /pubsub/ws {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Container interactive shell websocket (/projects/<slug>/containers/instances/<uid>/exec/ws).
    location ~ ^/projects/[^/]+/containers/instances/[^/]+/exec/ws$ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Workspace editor: code-server HTTP assets AND its websocket share one prefix
    # (/projects/<slug>/containers/instances/<uid>/code/...), so this location must
    # carry both. The catch-all sets Connection "" and would break the editor.
    location ~ ^/projects/[^/]+/containers/instances/[^/]+/code(/.*)?$ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # Real-time direct messages websocket (/messages/ws).
    location = /messages/ws {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # DeepSearch live progress and grounded chat websockets (/tools/deepsearch/<uid>/ws|chat).
    location ~ ^/tools/deepsearch/[^/]+/(ws|chat)$ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    location /avatar/ {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Container ingress (/p/<slug>): proxies HTTP and WebSocket to a published
    # container service. Handles upgrade for WS and uses long timeouts.
    location /p/ {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }

    # XML-RPC bridge (/xmlrpc): forwards XML-RPC method calls to the app, which
    # reverse-proxies them to the forking XML-RPC server. Long timeouts because a
    # single multicall can fan out into many internal REST calls.
    location /xmlrpc {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_read_timeout 900s;
        proxy_send_timeout 900s;
    }

    location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        # Tunnel hosts (*.tunnel.<domain>) are dispatched by the app and carry
        # arbitrary user apps, which routinely use websockets. They arrive here,
        # so the catch-all must forward the upgrade instead of dropping it -
        # dropping it is what closes a tunnelled websocket with status 1006.
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_connect_timeout 900s;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        ${NGINX_CACHE_CONFIG}
    }
}
