|
1 | | -// import cors from "cors"; |
2 | | -// import express, { NextFunction, Request, Response } from "express"; |
3 | | -// import { |
4 | | -// loadKeywords, |
5 | | -// normalizeKeywords, |
6 | | -// saveKeywords, |
7 | | -// } from "./adapters/goKeywords"; |
8 | | -// import { searchJobs, type ScrapeParams } from "./adapters/goScraper"; |
9 | | -// import { getConfig } from "./config"; |
10 | | -// import { logWarn } from "./logger"; |
11 | | -// import { authRoutes } from "./routes/auth.routes"; |
12 | | -// import { userRoutes } from "./routes/users.routes"; |
13 | | - |
14 | | -// interface JobsApiAppOptions { |
15 | | -// outputDir?: string; |
16 | | -// } |
17 | | - |
18 | | -// const DEFAULT_ALLOWED_ORIGINS = [ |
19 | | -// "https://painel-vagas-lake.vercel.app", |
20 | | -// "https://painel-vagas-m6hbzlqeh-bene-teslas-projects.vercel.app", |
21 | | -// "https://jobsglobalscraper.ddns.net", |
22 | | -// "http://jobsglobalscraper.ddns.net", |
23 | | -// "http://localhost:5173", |
24 | | -// "http://localhost:5174", |
25 | | -// ]; |
26 | | - |
27 | | -// function parseAllowedOrigins(value: string | undefined): Set<string> { |
28 | | -// const configured = String(value ?? "") |
29 | | -// .split(",") |
30 | | -// .map((o) => o.trim()) |
31 | | -// .filter(Boolean); |
32 | | -// return new Set(configured.length > 0 ? configured : DEFAULT_ALLOWED_ORIGINS); |
33 | | -// } |
34 | | - |
35 | | -// export function createJobsApiApp(_options: JobsApiAppOptions = {}) { |
36 | | -// const allowedOrigins = parseAllowedOrigins(process.env.CORS_ALLOWED_ORIGINS); |
37 | | - |
38 | | -// const corsOptions: cors.CorsOptions = { |
39 | | -// origin(origin, callback) { |
40 | | -// if (!origin || allowedOrigins.has(origin)) return callback(null, true); |
41 | | -// callback(new Error("Origin not allowed by CORS")); |
42 | | -// }, |
43 | | -// methods: ["GET", "POST", "OPTIONS"], |
44 | | -// allowedHeaders: ["Content-Type", "Authorization"], |
45 | | -// credentials: true, |
46 | | -// maxAge: 86400, |
47 | | -// }; |
48 | | - |
49 | | -// const app = express(); |
50 | | - |
51 | | -// app.disable("x-powered-by"); |
52 | | -// app.use(express.json({ limit: "16kb" })); |
53 | | -// app.use((_req: Request, res: Response, next: NextFunction) => { |
54 | | -// res.setHeader("X-Content-Type-Options", "nosniff"); |
55 | | -// res.setHeader("X-Frame-Options", "DENY"); |
56 | | -// res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); |
57 | | -// res.setHeader( |
58 | | -// "Permissions-Policy", |
59 | | -// "camera=(), microphone=(), geolocation=()", |
60 | | -// ); |
61 | | -// next(); |
62 | | -// }); |
63 | | -// app.use(cors(corsOptions)); |
64 | | - |
65 | | -// app.use("/api/auth", authRoutes); |
66 | | -// app.use("/api/users", userRoutes); |
67 | | - |
68 | | -// // ── health ──────────────────────────────────────────────────────────────── |
69 | | - |
70 | | -// /** |
71 | | -// * @swagger |
72 | | -// * /api/health: |
73 | | -// * get: |
74 | | -// * summary: Verifica se a API está online |
75 | | -// * tags: [System] |
76 | | -// * responses: |
77 | | -// * 200: |
78 | | -// * description: API funcionando |
79 | | -// */ |
80 | | -// app.get("/api/health", (_req, res) => { |
81 | | -// res.json({ ok: true }); |
82 | | -// }); |
83 | | - |
84 | | -// // ── jobs ────────────────────────────────────────────────────────────────── |
85 | | - |
86 | | -// /** |
87 | | -// * @swagger |
88 | | -// * /api/jobs/search: |
89 | | -// * get: |
90 | | -// * summary: Busca vagas (cache e dedup gerenciados pelo Go) |
91 | | -// * tags: [Jobs] |
92 | | -// * parameters: |
93 | | -// * - in: query |
94 | | -// * name: keywords |
95 | | -// * schema: |
96 | | -// * type: string |
97 | | -// * description: Palavras-chave separadas por vírgula |
98 | | -// * responses: |
99 | | -// * 200: |
100 | | -// * description: Resultado da busca com jobs, total, cachedAt, fromCache |
101 | | -// */ |
102 | | -// app.get("/api/jobs/search", async (req: Request, res: Response) => { |
103 | | -// try { |
104 | | -// const baseConfig = getConfig(); |
105 | | - |
106 | | -// const keywords = req.query.keywords |
107 | | -// ? String(req.query.keywords) |
108 | | -// .split(",") |
109 | | -// .map((k) => k.trim()) |
110 | | -// .filter(Boolean) |
111 | | -// : await loadKeywords(baseConfig.keywords); |
112 | | - |
113 | | -// const params: ScrapeParams = { |
114 | | -// keywords, |
115 | | -// location: baseConfig.searchLocation, |
116 | | -// }; |
117 | | - |
118 | | -// const result = await searchJobs(params); |
119 | | -// return res.json(result); |
120 | | -// } catch (error) { |
121 | | -// logWarn("Erro ao buscar vagas", { error: (error as Error).message }); |
122 | | -// return res.status(500).json({ |
123 | | -// message: "Erro ao buscar vagas.", |
124 | | -// error: (error as Error).message, |
125 | | -// }); |
126 | | -// } |
127 | | -// }); |
128 | | - |
129 | | -// // ── keywords ────────────────────────────────────────────────────────────── |
130 | | - |
131 | | -// /** |
132 | | -// * @swagger |
133 | | -// * /api/keywords: |
134 | | -// * get: |
135 | | -// * summary: Retorna palavras-chave configuradas |
136 | | -// * tags: [Keywords] |
137 | | -// * responses: |
138 | | -// * 200: |
139 | | -// * description: Lista de keywords |
140 | | -// */ |
141 | | -// app.get("/api/keywords", async (_req, res) => { |
142 | | -// try { |
143 | | -// const keywords = await loadKeywords(getConfig().keywords); |
144 | | -// return res.json({ ok: true, keywords }); |
145 | | -// } catch (error) { |
146 | | -// return res.status(500).json({ |
147 | | -// message: "Erro ao buscar keywords.", |
148 | | -// error: (error as Error).message, |
149 | | -// }); |
150 | | -// } |
151 | | -// }); |
152 | | - |
153 | | -// /** |
154 | | -// * @swagger |
155 | | -// * /api/keywords: |
156 | | -// * post: |
157 | | -// * summary: Atualiza palavras-chave |
158 | | -// * tags: [Keywords] |
159 | | -// * requestBody: |
160 | | -// * required: true |
161 | | -// * content: |
162 | | -// * application/json: |
163 | | -// * schema: |
164 | | -// * type: object |
165 | | -// * properties: |
166 | | -// * keywords: |
167 | | -// * type: array |
168 | | -// * items: |
169 | | -// * type: string |
170 | | -// * responses: |
171 | | -// * 200: |
172 | | -// * description: Keywords atualizadas |
173 | | -// * 400: |
174 | | -// * description: Dados inválidos |
175 | | -// */ |
176 | | -// app.post("/api/keywords", async (req: Request, res: Response) => { |
177 | | -// try { |
178 | | -// const normalized = normalizeKeywords(req.body?.keywords); |
179 | | -// if (normalized === null) { |
180 | | -// return res.status(400).json({ |
181 | | -// message: "O campo 'keywords' deve ser um array de strings.", |
182 | | -// }); |
183 | | -// } |
184 | | -// const saved = await saveKeywords(normalized); |
185 | | -// return res.json({ |
186 | | -// ok: true, |
187 | | -// message: "Keywords atualizadas com sucesso.", |
188 | | -// keywords: saved, |
189 | | -// }); |
190 | | -// } catch (error) { |
191 | | -// return res.status(500).json({ |
192 | | -// message: "Erro ao salvar keywords.", |
193 | | -// error: (error as Error).message, |
194 | | -// }); |
195 | | -// } |
196 | | -// }); |
197 | | - |
198 | | -// // ── error handler ───────────────────────────────────────────────────────── |
199 | | - |
200 | | -// app.use((error: Error, _req: Request, res: Response, _next: NextFunction) => { |
201 | | -// if (error.message === "Origin not allowed by CORS") { |
202 | | -// return res.status(403).json({ message: "Origem não permitida." }); |
203 | | -// } |
204 | | -// return res |
205 | | -// .status(500) |
206 | | -// .json({ message: "Erro interno.", error: error.message }); |
207 | | -// }); |
208 | | - |
209 | | -// return app; |
210 | | -// } |
211 | | - |
212 | 1 | import cors from "cors"; |
213 | 2 | import express from "express"; |
214 | 3 | import { corsOptions } from "./middleware/cors"; |
|
0 commit comments