From 72e55bbe09e101381b76e282608eb391619df45a Mon Sep 17 00:00:00 2001 From: Benevanio Santos Date: Tue, 7 Apr 2026 12:18:00 -0300 Subject: [PATCH] fix: add root Vercel API rewrite and robust frontend error handling --- frontend/src/services/jobsService.ts | 30 +++++++++++++++---- frontend/tests/unit/utils/jobsService.test.ts | 13 ++++++++ vercel.json | 8 +++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 vercel.json diff --git a/frontend/src/services/jobsService.ts b/frontend/src/services/jobsService.ts index 6ec28a5a..1aa91f6a 100644 --- a/frontend/src/services/jobsService.ts +++ b/frontend/src/services/jobsService.ts @@ -19,6 +19,26 @@ function buildApiUrl(path: string): string { return baseUrl ? `${baseUrl}${normalizedPath}` : normalizedPath; } +async function readPayload(response: Response): Promise> { + const contentType = response.headers?.get?.("content-type") ?? ""; + + if (!contentType || contentType.includes("application/json")) { + try { + const payload = await response.json(); + return payload && typeof payload === "object" ? (payload as Record) : {}; + } catch { + // Fallback below for non-JSON bodies returned by proxies/platforms. + } + } + + if (typeof response.text === "function") { + const text = await response.text(); + return text ? { message: text } : {}; + } + + return {}; +} + function buildError(message: unknown, fallback: string): Error { return new Error(typeof message === "string" && message ? message : fallback); } @@ -35,7 +55,7 @@ function readMessage(payload: unknown): string | undefined { export async function fetchJobFiles(): Promise { const response = await fetch(buildApiUrl("/api/jobs/files")); - const payload = (await response.json()) as { files?: unknown } & Record; + const payload = (await readPayload(response)) as { files?: unknown } & Record; if (!response.ok) { throw buildError(readMessage(payload), "Falha ao listar arquivos de vagas."); @@ -54,7 +74,7 @@ export async function fetchJobFiles(): Promise { export async function fetchJobsByFile(fileName: string): Promise { const suffix = fileName ? `?file=${encodeURIComponent(fileName)}` : ""; const response = await fetch(buildApiUrl(`/api/jobs${suffix}`)); - const payload = (await response.json()) as Record; + const payload = (await readPayload(response)) as Record; if (!response.ok) { throw buildError(readMessage(payload), "Falha ao carregar vagas."); @@ -70,7 +90,7 @@ export async function fetchJobsByFile(fileName: string): Promise { export async function fetchKeywords(): Promise { const response = await fetch(buildApiUrl("/api/keywords")); - const payload = (await response.json()) as { keywords?: unknown } & Record; + const payload = (await readPayload(response)) as { keywords?: unknown } & Record; if (!response.ok) { throw buildError(readMessage(payload), "Falha ao carregar keywords."); @@ -87,7 +107,7 @@ export async function saveKeywords(keywords: string[]): Promise { }, body: JSON.stringify({ keywords }), }); - const payload = (await response.json()) as Record; + const payload = (await readPayload(response)) as Record; if (!response.ok) { throw buildError(readMessage(payload), "Falha ao salvar keywords."); @@ -98,7 +118,7 @@ export async function runScraperRequest(): Promise { const response = await fetch(buildApiUrl("/api/scraper/run"), { method: "POST", }); - const payload = (await response.json()) as Record; + const payload = (await readPayload(response)) as Record; if (!response.ok) { throw buildError(readMessage(payload), "Falha ao executar o scraper."); diff --git a/frontend/tests/unit/utils/jobsService.test.ts b/frontend/tests/unit/utils/jobsService.test.ts index bc0fc320..820a3b26 100644 --- a/frontend/tests/unit/utils/jobsService.test.ts +++ b/frontend/tests/unit/utils/jobsService.test.ts @@ -148,6 +148,19 @@ describe("jobsService", () => { await expect(fetchKeywords()).rejects.toThrow("erro-api"); }); + it("lanca erro legivel quando a resposta nao e JSON", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => ({ + ok: false, + headers: { get: () => "text/plain; charset=utf-8" }, + text: async () => "Vercel rewrite nao encontrado.", + })), + ); + + await expect(fetchKeywords()).rejects.toThrow("Vercel rewrite nao encontrado."); + }); + it("salva keywords com sucesso", async () => { const fetchMock = vi.fn(async () => ({ ok: true, diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..12f85118 --- /dev/null +++ b/vercel.json @@ -0,0 +1,8 @@ +{ + "rewrites": [ + { + "source": "/api/(.*)", + "destination": "http://jobsglobalscraper.ddns.net/api/$1" + } + ] +}