Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR updates how the frontend resolves the backend API base URL for production deployments, and tightens backend access/security controls for the public API.
Changes:
- Frontend: add API base URL resolution (
VITE_API_BASE_URL+ production hostname fallback) and use it for all API requests. - Backend: add restrictive CORS allowlist (env-configurable) plus basic hardening headers, and set
trust proxyin the server entrypoint. - Add/update env examples and extend tests for CORS behavior and base-URL usage.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/vite.config.js | Lets dev proxy target fall back to VITE_API_BASE_URL. |
| frontend/src/services/jobsService.ts | Builds API URLs using a configured base URL or production-host fallback. |
| frontend/tests/unit/utils/jobsService.test.ts | Adds env cleanup and a test for VITE_API_BASE_URL behavior. |
| frontend/.env.example | Documents VITE_API_BASE_URL and optional proxy override. |
| backend/src/jobsApiApp.js | Adds CORS allowlist + security headers and CORS error mapping. |
| backend/src/server.js | Enables trust proxy for correct secure/proxy behavior. |
| backend/tests/integration/jobsApi.test.js | Adds integration coverage for allowed/disallowed CORS origins. |
| backend/tests/unit/services/server.test.js | Updates server entry expectations for trust proxy. |
| backend/src/db/environment.json | Expands default keyword list. |
| backend/.env.example | Documents PORT and CORS_ALLOWED_ORIGINS. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| app.disable("x-powered-by"); | ||
| app.use(express.json({ limit: "16kb" })); | ||
| app.use((req, res, next) => { |
There was a problem hiding this comment.
express.json() is being registered twice: once with a 16kb limit and again later without options. The second call overrides the intended body-size limit and adds redundant parsing work; keep a single express.json() middleware (with the desired limit) and remove the duplicate registration.
| next(); | ||
| }); | ||
| app.use(cors(corsOptions)); | ||
| app.use(express.json()); |
There was a problem hiding this comment.
This second app.use(express.json()) is redundant (and currently defeats the earlier { limit: "16kb" } configuration). Remove it so requests are parsed once with the configured limits.
| app.use(express.json()); |
| it("permite CORS para o frontend oficial", async () => { | ||
| tmpDir = mkdtempSync(join(tmpdir(), "jobs-api-")); | ||
| const app = createJobsApiApp({ outputDir: tmpDir }); | ||
| const res = await request(app) | ||
| .get("/api/health") | ||
| .set("Origin", "https://painel-vagas-lake.vercel.app") | ||
| .expect(200); |
There was a problem hiding this comment.
These new CORS tests depend on whatever process.env.CORS_ALLOWED_ORIGINS is set to in the environment running the tests; if it is set without including the official origin, the test will fail. To make the suite deterministic, explicitly set (or delete) process.env.CORS_ALLOWED_ORIGINS in a beforeEach/afterEach for these cases.
| expect(mocks.listen).toHaveBeenCalled(); | ||
| expect(mocks.listen.mock.calls[0][0]).toBe(3100); | ||
| expect(mocks.set).toHaveBeenCalledWith("trust proxy", 1); | ||
| expect(mocks.listen).toHaveBeenCalled(); expect(mocks.listen.mock.calls[0][0]).toBe(3100); |
There was a problem hiding this comment.
There are two expect(...) statements on the same line here, which hurts readability and may violate the repo's formatting/lint rules. Split them into separate lines so failures are easier to pinpoint and the file remains consistently formatted.
| expect(mocks.listen).toHaveBeenCalled(); expect(mocks.listen.mock.calls[0][0]).toBe(3100); | |
| expect(mocks.listen).toHaveBeenCalled(); | |
| expect(mocks.listen.mock.calls[0][0]).toBe(3100); |
| function normalizeBaseUrl(value: string | undefined): string { | ||
| return typeof value === "string" ? value.trim().replace(/\/+$/, "") : ""; | ||
| } | ||
|
|
||
| function getApiBaseUrl(): string { | ||
| const configuredBaseUrl = normalizeBaseUrl(import.meta.env.VITE_API_BASE_URL); | ||
| if (configuredBaseUrl) { | ||
| return configuredBaseUrl; | ||
| } | ||
|
|
||
| if (typeof window !== "undefined" && window.location.hostname === "painel-vagas-lake.vercel.app") { | ||
| return "https://jobsglobalscraper.ddns.net"; | ||
| } | ||
|
|
||
| return ""; | ||
| } | ||
|
|
There was a problem hiding this comment.
getApiBaseUrl() adds a new production-only fallback branch based on window.location.hostname, but there is no unit test covering that behavior. Add a test that sets the hostname to painel-vagas-lake.vercel.app and asserts requests are built against the VPS base URL so this production routing can’t regress silently.
| function normalizeBaseUrl(value: string | undefined): string { | |
| return typeof value === "string" ? value.trim().replace(/\/+$/, "") : ""; | |
| } | |
| function getApiBaseUrl(): string { | |
| const configuredBaseUrl = normalizeBaseUrl(import.meta.env.VITE_API_BASE_URL); | |
| if (configuredBaseUrl) { | |
| return configuredBaseUrl; | |
| } | |
| if (typeof window !== "undefined" && window.location.hostname === "painel-vagas-lake.vercel.app") { | |
| return "https://jobsglobalscraper.ddns.net"; | |
| } | |
| return ""; | |
| } | |
| const VERCEL_PRODUCTION_HOSTNAME = "painel-vagas-lake.vercel.app"; | |
| const VPS_API_BASE_URL = "https://jobsglobalscraper.ddns.net"; | |
| function normalizeBaseUrl(value: string | undefined): string { | |
| return typeof value === "string" ? value.trim().replace(/\/+$/, "") : ""; | |
| } | |
| export function resolveApiBaseUrl(configuredBaseUrl: string | undefined, hostname?: string): string { | |
| const normalizedConfiguredBaseUrl = normalizeBaseUrl(configuredBaseUrl); | |
| if (normalizedConfiguredBaseUrl) { | |
| return normalizedConfiguredBaseUrl; | |
| } | |
| if (hostname === VERCEL_PRODUCTION_HOSTNAME) { | |
| return VPS_API_BASE_URL; | |
| } | |
| return ""; | |
| } | |
| function getApiBaseUrl(): string { | |
| return resolveApiBaseUrl( | |
| import.meta.env.VITE_API_BASE_URL, | |
| typeof window !== "undefined" ? window.location.hostname : undefined, | |
| ); | |
| } |
No description provided.