Skip to content

Commit 8527daa

Browse files
authored
PAV-127: implementar dashboard Scraper Health e métricas de classificação (#313)
## Linear PAV-127 — [Front Admin] Implementar dashboard Scraper Health e métricas de classificação ## Objetivo Implementar no Front Admin uma visão operacional completa do Jobs Processor, consumindo os contratos de observabilidade existentes e expondo de forma clara o estado de execução, saúde dos serviços, progresso do pipeline, providers, catálogo e métricas de classificação. A entrega também consolida ajustes necessários no backend e no Jobs Processor para suportar os dados consumidos pelo painel administrativo e reorganiza os arquivos Docker Compose utilizados no ambiente local. ## Resumo das alterações A PAV-127 transforma o Front Admin em uma interface operacional para acompanhamento do Jobs Processor. A implementação adiciona e refina: - visão geral de saúde da plataforma; - estado atual do Jobs Processor; - execução atual e `run_id`; - origem e estágio da execução; - lock distribuído; - concorrência global e por provider; - progresso do pipeline; - métricas de coleta; - métricas de classificação; - métricas de persistência; - métricas de indexação; - catálogo de vagas persistidas; - resumo operacional de providers; - polling compartilhado e cancelável; - tratamento de dados parciais, indisponíveis e stale; - distinção entre valor `0` e dado indisponível; - melhorias de UX na tela de Observabilidade; - melhorias de UX na tela de Processamento de Vagas; - integração entre Front Admin, backend e Jobs Processor. ## Front Admin ### Observabilidade A tela de Observabilidade foi reorganizada para priorizar o estado operacional atual. Foram adicionadas ou refinadas as seções de: - Saúde geral; - Execução atual do Processor; - Pipeline; - Infraestrutura; - métricas operacionais; - histórico. A visão de saúde apresenta de forma compacta o estado de: - API; - PostgreSQL; - Valkey; - Jobs Processor; - taxa de erro HTTP; - latência HTTP p95. A execução atual apresenta informações como: - `run_id`; - estágio; - duração; - lock; - concorrência; - progresso da execução. O pipeline passou a representar de forma explícita: - Coleta; - Classificação; - Persistência; - Indexação. Também foi implementado tratamento visual consistente para: - dados indisponíveis; - dados parciais; - snapshots stale; - valor zero válido. ### Polling operacional Foi criada uma camada compartilhada de polling para evitar múltiplas consultas concorrentes ao mesmo recurso. O polling: - compartilha o snapshot entre consumidores; - permite cancelamento; - controla atualização periódica; - trata falhas de API; - evita duplicação de requisições entre telas. ### Processamento de Vagas A antiga visão de Scrapers foi refinada para representar melhor o domínio operacional. Na interface: - o menu passou a utilizar o nome `Processamento`; - a página utiliza o título `Processamento de Vagas`; - referências internas de rota e código continuam compatíveis com `scrapers`. A tela passou a apresentar informações operacionais dos providers de forma estruturada. A tabela da execução exibe: - provider; - tarefas; - vagas coletadas; - falhas; - tarefas ativas ou em espera; - último resultado; - detalhes expansíveis. ### Catálogo Os cards de catálogo passaram a utilizar os totais reais persistidos no PostgreSQL. O PostgreSQL é tratado como fonte de verdade do catálogo. O Valkey permanece como camada de índice/cache e não é utilizado como fonte de verdade para a quantidade total de vagas persistidas. Combinações equivalentes de fontes/providers são normalizadas de forma determinística. Exemplos equivalentes: - `Adzuna,Gupy`; - `Gupy,Adzuna`; - `Adzuna,Gupy,Adzuna`. Essas combinações passam a representar um único agrupamento lógico, com contagem agregada. ## Backend Foram realizados ajustes no contrato administrativo utilizado pelo Front Admin. As alterações incluem: - extensão dos tipos de observabilidade; - ajustes no cliente do Jobs Processor; - suporte aos dados utilizados pelo snapshot operacional; - suporte às contagens utilizadas pelo catálogo; - testes adicionais dos contratos administrativos. ## Jobs Processor O Jobs Processor foi ajustado para fornecer dados adicionais necessários ao Front Admin. Foram incluídos ou refinados: - snapshot da execução atual; - métricas associadas ao `run_id`; - estado do pipeline; - contagem de vagas por origem; - informações utilizadas pelo catálogo; - dados operacionais de providers; - testes do snapshot; - testes das contagens por origem. A implementação mantém o snapshot operacional em memória. Consequentemente, o snapshot representa a execução observada pela instância atual e pode ser reiniciado junto com o processo. As métricas operacionais representam eventos observados durante o processamento e não devem ser interpretadas automaticamente como contagem de entidades únicas. ## Docker / ambiente local A organização dos arquivos Docker Compose foi consolidada. Os arquivos específicos foram movidos para: - `docker/docker-compose.app.yml`; - `docker/docker-compose.infra.yml`; - `docker/docker-compose.migrate.yml`; - `docker/docker-compose.observability.yml`. Também foi adicionado: - `docker-full.docker-compose.yml`. O `docker-compose.yml` principal foi ajustado para acompanhar essa organização. Essa alteração reduz a dispersão dos arquivos de ambiente e deixa mais explícita a separação entre aplicação, infraestrutura, migração e observabilidade. ## Compatibilidade A implementação preserva: - rotas administrativas existentes; - nomenclaturas internas necessárias para compatibilidade; - contratos já entregues pelas PAVs anteriores; - funcionamento do Jobs Processor; - PostgreSQL como fonte de verdade do catálogo; - Valkey como índice/cache. A mudança de nomenclatura de `Scrapers` para `Processamento` é apenas de interface. Não houve renomeação estrutural de rotas ou módulos internos nesta entrega. ## Testes A suíte do Front Admin foi executada com cobertura. Resultado: - 47 arquivos de teste aprovados; - 212 testes aprovados; - cobertura global de statements: 95,72%; - cobertura global de branches: 87,81%; - cobertura global de functions: 96,29%; - cobertura global de lines: 98,29%. Entre os componentes principais: - `ObservabilityPage.tsx`: 100% de statements, functions e lines; - `OperationalSnapshotPanels.tsx`: 100% de statements, functions e lines; - `ScrapersPage.tsx`: 100% de statements, functions e lines. Também foram validados anteriormente os contratos e testes relacionados no backend e no Jobs Processor. Foi executado: - `git diff --check`. Não foram encontrados erros de whitespace no diff preparado para commit. ## Arquivos principais ### Front Admin - `front_admin/src/modules/observability/**` - `front_admin/src/modules/scrapers/**` - `front_admin/src/modules/dashboard/**` - `front_admin/src/lib/polling/**` - `front_admin/src/lib/api/**` - testes correspondentes em `front_admin/tests/**` ### Backend - `backend/src/modules/admin/observability/**` - `backend/src/modules/admin/scrapers/**` - testes administrativos relacionados ### Jobs Processor - `scraper-go/cmd/server/**` - `scraper-go/internal/catalog/**` - `scraper-go/internal/jobstore/**` - `scraper-go/internal/metrics/**` - `scraper-go/internal/pipeline/**` ### Docker - `docker-compose.yml` - `docker-full.docker-compose.yml` - `docker/docker-compose.app.yml` - `docker/docker-compose.infra.yml` - `docker/docker-compose.migrate.yml` - `docker/docker-compose.observability.yml` ## Fora do escopo Esta entrega não inclui: - renomeação estrutural de `scraper-go`; - renomeação das rotas internas `/admin/scrapers`; - persistência histórica do snapshot operacional; - transformação do snapshot em armazenamento permanente; - utilização do Valkey como fonte de verdade do catálogo. ## Riscos O snapshot operacional do Jobs Processor é mantido em memória. Em caso de reinicialização da instância, o estado da execução anteriormente observada não permanece disponível no snapshot atual. Algumas métricas representam eventos acumulados do processamento e não necessariamente entidades únicas. Essas características são refletidas na apresentação do Front Admin para evitar interpretação incorreta dos dados. ## Rollback O rollback pode ser realizado revertendo o commit desta PR. Não existem migrações destrutivas de banco de dados associadas a esta entrega. ## Checklist - [x] Dashboard operacional integrado - [x] Saúde geral dos serviços - [x] Estado atual do Jobs Processor - [x] `run_id` exibido - [x] Estágio da execução exibido - [x] Lock distribuído representado - [x] Concorrência representada - [x] Pipeline de coleta - [x] Pipeline de classificação - [x] Pipeline de persistência - [x] Pipeline de indexação - [x] Métricas de classificação - [x] Polling compartilhado - [x] Tratamento de estado stale - [x] Tratamento de dados parciais - [x] Distinção entre `0` e indisponível - [x] Tela de Processamento de Vagas refinada - [x] Providers da execução estruturados - [x] Catálogo baseado em PostgreSQL - [x] Combinações de providers normalizadas - [x] Backend administrativo atualizado - [x] Snapshot do Processor atualizado - [x] Contagens por origem implementadas - [x] Testes do Front Admin - [x] 212 testes aprovados - [x] Cobertura acima de 80% - [x] Docker Compose reorganizado - [x] `git diff --check`
2 parents 53c43c0 + b040ce6 commit 8527daa

95 files changed

Lines changed: 2960 additions & 1062 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎backend/src/modules/admin/observability/observability.types.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,27 @@ export type ObservabilityOverview = z.infer<typeof ObservabilityOverviewSchema>;
8888
// Processor-owned runtime state. Unknown fields are stripped before forwarding,
8989
// so an internal response cannot expose credentials or incidental payloads.
9090
const nullableTimestamp = z.string().datetime().nullable();
91+
const runCount = z.number().int().nonnegative();
92+
const ProviderStateSchema = z.object({
93+
id: z.enum(["linkedin", "adzuna", "themuse", "gupy", "inhire", "jooble", "greenhouse", "lever", "unknown"]),
94+
configuredConcurrency: runCount,
95+
active: runCount,
96+
waiting: runCount,
97+
tasksTotal: runCount,
98+
tasksCompleted: runCount,
99+
tasksFailed: runCount,
100+
tasksCanceled: runCount,
101+
jobsCollected: runCount,
102+
lastTaskStatus: z.enum(["success", "failed", "canceled", "timeout"]).nullable(),
103+
lastTaskFinishedAt: nullableTimestamp,
104+
lastTaskDurationSeconds: z.number().nonnegative().nullable(),
105+
lastErrorType: z.enum(["network", "timeout", "canceled", "rate_limit", "authentication", "parsing", "validation", "unknown"]).nullable(),
106+
});
91107
export const ProcessorSnapshotSchema = z.object({
92108
status: z.enum(["ok", "partial"]),
93109
timestamp: z.string().datetime(),
94110
execution: z.object({
111+
runId: z.string().min(1).max(128).nullable().optional().default(null),
95112
status: z.enum(["idle", "running", "canceling", "failed", "completed"]),
96113
source: z.enum(["manual", "cron"]).nullable(),
97114
startedAt: nullableTimestamp,
@@ -108,6 +125,11 @@ export const ProcessorSnapshotSchema = z.object({
108125
applicationVersion: z.string().max(128),
109126
taxonomyVersion: z.string().max(128),
110127
}),
128+
providers: z.array(ProviderStateSchema).max(9)
129+
.refine((providers) => new Set(providers.map((p) => p.id)).size === providers.length, "Providers duplicados")
130+
.nullable().optional().default(null),
131+
classification: z.object({ received: runCount, classified: runCount, approved: runCount, rejected: runCount, invalid: runCount }).nullable().optional().default(null),
132+
persistence: z.object({ received: runCount, inserted: runCount, updated: runCount, skipped: runCount, failed: runCount, failedBatches: runCount }).nullable().optional().default(null),
111133
lock: z.object({ held: z.boolean(), ttlSeconds: z.number().nonnegative() }),
112134
concurrency: z.object({
113135
configured: z.number().int().nonnegative(),

‎backend/src/modules/admin/scrapers/scraperClient.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { JobsCountResultSchema } from "./scrapers.types";
12
import { config } from "../../../config";
23
import type {
34
GetJobsResult,
@@ -67,8 +68,8 @@ export const scraperClient = {
6768
return request<GetJobsResult>(`/admin/jobs${suffix}`);
6869
},
6970

70-
getJobsCount(): Promise<JobsCountResult> {
71-
return request<JobsCountResult>("/admin/jobs/count");
71+
async getJobsCount(): Promise<JobsCountResult> {
72+
return JobsCountResultSchema.parse(await request<unknown>("/admin/jobs/count"));
7273
},
7374

7475
reprocessJobs(): Promise<ReprocessScrapeResult> {

‎backend/src/modules/admin/scrapers/scrapers.types.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ export const GetJobsResultSchema = z.object({
6868
export type GetJobsResult = z.infer<typeof GetJobsResultSchema>;
6969

7070
// --- JobsCountResult ---
71+
const providerId = z.enum(["linkedin", "adzuna", "themuse", "gupy", "inhire", "jooble", "greenhouse", "lever", "unknown"]);
7172
export const JobsCountResultSchema = z.object({
72-
total: z.number().int().min(0),
73+
total: z.number().int().nonnegative(),
74+
sources: z.array(z.object({ source: z.string().nullable(), provider: providerId, total: z.number().int().nonnegative() })).nullable().optional(),
75+
configuredProviders: z.array(providerId).max(9).refine((ids) => new Set(ids).size === ids.length, "Providers duplicados").nullable().optional(),
76+
}).superRefine((value, ctx) => {
77+
if (value.sources != null && value.sources.reduce((sum, source) => sum + source.total, 0) !== value.total)
78+
ctx.addIssue({ code: "custom", message: "Distribuição incompatível com total do catálogo", path: ["sources"] });
7379
});
7480
export type JobsCountResult = z.infer<typeof JobsCountResultSchema>;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe,expect,it,vi,afterEach } from "vitest";
2+
import { JobsCountResultSchema } from "../../../../src/modules/admin/scrapers/scrapers.types";
3+
import { scraperClient } from "../../../../src/modules/admin/scrapers/scraperClient";
4+
const fixture={total:205,sources:[{source:"Green House",provider:"greenhouse",total:200},{source:"LinkedIn",provider:"linkedin",total:4},{source:null,provider:"unknown",total:1}],configuredProviders:["greenhouse","themuse"]};
5+
afterEach(()=>vi.unstubAllGlobals());
6+
describe("complete catalog attribution",()=>{
7+
it("forwards global totals independently of preview and keeps configured absent provider",async()=>{
8+
vi.stubGlobal("fetch",vi.fn().mockResolvedValue({ok:true,status:200,json:async()=>({...fixture,token:"secret"})}));
9+
const result=await scraperClient.getJobsCount();expect(result).toEqual(fixture);expect(result.configuredProviders).toContain("themuse");expect(result.sources?.reduce((n,s)=>n+s.total,0)).toBe(result.total);
10+
});
11+
it.each([{total:0,sources:[],configuredProviders:["themuse"]},{total:205,sources:null,configuredProviders:["themuse"]},{total:205,sources:fixture.sources,configuredProviders:null},{total:205}])("accepts zero, partial and old contract: %j",payload=>{expect(JobsCountResultSchema.parse(payload)).toEqual(payload)});
12+
it.each([{...fixture,total:206},{...fixture,sources:[{source:"x",provider:"made-up",total:205}]},{...fixture,configuredProviders:["gupy","gupy"]},{...fixture,sources:[{source:null,provider:"unknown",total:-1}]}])("rejects incompatible counts %j",payload=>expect(JobsCountResultSchema.safeParse(payload).success).toBe(false));
13+
});

‎backend/tests/unit/modules/admin/operationalSnapshot.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,35 @@ it("honors the internal deadline signal and returns partial after abort", async
179179
timeout.mockRestore();
180180
vi.unstubAllGlobals();
181181
});
182+
183+
const classification = { received: 3, classified: 2, approved: 1, rejected: 1, invalid: 1 };
184+
const persistence = { received: 1, inserted: 1, updated: 0, skipped: 0, failed: 0, failedBatches: 0 };
185+
const provider = { id: "greenhouse", configuredConcurrency: 2, active: 0, waiting: 0, tasksTotal: 2, tasksCompleted: 1, tasksFailed: 0, tasksCanceled: 0, jobsCollected: 3, lastTaskStatus: "success", lastTaskFinishedAt: now, lastTaskDurationSeconds: 1, lastErrorType: null };
186+
describe("per-execution additive contract", () => {
187+
it("accepts the old Processor and preserves absence instead of inventing zero", () => {
188+
const result = ProcessorSnapshotSchema.parse(fixture);
189+
expect(result).toMatchObject({ providers: null, classification: null, persistence: null, execution: { runId: null } });
190+
});
191+
it.each(["running", "completed", "failed"])("forwards real totals and identity for %s", async (status) => {
192+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({ ...fixture, execution: { ...fixture.execution, status, runId: "real-lease-id", startedAt: now, finishedAt: status === "running" ? null : now }, providers: [{ ...provider, token: "private-secret", error: "private-secret" }], classification, persistence }) }));
193+
const result = await new ObservabilityService({} as any, {} as any).getOperationalSnapshot();
194+
expect(result.processor).toMatchObject({ execution: { runId: "real-lease-id", status }, classification, persistence, providers: [provider] });
195+
expect(JSON.stringify(result)).not.toContain("private-secret");
196+
});
197+
it("keeps explicit zero, known empty plan and unavailable totals distinct", () => {
198+
const result = ProcessorSnapshotSchema.parse({ ...fixture, providers: [], classification: { received: 0, classified: 0, approved: 0, rejected: 0, invalid: 0 }, persistence: null });
199+
expect(result.providers).toEqual([]);
200+
expect(result.classification?.classified).toBe(0);
201+
expect(result.persistence).toBeNull();
202+
});
203+
it.each([
204+
{ providers: [{ ...provider, id: "company-secret" }] },
205+
{ providers: Array(10).fill(provider) },
206+
{ providers: [provider, provider] },
207+
{ providers: [{ ...provider, lastErrorType: "password-secret" }] },
208+
{ classification: { ...classification, received: -1 } },
209+
{ persistence: { ...persistence, failed: 0.5 } },
210+
])("rejects unbounded or invalid new state %j", (patch) => {
211+
expect(ProcessorSnapshotSchema.safeParse({ ...fixture, ...patch }).success).toBe(false);
212+
});
213+
});

‎backend/tests/unit/modules/admin/scrapers.test.ts‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,7 @@ describe("scraperClient", () => {
119119
jobs: [],
120120
total: 0,
121121
});
122-
await expect(scraperClient.getJobsCount()).resolves.toEqual({
123-
ok: true,
124-
jobs: [],
125-
total: 0,
126-
});
122+
await expect(scraperClient.getJobsCount()).resolves.toEqual({ total: 0 });
127123
await expect(scraperClient.reprocessJobs()).resolves.toEqual({
128124
ok: true,
129125
jobs: [],

‎docker-compose.yml‎

Lines changed: 8 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,11 @@
1-
services:
2-
scraper-go:
3-
build:
4-
context: ./scraper-go
5-
dockerfile: Dockerfile
6-
container_name: vagas-scraper-go
7-
cpus: 1.5
8-
mem_limit: 2g
9-
env_file:
10-
- ./.env
11-
volumes:
12-
- ./.env:/app/.env:ro
13-
environment:
14-
- GO_SCRAPER_ADDR=:8081
15-
- VALKEY_URL=redis://valkey:6379/0
16-
- SCRAPER_MAX_CONCURRENCY=${SCRAPER_MAX_CONCURRENCY-12}
17-
- SCRAPER_PROVIDER_MAX_CONCURRENCY=${SCRAPER_PROVIDER_MAX_CONCURRENCY-2}
18-
- SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES=${SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES-}
19-
- SCRAPER_RUN_LOCK_TTL=${SCRAPER_RUN_LOCK_TTL-120s}
20-
- SCRAPER_RUN_LOCK_RENEW_INTERVAL=${SCRAPER_RUN_LOCK_RENEW_INTERVAL-30s}
21-
- SCRAPER_CLASSIFICATION_BATCH_SIZE=${SCRAPER_CLASSIFICATION_BATCH_SIZE-100}
22-
- SCRAPER_PERSIST_BATCH_SIZE=${SCRAPER_PERSIST_BATCH_SIZE-100}
23-
- SCRAPER_INDEX_BATCH_SIZE=${SCRAPER_INDEX_BATCH_SIZE-250}
24-
- SCRAPER_CATALOG_LIFETIME=${SCRAPER_CATALOG_LIFETIME-216h}
25-
- APPLICATION_VERSION=${APPLICATION_VERSION-unknown}
26-
- GOMAXPROCS=${GOMAXPROCS:-2}
27-
- GOMEMLIMIT=${GOMEMLIMIT:-1500MiB}
28-
- GUPY_ENABLED=${GUPY_ENABLED:-true}
29-
- GUPY_RAW_DISCOVERY_ENABLED=${GUPY_RAW_DISCOVERY_ENABLED:-true}
30-
- GUPY_FULL_SWEEP_ENABLED=${GUPY_FULL_SWEEP_ENABLED:-true}
31-
- GUPY_FULL_REMOTE_SWEEP_ENABLED=${GUPY_FULL_REMOTE_SWEEP_ENABLED:-true}
32-
- INHIRE_ENABLED=${INHIRE_ENABLED:-true}
33-
- INHIRE_TENANTS_FILE=/app/internal/interfaces/inhireTenants.json
34-
- INHIRE_ENRICH_DETAILS=${INHIRE_ENRICH_DETAILS:-false}
35-
- INHIRE_DETAILS_MODE=${INHIRE_DETAILS_MODE:-ambiguous}
36-
- INHIRE_DETAILS_TIMEOUT_MS=${INHIRE_DETAILS_TIMEOUT_MS:-10000}
37-
- GREENHOUSE_ENABLED=${GREENHOUSE_ENABLED:-true}
38-
- GREENHOUSE_COMPANIES_FILE=/app/internal/interfaces/greenhouseCompanies.json
39-
- LEVER_ENABLED=${LEVER_ENABLED:-false}
40-
- LEVER_COMPANIES_FILE=/app/internal/interfaces/leverCompanies.json
41-
- LEVER_INCLUDE_ALL_JOBS=${LEVER_INCLUDE_ALL_JOBS:-true}
42-
expose:
43-
- "8081"
44-
healthcheck:
45-
test: ["CMD", "wget", "--spider", "http://localhost:8081/health"]
46-
interval: 5s
47-
timeout: 3s
48-
retries: 5
49-
start_period: 5s
50-
networks:
51-
- vagas-net
52-
restart: unless-stopped
1+
name: candidate
532

54-
backend:
55-
build:
56-
context: .
57-
dockerfile: docker/node.Dockerfile
58-
target: backend
59-
container_name: vagas-backend
60-
env_file:
61-
- ./.env
62-
environment:
63-
- PORT=3001
64-
- JOB_SEARCH_CACHE_TTL_SECONDS=${JOB_SEARCH_CACHE_TTL_SECONDS-120}
65-
- PROMETHEUS_URL=http://prometheus:9090
66-
- SCRAPER_URL=http://scraper-go:8081
67-
- GO_SCRAPER_URL=http://scraper-go:8081
68-
- KEYWORDS_STORAGE_MODE=${KEYWORDS_STORAGE_MODE:-env}
69-
- CACHE_TTL_MS=${CACHE_TTL_MS:-600000}
70-
- REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX:-vagas-full}
71-
volumes:
72-
- ./backend/output:/app/output
73-
ports:
74-
- "3001:3001"
75-
depends_on:
76-
scraper-go:
77-
condition: service_healthy
78-
networks:
79-
- vagas-net
80-
restart: unless-stopped
3+
include:
4+
- path: ./docker/docker-compose.app.yml
5+
project_directory: .
816

82-
frontend:
83-
build:
84-
context: .
85-
dockerfile: docker/node.Dockerfile
86-
target: frontend-runtime
87-
args:
88-
VITE_API_BASE_URL: ${VITE_API_BASE_URL}
89-
VITE_API_URL: ${VITE_API_URL}
90-
VITE_APP_ENV: ${VITE_APP_ENV:-production}
91-
container_name: vagas-frontend
92-
ports:
93-
- "5173:80"
94-
depends_on:
95-
- backend
96-
networks:
97-
- vagas-net
98-
restart: unless-stopped
7+
- path: ./docker/docker-compose.infra.yml
8+
project_directory: .
999

100-
front_admin:
101-
build:
102-
context: .
103-
dockerfile: docker/node.Dockerfile
104-
target: front-admin-runtime
105-
args:
106-
VITE_API_BASE_URL: ${VITE_API_BASE_URL}
107-
VITE_API_URL: ${VITE_API_URL}
108-
VITE_APP_ENV: ${VITE_APP_ENV:-production}
109-
container_name: vagas-front-admin
110-
ports:
111-
- "5174:80"
112-
depends_on:
113-
- backend
114-
networks:
115-
- vagas-net
116-
restart: unless-stopped
117-
118-
networks:
119-
vagas-net:
120-
external: true
10+
- path: ./docker/docker-compose.migrate.yml
11+
project_directory: .

‎docker-full.docker-compose.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: candidate-full
2+
3+
include:
4+
- path: ./docker/docker-compose.infra.yml
5+
project_directory: .
6+
7+
- path: ./docker/docker-compose.migrate.yml
8+
project_directory: .
9+
10+
- path: ./docker/docker-compose.app.yml
11+
project_directory: .
12+
13+
- path: ./docker/docker-compose.observability.yml
14+
project_directory: .

‎docker/docker-compose.app.yml‎

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
services:
2+
scraper-go:
3+
build:
4+
context: ./scraper-go
5+
dockerfile: Dockerfile
6+
container_name: vagas-scraper-go
7+
cpus: 1.5
8+
mem_limit: 2g
9+
env_file:
10+
- ./.env
11+
volumes:
12+
- ./.env:/app/.env:ro
13+
environment:
14+
- GO_SCRAPER_ADDR=:8081
15+
- VALKEY_URL=redis://valkey:6379/0
16+
- SCRAPER_MAX_CONCURRENCY=${SCRAPER_MAX_CONCURRENCY-12}
17+
- SCRAPER_PROVIDER_MAX_CONCURRENCY=${SCRAPER_PROVIDER_MAX_CONCURRENCY-2}
18+
- SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES=${SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES-}
19+
- SCRAPER_RUN_LOCK_TTL=${SCRAPER_RUN_LOCK_TTL-120s}
20+
- SCRAPER_RUN_LOCK_RENEW_INTERVAL=${SCRAPER_RUN_LOCK_RENEW_INTERVAL-30s}
21+
- SCRAPER_CLASSIFICATION_BATCH_SIZE=${SCRAPER_CLASSIFICATION_BATCH_SIZE-100}
22+
- SCRAPER_PERSIST_BATCH_SIZE=${SCRAPER_PERSIST_BATCH_SIZE-100}
23+
- SCRAPER_INDEX_BATCH_SIZE=${SCRAPER_INDEX_BATCH_SIZE-250}
24+
- SCRAPER_CATALOG_LIFETIME=${SCRAPER_CATALOG_LIFETIME-216h}
25+
- APPLICATION_VERSION=${APPLICATION_VERSION-unknown}
26+
- GOMAXPROCS=${GOMAXPROCS:-2}
27+
- GOMEMLIMIT=${GOMEMLIMIT:-1500MiB}
28+
- GUPY_ENABLED=${GUPY_ENABLED:-true}
29+
- GUPY_RAW_DISCOVERY_ENABLED=${GUPY_RAW_DISCOVERY_ENABLED:-true}
30+
- GUPY_FULL_SWEEP_ENABLED=${GUPY_FULL_SWEEP_ENABLED:-true}
31+
- GUPY_FULL_REMOTE_SWEEP_ENABLED=${GUPY_FULL_REMOTE_SWEEP_ENABLED:-true}
32+
- INHIRE_ENABLED=${INHIRE_ENABLED:-true}
33+
- INHIRE_TENANTS_FILE=/app/internal/interfaces/inhireTenants.json
34+
- INHIRE_ENRICH_DETAILS=${INHIRE_ENRICH_DETAILS:-false}
35+
- INHIRE_DETAILS_MODE=${INHIRE_DETAILS_MODE:-ambiguous}
36+
- INHIRE_DETAILS_TIMEOUT_MS=${INHIRE_DETAILS_TIMEOUT_MS:-10000}
37+
- GREENHOUSE_ENABLED=${GREENHOUSE_ENABLED:-true}
38+
- GREENHOUSE_COMPANIES_FILE=/app/internal/interfaces/greenhouseCompanies.json
39+
- LEVER_ENABLED=${LEVER_ENABLED:-false}
40+
- LEVER_COMPANIES_FILE=/app/internal/interfaces/leverCompanies.json
41+
- LEVER_INCLUDE_ALL_JOBS=${LEVER_INCLUDE_ALL_JOBS:-true}
42+
expose:
43+
- "8081"
44+
healthcheck:
45+
test: ["CMD", "wget", "--spider", "http://localhost:8081/health"]
46+
interval: 5s
47+
timeout: 3s
48+
retries: 5
49+
start_period: 5s
50+
networks:
51+
- vagas-net
52+
restart: unless-stopped
53+
54+
backend:
55+
build:
56+
context: .
57+
dockerfile: docker/node.Dockerfile
58+
target: backend
59+
container_name: vagas-backend
60+
env_file:
61+
- ./.env
62+
environment:
63+
- PORT=3001
64+
- JOB_SEARCH_CACHE_TTL_SECONDS=${JOB_SEARCH_CACHE_TTL_SECONDS-120}
65+
- PROMETHEUS_URL=http://prometheus:9090
66+
- SCRAPER_URL=http://scraper-go:8081
67+
- GO_SCRAPER_URL=http://scraper-go:8081
68+
- KEYWORDS_STORAGE_MODE=${KEYWORDS_STORAGE_MODE:-env}
69+
- CACHE_TTL_MS=${CACHE_TTL_MS:-600000}
70+
- REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX:-vagas-full}
71+
volumes:
72+
- ./backend/output:/app/output
73+
ports:
74+
- "3001:3001"
75+
depends_on:
76+
scraper-go:
77+
condition: service_healthy
78+
networks:
79+
- vagas-net
80+
restart: unless-stopped
81+
82+
frontend:
83+
build:
84+
context: .
85+
dockerfile: docker/node.Dockerfile
86+
target: frontend-runtime
87+
args:
88+
VITE_API_BASE_URL: ${VITE_API_BASE_URL}
89+
VITE_API_URL: ${VITE_API_URL}
90+
VITE_APP_ENV: ${VITE_APP_ENV:-production}
91+
container_name: vagas-frontend
92+
ports:
93+
- "5173:80"
94+
depends_on:
95+
- backend
96+
networks:
97+
- vagas-net
98+
restart: unless-stopped
99+
100+
front_admin:
101+
build:
102+
context: .
103+
dockerfile: docker/node.Dockerfile
104+
target: front-admin-runtime
105+
args:
106+
VITE_API_BASE_URL: ${VITE_API_BASE_URL}
107+
VITE_API_URL: ${VITE_API_URL}
108+
VITE_APP_ENV: ${VITE_APP_ENV:-production}
109+
container_name: vagas-front-admin
110+
ports:
111+
- "5174:80"
112+
depends_on:
113+
- backend
114+
networks:
115+
- vagas-net
116+
restart: unless-stopped
117+
118+
networks:
119+
vagas-net:
120+
external: true

0 commit comments

Comments
 (0)