From c536def5858659d4ead89d781a98586e5a0443e4 Mon Sep 17 00:00:00 2001 From: "ahmed.mujtaba" Date: Thu, 20 Aug 2026 18:55:49 +0500 Subject: [PATCH] Update Docker and API configurations for improved job fetching and API proxying - Adjusted `VITE_API_BASE` in Dockerfile and docker-compose.yml to allow same-origin requests, enhancing compatibility with nginx proxy settings. - Increased the `top` query limit in `app.py` to 500 to accommodate frontend requirements while ensuring consistency across job fetching in `Jobs.jsx` and `Managers.jsx`. - Updated nginx configuration to properly proxy API requests, preventing incorrect responses for job-related endpoints. These changes streamline the interaction between the frontend and backend, ensuring a smoother user experience when fetching job data. --- backend/job/app.py | 4 +++- docker-compose.yml | 9 +++++++-- frontend/Dockerfile | 12 +++++++----- frontend/nginx.conf | 14 ++++++++++++++ frontend/src/screens/Jobs.jsx | 2 +- frontend/src/screens/Managers.jsx | 4 +++- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/backend/job/app.py b/backend/job/app.py index 8502194..a16758b 100644 --- a/backend/job/app.py +++ b/backend/job/app.py @@ -447,7 +447,9 @@ async def fetch_jobs( department: str | None = Query(None), requisition_status: str | None = Query(None), employment_type: str | None = Query(None), - top: int | None = Query(10, ge=1, le=100), + # le=500 (not 100): the Jobs board loads a full client-side page for facets; + # a 200 ceiling used to 422 the SPA and render an empty requisition list. + top: int | None = Query(10, ge=1, le=500), skip: int = Query(0, ge=0), # Defaults False, unlike /job/fetch: a requisition list must show CLOSED # requisitions, and those carry is_active = false. Soft-deleted rows are still diff --git a/docker-compose.yml b/docker-compose.yml index 745e7f2..3d33432 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -151,10 +151,15 @@ services: build: context: ./frontend args: - # Baked into the bundle at build time — change it and rebuild, not restart. - VITE_API_BASE: ${VITE_API_BASE:-http://localhost:8000} + # Empty = same-origin; nginx proxies API paths to backend-api (see nginx.conf). + # A baked http://localhost:8000 / LAN IP sends the browser to a *different* + # listener than the SPA (Cursor steals 127.0.0.1:8000) and empties /jobs. + VITE_API_BASE: ${VITE_API_BASE:-} image: hrms-frontend:local container_name: hrms-frontend + depends_on: + backend-api: + condition: service_healthy ports: - "${FRONTEND_PORT:-5173}:80" healthcheck: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f2a2e3a..2be9bd2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -14,11 +14,13 @@ RUN npm ci COPY . . -# Vite inlines VITE_* at BUILD time, so the API origin is fixed when the image is -# built, not when the container starts — rebuild the image to point it elsewhere. -# `.env.production.local` outranks every other env file, so this wins over the empty -# VITE_API_BASE in .env.production (which means "same origin, behind a proxy"). -ARG VITE_API_BASE=http://172.16.204.191:8000 +# Empty VITE_API_BASE = same-origin requests. nginx.conf proxies API paths to +# backend-api:8000, so a LAN IP baked into the bundle can no longer send the +# browser to a different listener than the one serving the SPA (the localhost +# vs 127.0.0.1 vs Docker split that emptied /jobs). +# Override with --build-arg VITE_API_BASE=https://api.example.com only when the +# API is intentionally on another origin. +ARG VITE_API_BASE= RUN printf 'VITE_API_BASE=%s\n' "$VITE_API_BASE" > .env.production.local \ && npm run build diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 147d1dd..7a9def0 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -5,6 +5,20 @@ server { root /usr/share/nginx/html; index index.html; + # Same-origin API proxy. The SPA is built with an empty VITE_API_BASE so + # fetch('/jobs/fetch') stays on this host; without this block nginx would + # serve index.html for those paths (200 HTML) and the Jobs board would + # parse an empty payload. backend-api is the compose service name. + location ~ ^/(users|roles|permissions|permission-tags|managers|inbox|email|job|jobs|candidate|notes|interview|feedback|activity|pipeline|notifications|analytics|offers|tasks|assessments|org-settings|saved-searches|search|docs|openapi\.json|redoc)(/|$) { + proxy_pass http://backend-api:8000; + proxy_http_version 1.1; + 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_set_header Authorization $http_authorization; + } + # One SPA at `/`. `vite dev` and `vite preview` serve index.html for every # unmatched path; vite.config.js notes that a static deploy needs the equivalent # rewrite rule. This is it — without it /auth/confirm-email (a router path, not a diff --git a/frontend/src/screens/Jobs.jsx b/frontend/src/screens/Jobs.jsx index a37c6da..2793868 100644 --- a/frontend/src/screens/Jobs.jsx +++ b/frontend/src/screens/Jobs.jsx @@ -28,7 +28,7 @@ import * as tasksApi from '../api/tasks' import { JOB_STATUSES } from '../api/jobs' import { empTypes, fmtShort } from '../data/seed' -// Must stay within GET /jobs/fetch `top` max (le=100); 200 returns 422 and an empty board. +// Backend allows up to 500; stay at 100 so we match Managers.jsx (shared qk.jobs.list). const JOB_LIMIT = 100 async function fetchJobs() { diff --git a/frontend/src/screens/Managers.jsx b/frontend/src/screens/Managers.jsx index c62da51..01ef2c7 100644 --- a/frontend/src/screens/Managers.jsx +++ b/frontend/src/screens/Managers.jsx @@ -19,7 +19,9 @@ async function fetchManagers() { } async function fetchJobs() { - const res = await jobsApi.list({ top: 200 }) + // Keep within GET /jobs/fetch `top` ceiling (and match Jobs.jsx) so a shared + // qk.jobs.list() cache entry is never poisoned by a 422 from top=200. + const res = await jobsApi.list({ top: 100 }) const rows = Array.isArray(res?.data) ? res.data : [] return rows.map(jobsApi.toJobView) }