fix(ui): actionable 413/504/401 toasts + VPS nginx deploy template

Production QA reported "Submit failed 413" on /generation and plain
"Upload failed" (no detail) on resource upload. Root cause is the
outer reverse-proxy nginx on the VPS — still on default
`client_max_body_size 1m` — rejecting requests before they reach
Odoo. The inner docker-nginx and Odoo limits were already raised in
earlier commits; only the VPS proxy was left unpatched.

UI side
- Add `describeApiError(err, fallback, context)` helper in
  lib/api-client.ts. Maps common HTTP codes to human-readable,
  actionable toast descriptions (413 → "ask ops to raise nginx
  client_max_body_size", 504 → "server took too long, retry", 502
  → "Odoo is restarting", 401 → "sign in again", 4xx/5xx → server
  error body + status code).
- Use it in GenerationPage submit, ResourceManager upload,
  TeacherLibrary upload, CustomExamCreate save/publish. Replaces
  four slightly-different ad-hoc mappings with one source of truth.

Deploy side
- Add deploy/nginx-vps.conf.example: full TLS reverse-proxy template
  with the body/timeout knobs that must match Odoo's limit_request
  (128 MB) and limit_time_real (900 s).
- Add docs/DEPLOY_413_504_FIX.md: step-by-step remediation guide for
  the team lead covering the 3 layers that can block large bodies
  (outer nginx, Cloudflare, Odoo worker) and how to verify each.

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-21 09:09:23 +04:00
parent d34180e107
commit 75ee0f1fe0
7 changed files with 279 additions and 29 deletions

View File

@@ -0,0 +1,85 @@
# EnCoach — example VPS reverse-proxy config
#
# Sits in front of the docker-compose stack (frontend:80 + backend:8069).
# Copy to /etc/nginx/sites-available/encoach, edit server_name / TLS paths,
# symlink to sites-enabled, then `nginx -t && systemctl reload nginx`.
#
# The inner frontend container already raises its own nginx limits
# (see frontend/Dockerfile). If THIS outer proxy isn't raised too, the
# outer layer returns 413/504 before traffic ever reaches Odoo, and users
# see "Submit failed 413" or "Upload failed" with no server-side log entry.
# ─────────────────────────────────────────────────────────────────
# HTTP → HTTPS redirect (remove if you're not terminating TLS here)
server {
listen 80;
listen [::]:80;
server_name encoach.example.com;
return 301 https://$host$request_uri;
}
# ─────────────────────────────────────────────────────────────────
# Main TLS vhost
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name encoach.example.com;
ssl_certificate /etc/letsencrypt/live/encoach.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/encoach.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# ── CRITICAL ────────────────────────────────────────────────
# Default nginx caps request bodies at 1 MB, which triggers
# HTTP 413 on:
# * /api/resources (PDF / audio / image uploads)
# * /api/exam/generation/submit (full module payload — can
# easily be 210 MB when all tasks are filled)
# * /api/approval-requests (attachments on approval flow)
# Match this value to Odoo's `limit_request` (128 MB).
client_max_body_size 128m;
client_body_buffer_size 1m;
client_body_timeout 900s;
large_client_header_buffers 4 16k;
# ── Static / SPA served by the frontend container ───────────
location / {
proxy_pass http://127.0.0.1:8080; # docker-compose: frontend:80
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;
}
# ── Odoo API: AI calls can take minutes, needs long timeouts ─
# Must exceed Odoo's limit_time_real (900s in odoo-docker.conf).
# proxy_request_buffering off streams the body to Odoo instead of
# buffering it entirely on disk → less chance of hitting the 413
# when uploading big resources.
location /api/ {
proxy_pass http://127.0.0.1:8069/api/;
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_read_timeout 900s;
proxy_send_timeout 900s;
proxy_connect_timeout 60s;
proxy_request_buffering off;
proxy_buffering off; # stream AI responses to the client
}
# ── Odoo web client + longpolling (optional, for /web/* URLs) ─
location /longpolling/ {
proxy_pass http://127.0.0.1:8069/longpolling/;
proxy_http_version 1.1;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# Access / error logs — keep them separate so 413s are easy to spot
access_log /var/log/nginx/encoach.access.log;
error_log /var/log/nginx/encoach.error.log warn;
}