Skip to content

feat: initialize Vagas Full application with Electron, React, and Nod…#20

Merged
Benevanio merged 2 commits into
developfrom
feature/electron-desktop-app
Mar 24, 2026
Merged

feat: initialize Vagas Full application with Electron, React, and Nod…#20
Benevanio merged 2 commits into
developfrom
feature/electron-desktop-app

Conversation

@Benevanio

Copy link
Copy Markdown
Collaborator

…e.js

  • Added package.json with project metadata, dependencies, and build scripts.
  • Created loading.html for a loading screen with a spinner and status message.
  • Implemented main.js to manage the Electron app lifecycle, including backend startup and health checks.
  • Added preload.js for potential IPC helpers in the future.

…e.js

- Added package.json with project metadata, dependencies, and build scripts.
- Created loading.html for a loading screen with a spinner and status message.
- Implemented main.js to manage the Electron app lifecycle, including backend startup and health checks.
- Added preload.js for potential IPC helpers in the future.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Initializes the “Vagas Full” desktop app packaging by introducing an Electron main process that boots the existing Express backend in-process and serves the built React frontend, plus adds a UI action to trigger the scraper via a new backend endpoint.

Changes:

  • Adds Electron entrypoints (main/preload) and a loading screen, plus electron-builder packaging config and scripts.
  • Extends the backend to (optionally) serve the built frontend and adds POST /api/scraper/run to trigger the scraper.
  • Updates frontend data flow/UI to trigger scraping and refresh available XLSX files.

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds Electron scripts, electron-builder config, and root deps/main entry for packaging.
frontend/src/services/jobsService.ts Adds runScraperRequest() client call for the new scraper endpoint.
frontend/src/hooks/useJobsData.ts Adds “scraping” state and triggerScraper() orchestration (run scraper → reload files/jobs).
frontend/src/App.tsx Adds “Buscar vagas” button with spinner and wires it to triggerScraper().
electron/preload.js Adds placeholder preload file (no exposed APIs yet).
electron/main.js Implements Electron boot flow: loading window → import backend → health poll → main window.
electron/loading.html Adds a minimal loading UI with strict CSP.
backend/src/server.js Uses Electron env vars for output/static dirs and serves frontend/dist when configured.
backend/src/jobsApiApp.js Adds POST /api/scraper/run with single-run concurrency guard.
.gitignore Ignores electron build output directory.
Comments suppressed due to low confidence (1)

backend/src/server.js:37

  • app.listen(PORT) sem host explícito tende a bindar em 0.0.0.0, o que expõe a API para a rede quando o app está empacotado (e.g. /api/scraper/run). Para o modo Electron, considere bindar apenas em 127.0.0.1 (ou ::1) e/ou desabilitar CORS externo para reduzir a superfície de ataque.
app.listen(PORT, () => {
  // eslint-disable-next-line no-console
  console.log(`API de vagas rodando em http://localhost:${PORT}`);
});

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json
Comment on lines +27 to 35
"dependencies": {
"axios": "^1.13.6",
"cheerio": "^1.2.0",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"pdfkit": "^0.18.0",
"xlsx": "^0.18.5"
},

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As dependências de runtime (axios/express/etc.) estão declaradas tanto no package.json raiz quanto no backend/package.json. Isso aumenta risco de divergência de versões e dificulta manutenção. Se a motivação é empacotamento com electron-builder, considere configurar o build para incluir deps do workspace backend (em vez de duplicar), ou documentar/automatizar a fonte única de verdade para essas versões.

Suggested change
"dependencies": {
"axios": "^1.13.6",
"cheerio": "^1.2.0",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"pdfkit": "^0.18.0",
"xlsx": "^0.18.5"
},
"dependencies": {},

Copilot uses AI. Check for mistakes.
Comment thread backend/src/server.js
Comment on lines +26 to +31
app.use((req, res) => {
if (req.path.startsWith("/api")) {
return res.status(404).json({ error: "Rota não encontrada." });
}
res.sendFile(path.join(staticDir, "index.html"));
});

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O fallback de SPA foi registrado com app.use(...), então ele intercepta qualquer método HTTP (POST/PUT/etc.) e pode acabar devolvendo index.html para rotas não-API. Além disso, para rotas /api/* ele retorna { error: ... }, mas o frontend lê mensagens em payload.message. Sugestão: limitar o catch-all a GET/HEAD e padronizar o campo de erro para message (mantendo o formato das demais respostas da API).

Suggested change
app.use((req, res) => {
if (req.path.startsWith("/api")) {
return res.status(404).json({ error: "Rota não encontrada." });
}
res.sendFile(path.join(staticDir, "index.html"));
});
app.get("*", (req, res) => {
if (req.path.startsWith("/api")) {
return res.status(404).json({ message: "Rota não encontrada." });
}
res.sendFile(path.join(staticDir, "index.html"));
});
app.head("*", (req, res) => {
if (req.path.startsWith("/api")) {
return res.status(404).json({ message: "Rota não encontrada." });
}
res.sendFile(path.join(staticDir, "index.html"));
});

Copilot uses AI. Check for mistakes.
Comment thread backend/src/jobsApiApp.js
const app = express();
let activeScraperRun = null;

app.use(cors());

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.use(cors()) com configuração padrão permite qualquer origem. Com a adição de POST /api/scraper/run, isso facilita que qualquer site aberto no navegador do usuário acione o scraper se o servidor estiver acessível. Sugestão: configurar CORS de forma restritiva (por exemplo, condicionar por env no modo Electron e permitir apenas a origem esperada) e/ou adicionar algum mecanismo simples de autenticação/nonce para endpoints de execução.

Copilot uses AI. Check for mistakes.
Comment thread backend/src/jobsApiApp.js
Comment on lines +104 to +121
app.post("/api/scraper/run", async (_req, res) => {
if (activeScraperRun) {
return res.status(409).json({
message: "O scraper ja esta em execucao.",
});
}

try {
activeScraperRun = runScraper();
await activeScraperRun;

const files = listXlsxFiles();
return res.json({
ok: true,
file: files[0]?.file ?? null,
modifiedAt: files[0]?.modifiedAt ?? null,
totalFiles: files.length,
});

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O novo endpoint POST /api/scraper/run não tem cobertura de testes, enquanto os demais endpoints de jobs já possuem testes de integração. Recomenda-se adicionar testes que: (1) validem o 409 quando já existe uma execução ativa e (2) validem o 200/500 sem rodar o scraper real (mockando run()), para evitar flakiness e tempo alto de execução.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
import { fetchJobFiles, fetchJobsByFile, runScraperRequest } from "@/services/jobsService";
import type { Job, JobFile, JobsMeta } from "@/types/jobs";
import { useCallback, useEffect, useState } from "react";

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Os testes existentes que mockam @/services/jobsService vão quebrar porque useJobsData agora importa runScraperRequest como named export. No Vitest, um mock que não exporta esse símbolo faz o import falhar. Atualize os mocks para também fornecer runScraperRequest (mesmo que como stub).

Copilot uses AI. Check for mistakes.
Comment thread frontend/src/App.tsx
Comment on lines +24 to +25
const { files, selectedFile, setSelectedFile, jobs, meta, loading, scraping, error, loadJobs, triggerScraper } =
useJobsData();

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Com a inclusão de scraping e triggerScraper no retorno de useJobsData, testes que mockam esse hook (ex.: App.test.tsx) precisam expor essas chaves também; caso contrário, o destructuring em App pode resultar em undefined/erros. Ajuste o mock para incluir scraping e triggerScraper.

Suggested change
const { files, selectedFile, setSelectedFile, jobs, meta, loading, scraping, error, loadJobs, triggerScraper } =
useJobsData();
const {
files,
selectedFile,
setSelectedFile,
jobs,
meta,
loading,
scraping = false,
error,
loadJobs,
triggerScraper = () => {},
} = useJobsData();

Copilot uses AI. Check for mistakes.
Comment thread electron/main.js
Comment on lines +180 to +182
dialog.showErrorBox(
'Falha ao iniciar o aplicativo',
`O servidor não pôde ser iniciado.\n\n${fatal.message}`,

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No catch, o código assume que fatal tem a propriedade .message. Se o valor lançado não for um Error (ou for null/undefined), isso pode gerar erro durante o tratamento da falha e impedir que a mensagem seja exibida corretamente. Sugestão: normalizar fatal (ex.: fatal instanceof Error ? fatal.message : String(fatal)) antes de montar o texto do diálogo.

Suggested change
dialog.showErrorBox(
'Falha ao iniciar o aplicativo',
`O servidor não pôde ser iniciado.\n\n${fatal.message}`,
const errorMessage =
fatal instanceof Error ? fatal.message : String(fatal);
dialog.showErrorBox(
'Falha ao iniciar o aplicativo',
`O servidor não pôde ser iniciado.\n\n${errorMessage}`,

Copilot uses AI. Check for mistakes.
@Benevanio
Benevanio merged commit 83577c2 into develop Mar 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants