Este documento descreve as principais funcionalidades, rotas, módulos e variáveis de ambiente do backend do projeto.
Localização do código: backend
- API REST em Express (TypeScript).
- Scraper externo em Go integrado via HTTP (
GO_SCRAPER_URL). - Banco de dados gerenciado com Drizzle (Postgres).
- Autenticação via OAuth (Google/GitHub/LinkedIn) e credenciais (email/senha) com
iron-session. - Cache/índices em memória (Redis) e integração com sistema Valkey para pesquisa rápida.
- Rotas administrativas para usuários, permissões, scrapers, auditoria e observabilidade.
- Métricas Prometheus em
/metrics. - Documentação OpenAPI/Swagger disponível em
/docs(quando habilitado).
Requisitos: Node.js >= 22, PostgreSQL, Redis (opcional para cache), Go scraper (opcional)
Instalar dependências e rodar API:
cd backend
npm install
npm run dev # inicia em modo de desenvolvimento
# ou
npm start # inicia a APITestes:
npm test
npm run test:watchScripts relevantes em backend/package.json:
start,dev,api— iniciar servidortest,test:coverage,test:watch,validate— testes com Vitest (validaterodanpm test)db:generate,db:migrate,db:push— comandos Drizzledb:seed— popula o banco local com usuários e dados de teste (src/scripts/seed.ts, idempotente; ver LOCAL_DEVELOPMENT.md)security:backfill-user-pii— backfill de campos de PII criptografadosclear-cache— limpa cache/índices no Valkey (src/cache/clearCache.ts)
src/app.ts— monta a aplicação Express, middlewares e rotas.src/server.ts— inicia o servidor e registra Swagger (/docs).src/config.ts— leitura e validação das variáveis de ambiente.src/swagger.ts— gera especificação OpenAPI viaswagger-jsdoc.
Módulos principais:
src/modules/auth— OAuth providers,AuthController,AuthService,credentials(registro/login/logout).src/modules/users— perfis e preferências do usuário (UsersController,UsersService).src/modules/savedJobs— CRUD de vagas salvas (SavedJobsController,SavedJobsService).src/modules/notifications— notificações do usuário autenticado.src/modules/jobs— busca, parsing de filtros, fallback pós-filtro e regras de matching/score de vagas.src/modules/admin— usuários admin, permissões, scrapers, auditoria, dashboard e observabilidade.src/modules/email— envio de e-mails transacionais assíncronos (ver seção Módulo de E-mail).
Adaptadores externos:
src/adapters/goScraper.ts— envia requisições para o serviço Go que faz o scraping (/scrape).src/adapters/goKeywords.ts— carrega e envia keywords do/para o serviço Go.
Database / Schemas (Drizzle):
src/db/schema/users.ts— tabelausers. Camporole(enumuser_role:user<support<admin<super_admin, hierarquia crescente) eisBlocked. Vários campos de PII (email,firstName,lastName,displayName,phone,cpf,technologies,level) existem em par: uma coluna em texto plano (legado/transição) e uma coluna*Encryptedcom o valor cifrado (AES-256-GCM,src/lib/security/encryption.ts);emailHash/cpfHashguardam hash HMAC pesquisável (src/lib/security/searchableHash.ts) para permitir busca sem descriptografar. O fluxo de criação/atualização sempre grava a versão criptografada e zera a coluna plana.src/db/schema/credentials.ts— credenciais de login local (email,emailHash,passwordHash), 1:1 comusersviauserId.src/db/schema/accounts.ts— vínculos OAuth (provider,providerAccountId, tokens) associados a umusers.id.src/db/schema/keywords.ts— palavras-chave (fonteuser|scraper).src/db/schema/savedJobs.ts— vagas salvas (saved_jobs); campostatusaceitasaved,applied,interviewing,rejected,accepted; camponotesguarda anotação privada do usuário sobre a vaga.src/db/schema/applicationEvents.ts—application_events: histórico de mudança de status de uma vaga salva (fromStatus/toStatus), exposto emGET /saved-jobs/:id/events.src/db/schema/applicationNotes.ts—application_notes: múltiplas notas privadas por vaga salva (content, timestamps), uma tabela separada do campo legadosaved_jobs.notes— CRUD completo em/saved-jobs/:id/notes.src/db/schema/userPreferences.ts—user_preferences: preferências de busca e checklist de carreira do usuário, criada automaticamente no registro.src/db/schema/userNotifications.ts—user_notifications: notificações in-app do usuário.src/db/schema/auditLogs.ts—audit_logs: trilha de ações administrativas (ator, ação, alvo, metadata).src/db/schema/permissionRules.ts—permission_rules: matriz de permissões (recurso/ação/role mínima) persistida no banco, além da matriz em código (src/modules/admin/permissions/permissionMatrix.ts).- Migrações e snapshots em
drizzle/.
Cache & Indexes:
src/lib/cache.ts— helpers para Redis/Valkey; usado pelo módulo de jobs para obter ids e buscar vagas em memória.- Busca por palavras-chave usa índices invertidos e interseção para eficiência.
- Filtros estruturados podem usar índices por família (
family), tecnologia (technology), senioridade (seniority), localização, modelo e contrato.
Módulo centralizado em src/modules/email para envio de e-mails transacionais de forma assíncrona e resiliente. Qualquer módulo do backend consome a mesma API interna (emailService), sem conhecer o provedor.
Fluxo: emailService.send() valida e enfileira um job na fila BullMQ email (sobre Valkey, via ioredis) → um worker in-process (startEmailWorker, iniciado no boot do server.ts) renderiza o template react-email e despacha pelo MailProvider configurado. Falha do provedor aciona retry com backoff exponencial; no fracasso final apenas loga. Falha ao enfileirar (ex.: Valkey indisponível) é logada e não propaga para o fluxo de negócio chamador.
Arquivos:
email.service.ts— API interna (emailService.send/sendWelcome).email.queue.ts— fila BullMQ + conexãoioredisdedicada (getEmailQueue,enqueueEmail,closeEmailQueue).email.worker.ts— worker in-process (startEmailWorker,stopEmailWorker).providers/mail-provider.ts— interfaceMailProvider+getMailProvider()(selecionaResendProviderseEMAIL_API_KEYpresente, senãoNoopProvider).providers/resend.provider.ts,providers/noop.provider.ts— provedores concretos.templates/registry.ts+templates/*.tsx— templates react-email e lookup por nome.
Uso (API interna):
import { emailService } from "./modules/email/email.service";
// Envio genérico: valida `to` (formato) e `template` (existe no registry).
await emailService.send({
template: "welcome",
to: "usuario@exemplo.com",
data: { name: "Ana", appUrl: "https://painelvagas.com" },
});
// Açúcar para boas-vindas: injeta `appUrl` a partir de FRONTEND_URL.
await emailService.sendWelcome({ email: "usuario@exemplo.com", name: "Ana" });send resolve sem aguardar a entrega. to inválido ou template desconhecido lançam AppError.validation (antes de enfileirar).
Variáveis de ambiente:
EMAIL_API_KEY— chave da Resend. Vazia ⇒NoopProvider(apenas loga; não envia, não quebra o boot).EMAIL_FROM_ADDRESS— endereço remetente (ex.:no-reply@painelvagas.com).EMAIL_FROM_NAME— nome exibido do remetente (ex.:Painel Vagas).EMAIL_QUEUE_ATTEMPTS— nº de tentativas do job (padrão3).FRONTEND_URL— reusada para o botão "Acessar plataforma" do template de boas-vindas (nenhuma env de URL nova é criada).
Adicionar um novo template:
- Crie o componente react-email em
src/modules/email/templates/<nome>.tsx(props tipadas), reusandoBaseLayout. - Registre-o no mapa
templatesderegistry.ts(subject + component) e adicione as props emTemplateDataMap. OTemplateNameeisTemplatepassam a reconhecê-lo automaticamente. - Consuma via
emailService.send({ template: "<nome>", to, data }).
Adicionar um novo provider:
- Implemente a interface
MailProvider(send({ to, subject, html, replyTo? })) emsrc/modules/email/providers/<nome>.provider.ts. Em falha, lance (para o BullMQ re-tentar). - Ajuste
getMailProvider()emmail-provider.tspara selecioná-lo pela configuração. Nenhum caller precisa mudar (contrato via interface).
withSession— integrairon-session(sessões + cookievagas_session).requireAuth— valida autenticação nas rotas que exigem usuário.securityHeaders— cabeçalhos de segurança (ver seção Segurança e criptografia).cors— configuração de CORS (opções emsrc/middleware/cors.ts).rateLimit— limitadores de tentativas em endpoints de autenticação (src/middleware/rateLimit.ts).validate— validação/normalização debody/query/paramsvia schemas Zod.requestId— correlação de requisições.metrics— coleta de métricas Prometheus.rateLimit(authIpRateLimiter,authAccountRateLimiter) — limita tentativas de login por IP e por conta (AUTH_RATE_LIMIT_*).errorHandler— tratamento centralizado de erros.
Base: /api/v1 (prefixo oficial, backend/src/app.ts). As mesmas rotas seguem respondendo sem prefixo (ex.: /auth/login além de /api/v1/auth/login) como compatibilidade temporária para clientes ainda não migrados; GET /health responde nos dois formatos. Veja também a seção "Versionamento da API" do README.md.
-
Sistema
GET /health— verifica disponibilidade (retorna{ ok: true }).GET /metrics— métricas Prometheus.GET /docs— UI do Swagger (quando habilitado).
-
Auth / OAuth
GET /auth/:provider/url— retorna URL de autenticação (ex:google,github,linkedin).GET /auth/:provider/callback— callback OAuth — processa código/state e cria sessão.
-
Credenciais (email/senha)
POST /auth/register— registra usuário (criausers,credentials,userPreferences) e inicia sessão.POST /auth/login— autentica e inicia sessão. Sujeito a rate limit por IP e por conta (AUTH_RATE_LIMIT_IP_MAX,AUTH_RATE_LIMIT_ACCOUNT_MAX,AUTH_RATE_LIMIT_WINDOW_SECONDS).POST /auth/logout— destroi sessão.GET /auth/me— retorna{ user }com o registro completo do usuário autenticado (401 se a sessão for inválida/expirada).GET /auth/connections— lista provedores OAuth conectados ao usuário autenticado.DELETE /auth/connections/:provider— desconecta um provedor OAuth do usuário autenticado.
-
Usuários
GET /users/profile— retorna perfil do usuário autenticado.PATCH /users/profile— atualiza campos do perfil.GET /users/preferences— obtém preferências do usuário.POST /users/preferences— cria preferências (caso não existam).PATCH /users/preferences— atualiza preferências.
-
Jobs
GET /jobs/search?keywords=...— busca vagas utilizando índices/Valkey/Redis. Retorna paginação e fonte (source).- Filtros aceitos incluem
keywords,family,technology,seniority,level,location,country,state,city,type/model,contract/contractType/jobTypesematchSort.
-
Keywords
GET /keywords— lista keywords do usuário autenticado.POST /keywords— seKWSYNC_ENABLED=false(padrão), retorna403({ ok:false, message: "Submissão de keywords por usuário está desabilitada." }). Se habilitado, insere a keyword na tabelakeywords(dedupe poruserId+keyword) e publica na fila Valkeyscraper:keywords:pendingpara o serviço Go processar (retorna 202).
-
Notificações
GET /notifications— lista notificações do usuário autenticado.PATCH /notifications/:id/read— marca uma notificação como lida.PATCH /notifications/read-all— marca todas como lidas.DELETE /notifications— limpa notificações conforme filtros aceitos.
-
Vagas salvas (Saved Jobs)
GET /saved-jobs— lista vagas salvas do usuário.GET /saved-jobs/:id— obtém vaga salva por id.GET /saved-jobs/:id/events— histórico de mudanças de status da vaga salva (tabelaapplication_events).GET /saved-jobs/:id/notes— lista as notas privadas da vaga salva (tabelaapplication_notes, mais de uma por vaga).POST /saved-jobs/:id/notes— cria uma nota ({ content }, 1–5000 caracteres).PATCH /saved-jobs/:id/notes/:noteId— atualiza o conteúdo de uma nota.DELETE /saved-jobs/:id/notes/:noteId— remove uma nota.POST /saved-jobs— cria nova vaga salva.PATCH /saved-jobs/:id— atualiza vaga salva (incluistatuse o campo legado de nota únicanotes, distinto das notas em/saved-jobs/:id/notes).DELETE /saved-jobs/:id— remove vaga salva.
-
Admin
As rotas
/admin/*são montadas por três routers distintos, cada um com uma role mínima diferente (src/modules/admin/permissions/roles.ts, hierarquiauser < support < admin < super_admin). A matriz completa de recurso/ação/role fica emsrc/modules/admin/permissions/permissionMatrix.ts(também espelhada na tabelapermission_rules).Role mínima
support(src/routes/support.routes.ts):GET /admin/dashboard— métricas gerais (usuários, vagas coletadas, status do scraper).GET /admin/scrapers,GET /admin/scrapers/status,GET /admin/scrapers/jobs,GET /admin/scrapers/jobs/count— leitura de estado/dados do scraper.GET /admin/observability/health— healthcheck agregado dos serviços.
Role mínima
admin(src/routes/admin.routes.ts):GET /admin/users— lista usuários.GET /admin/users/:id— obtém usuário por id.PATCH /admin/users/:id/block— bloqueia usuário.PATCH /admin/users/:id/unblock— desbloqueia usuário.POST /admin/users/:id/reset— reseta credenciais/senha conforme regra do serviço.POST /admin/scrapers/run— dispara execução dos scrapers.- Sucesso:
202com{ ok: true, message }. - Execução concorrente:
409com{ ok: false, code: "SCRAPER_ALREADY_RUNNING", message }. - Lock/Valkey indisponível:
503com{ ok: false, code: "SCRAPER_RUN_LOCK_UNAVAILABLE", message }.
- Sucesso:
POST /admin/scrapers/:id/run— aplica o mesmo contrato ao scraper nomeado (go-scraper).GET /admin/observability/metrics— visão de métricas administrativas.GET /admin/observability/dashboards— lista dashboards de observabilidade.GET /admin/audit— consulta logs de auditoria.GET /admin/permissions/rules— lista regras de permissão.
Role mínima
super_admin(src/routes/superAdmin.routes.ts):PATCH /admin/users/:id/role— altera a role de um usuário.DELETE /admin/users/:id— remove um usuário definitivamente.PATCH /admin/permissions/rules— atualiza a matriz de permissões.DELETE /admin/jobs/cache— limpa o cache/índice de vagas no Valkey.
Observações de segurança nas rotas:
- Rotas sob
/users,/jobs,/keywords,/notifications,/saved-jobse/adminusamwithSession+requireAuth(quando aplicável);/admin/*adicionalmente exigerequireRole/requirePermissionconforme a tabela acima. authusawithSessionpara armazenar OAuth state e criar sessão;POST /auth/loginpassa também porauthIpRateLimiter/authAccountRateLimiter.
Definidas/consumidas em src/config.ts e outros módulos:
HEADLESS— modo headless do scraper (bool).WAIT_BETWEEN_SEARCHES_MS— intervalo entre buscas (ms).PAGE_TIMEOUT_MS— timeout de página (ms).MAX_PAGES_PER_KEYWORD— limite de páginas por keyword.VIEWPORT_WIDTH,VIEWPORT_HEIGHT— dimensões do browser.SEARCH_LOCATION,SEARCH_GEO_ID,SEARCH_LANGUAGE— parâmetros de busca.REMOTE_ONLY— filtrar vagas remotas.JOB_TYPES— filtros de tipo de vaga.TIME_FILTER— filtro temporal (ex:r604800).DATABASE_URL— conexão com Postgres.VALKEY_URL— endpoint do Valkey (cache, fila de e-mail via BullMQ e fila de keywords do kwsync).CACHE_TTL_MS— TTL padrão (ms) do cache de vagas no Valkey.KWSYNC_ENABLED— padrãofalse. Liga/desliga tantoPOST /keywordsno backend quanto o consumidor da filascraper:keywords:pendingno scraper-go (ver SCRAPER.md).APP_URL— URL base pública da aplicação (uso informativo/documental).FRONTEND_URL— URL do frontend; reusada no CTA do e-mail de boas-vindas e no redirect pós-OAuth.EMAIL_API_KEY— chave da Resend (vazio ⇒ envio no-op logado).EMAIL_FROM_ADDRESS— endereço remetente dos e-mails.EMAIL_FROM_NAME— nome exibido do remetente.EMAIL_QUEUE_ATTEMPTS— tentativas por job de e-mail (padrão 3).GO_SCRAPER_URL— URL usada pelos adaptersgoScraper.ts/goKeywords.ts(fluxo de scraping/keywords direto).SCRAPER_URL— URL usada peloscraperClientnos endpoints administrativos/admin/scrapers/*(config.scraperUrl). É uma variável distinta deGO_SCRAPER_URL, apontando ao mesmo serviço Go por um caminho de integração diferente.SESSION_SECRET— senha parairon-session(obrigatória em produção).ENCRYPTION_MASTER_KEY,ENCRYPTION_KEY_ID,SEARCH_KEY— criptografia e campos pesquisáveis de PII.AUTH_RATE_LIMIT_IP_MAX,AUTH_RATE_LIMIT_ACCOUNT_MAX,AUTH_RATE_LIMIT_WINDOW_SECONDS— limites de tentativas de login por IP/conta e janela (segundos) do rate limiter dePOST /auth/login.CORS_ALLOWED_ORIGINS— origens permitidas, incluindohttp://localhost:5173ehttp://localhost:5174em desenvolvimento local com admin.PROMETHEUS_URL— integração com Prometheus para rotas de observabilidade.PORT— porta do servidor (padrão 3001).
- Senhas com Argon2id (
argon2), parâmetrosmemoryCost: 65536,timeCost: 3,parallelism: 4(src/modules/auth/credentials.service.ts). - Cookies de sessão (
vagas_session, viairon-session)httpOnlysempre;secureesameSite: "none"quandoNODE_ENV=production,sameSite: "lax"em desenvolvimento (src/lib/session.ts). - Campos sensíveis de perfil (
email, nome, telefone, CPF, tecnologias) são criptografados com AES-256-GCM (ENCRYPTION_MASTER_KEY) e indexados para busca via hash HMAC (SEARCH_KEY) — versrc/lib/security/encryption.tsesrc/lib/security/searchableHash.ts. toPublicUser(src/modules/users/users.mapper.ts) remove os campos internos*Encrypted/*Hashantes de qualquer resposta JSON conter umuser— apenas os campos decifrados (email,firstName, etc.) e os demais campos não sensíveis (id,username,role,isBlocked, timestamps) são expostos.
Limitadores por janela deslizante, com contador no Valkey quando VALKEY_URL está definido e fallback em memória caso contrário. Respostas incluem RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset; ao estourar, 429 com Retry-After. Falha do backend de contagem responde 503 (fail-closed).
| Rota | Limitadores | Chave |
|---|---|---|
POST /auth/login |
authIpRateLimiter, authAccountRateLimiter |
IP (hash) / e-mail (hash) |
POST /auth/register |
authIpRateLimiter, authRegisterRateLimiter |
IP (hash) / e-mail (hash), bucket próprio |
Configuração (variável ausente usa o default; valor ≤ 0 ou não numérico também cai no default):
AUTH_RATE_LIMIT_IP_MAX— máximo de tentativas por IP na janela. Default20.AUTH_RATE_LIMIT_ACCOUNT_MAX— máximo por e-mail na janela (login e cadastro têm buckets separados). Default5.AUTH_RATE_LIMIT_WINDOW_SECONDS— tamanho da janela em segundos. Default900(15 min).
Pendente: aplicar rate limit ao endpoint de exportação de dados (LGPD) quando a PAV-41 for mergeada.
CORS_ALLOWED_ORIGINS(lista separada por vírgula) é a fonte da verdade das origens permitidas.- Sem a env: em
productioncai apenas nas origens de produção (*.candidate.app.br) e loga um aviso —localhostnunca entra no allowlist de produção por fallback. Fora de produção, o fallback incluihttp://localhost:5173e:5174. credentials: true; métodosGET, POST, PATCH, DELETE, OPTIONS; headersContent-Type, Authorization, X-Requested-With; preflight cacheado por 24 h. Requisições sem headerOrigin(server-to-server, mesma origem) são permitidas.- Origem fora do allowlist retorna
403com{ code: "FORBIDDEN", message: "Origem não permitida." }.
Aplicados a todas as respostas:
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Strict-Transport-Security: max-age=31536000; includeSubDomains— só sobre HTTPS (req.secure, resolvido viatrust proxy) ouNODE_ENV=production.preloadfica de fora de propósito (opt-in do time).x-powered-bydesabilitado.- CSP da API e dos frontends (Nginx/Vercel) é tratada em SECURITY.md (PAV-132).
- Corpo de requisição limitado a
16kb(express.json({ limit: "16kb" })). - Validação/normalização de entrada via schemas Zod (
middleware/validate) nas rotas de auth, users, keywords e saved-jobs. - Acesso ao banco via Drizzle (queries parametrizadas — sem concatenação de SQL).
- Índices únicos e constraints no DB (ex: email/username/keyword uniques) definidos nas tabelas Drizzle.
goScraper.tsfaz POST em${GO_SCRAPER_URL}/scrapecomScrapeParamse validaScrapeResponse.goKeywords.tsconsulta e publica keywords via endpoints do serviço Go (/api/keywords).- O backend lê os índices criados pelo scraper no Valkey, incluindo
scraper:jobs:keyword:*,scraper:jobs:family:*,scraper:jobs:technology:*escraper:jobs:seniority:*. - Disparos administrativos usam
scraperClient(viaSCRAPER_URL) e preservam os códigos operacionais do serviço Go. O códigoSCRAPER_ALREADY_RUNNINGé um conflito esperado;SCRAPER_RUN_LOCK_UNAVAILABLEindica política fail-closed e não inicia coleta. - Fila
scraper:keywords:pendingno Valkey (src/lib/kwsync.tsno backend,scraper-go/internal/kwsync) sincroniza keywords criadas pelo usuário para o scraper-go processar, controlada porKWSYNC_ENABLED.
- Uso de Drizzle ORM com tipos gerados em
src/db/schema. - Tabelas:
users,credentials,accounts,keywords,saved_jobs,application_events,application_notes,user_preferences,user_notifications,audit_logs,permission_rules(ver detalhes de cada uma em Database / Schemas). - Migrations em
drizzle/.
src/logger.tsexportalogInfo,logWarn, etc.- Erros críticos são logados; rotas tratam respostas e retornam mensagens amigáveis.
- Testes unitários e de integração com
vitestemtests/. - Cobertura configurada em
test:coverage.
backend/Dockerfileexiste para o backend.- O fluxo Docker principal usa
docker/node.Dockerfilecom targets para backend, frontend e admin. docker-compose.ymlno projeto raiz orquestrascraper-go,backend,frontendefront_admin.docker-compose.infra.ymlsobe Postgres e Valkey.docker-compose.migrate.ymlexecuta migrations e backfill antes do backend.
- Garantir
SESSION_SECRETseguro em produção. - Documentar contrato do Valkey (se for serviço externo) e endpoints do Go scraper com exemplos de payload.
- Adicionar ao Swagger (
backend/src/swagger.ts) os endpoints de notas de candidatura (/saved-jobs/:id/notes*), que ainda não estão documentados ali. - Corrigir
backend/src/swagger.ts: osecuritySchemes.cookieAuthdeclara o cookie comocandidate_session, mas o cookie de sessão real évagas_session(src/lib/session.ts).
toPublicUser(src/modules/users/users.mapper.ts) vazava os campos internos*Encrypted/*Hash(ciphertext e hashes pesquisáveis) em toda resposta que incluísse umuser—/auth/register,/auth/login,/auth/me,/users/profile,/admin/users*. A função agora remove explicitamente esses campos antes de retornar o objeto público.- O script
scraper/scraper:watchdobackend/package.jsonapontava paraindex.ts/nodemon index.ts, masbackend/index.js(o único entrypoint existente) importava arquivos.jsinexistentes e uma funçãorun()que não existe emsrc/app.ts— ou seja, o comando já estava completamente quebrado e sem nenhum consumidor no repositório (scraping real é feito pelo serviçoscraper-go). O script, o arquivoindex.jse a dependêncianodemonforam removidos.
Seguem exemplos práticos para os endpoints mais usados. Ajuste HOST para seu ambiente (ex: http://localhost:3001).
- Registrar (credentials)
Request:
POST /auth/register
{
"email": "user@example.com",
"password": "StrongP@ssw0rd",
"name": "Fulano"
}Response (201):
{
"user": {
"id": "uuid",
"email": "user@example.com",
"displayName": "Fulano",
"username": "fulano",
"emailVerified": false,
"role": "user",
"isBlocked": false
},
"session": { "userId": "uuid", "role": "user" }
}O user retornado é o registro da tabela users já sanitizado por toPublicUser (sem os campos internos *Encrypted/*Hash); o exemplo acima mostra só os campos mais relevantes.
- Login (credentials)
Request:
POST /auth/login
{
"email": "user@example.com",
"password": "StrongP@ssw0rd"
}Response (200):
{
"user": { "id": "uuid", "email": "user@example.com", "username": "fulano", "role": "user" },
"session": { "userId": "uuid", "role": "user" }
}- Buscar vagas (Jobs search)
Request:
GET /jobs/search?keywords=react,node&page=1&limit=10
Response (200):
{
"total": 123,
"page": 1,
"limit": 10,
"totalPages": 13,
"hasNext": true,
"hasPrev": false,
"jobs": [ { "id": "job-id", "title": "Frontend Developer", "company": "ACME" } ],
"source": "valkey_filtered_by_keywords:react+node"
}- Enfileirar keyword
Request:
POST /keywords
{
"keyword": "typescript"
}Response (202) — apenas com KWSYNC_ENABLED=true (padrão é false, e a rota responde 403 nesse caso):
{
"ok": true,
"message": "Keyword enfileirada para processamento."
}- Vagas salvas (Saved Jobs) — criar
Request:
POST /saved-jobs
{
"jobLink": "https://www.linkedin.com/jobs/view/123",
"jobTitle": "Backend Developer",
"company": "ACME",
"location": "São Paulo",
"source": "linkedin",
"keyword": "node"
}Response (201):
{
"id": "uuid",
"userId": "uuid",
"jobLink": "https://...",
"jobTitle": "Backend Developer",
"company": "ACME",
"location": "São Paulo",
"status": "saved",
"createdAt": "2026-05-26T..."
}- Perfil do usuário
Request:
GET /users/profile
Response (200):
{
"id": "uuid",
"displayName": "Fulano",
"username": "fulano",
"email": "user@example.com",
"avatarUrl": null
}Os exemplos acima são intencionais e servem como referência rápida para integrar o frontend ou scripts que consomem a API.