Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,6 @@ Usuários:

Jobs:

- GET /jobs
- GET /jobs/files
- GET /jobs/search

Keywords:
Expand Down
86 changes: 3 additions & 83 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,86 +1,3 @@
# services:
# scraper-go:
# build:
# context: ./scraper-go
# dockerfile: Dockerfile
# container_name: vagas-scraper-go
# env_file:
# - ./.env
# volumes:
# - ./.env:/app/.env:ro
# environment:
# - GO_SCRAPER_ADDR=:8081
# ports:
# - "8081:8081"
# healthcheck:
# test: ["CMD", "wget", "--spider", "http://localhost:8081/health"]
# interval: 5s
# timeout: 3s
# retries: 5
# start_period: 5s
# networks:
# - vagas-net
# restart: unless-stopped

# backend:
# build:
# context: .
# dockerfile: backend/Dockerfile
# container_name: vagas-backend
# env_file:
# - ./backend/.env
# environment:
# - PORT=3001
# - GO_SCRAPER_URL=http://scraper-go:8081
# - KEYWORDS_STORAGE_MODE=${KEYWORDS_STORAGE_MODE:-env}
# - CACHE_TTL_MS=${CACHE_TTL_MS:-600000}
# - REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX:-vagas-full}
# volumes:
# - ./backend/output:/app/output
# ports:
# - "3001:3001"
# depends_on:
# scraper-go:
# condition: service_healthy
# networks:
# - vagas-net
# restart: unless-stopped

# frontend:
# build:
# context: .
# dockerfile: frontend/Dockerfile
# container_name: vagas-frontend
# environment:
# - VITE_API_PROXY_TARGET=http://backend:3001
# ports:
# - "5173:80"
# depends_on:
# - backend
# networks:
# - vagas-net
# restart: unless-stopped

# networks:
# vagas-net:
# external: true


# front_admin:
# build:
# context: .
# dockerfile: front_admin/Dockerfile
# container_name: vagas-front-admin
# environment:
# - VITE_API_PROXY_TARGET=http://backend:3001
# ports:
# - "5174:80"
# depends_on:
# - backend
# networks:
# - vagas-net
# restart: unless-stopped

services:
scraper-go:
build:
Expand Down Expand Up @@ -156,6 +73,9 @@ services:
context: .
dockerfile: docker/node.Dockerfile
target: front-admin-runtime
args:
VITE_API_URL: https://api.candidate.app.br
VITE_APP_ENV: production
container_name: vagas-front-admin
env_file:
- ./front_admin/.env
Expand Down
8 changes: 7 additions & 1 deletion docker/node.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ COPY --from=frontend /app/frontend/dist /usr/share/nginx/html

FROM deps AS front-admin

ARG VITE_API_URL
ARG VITE_APP_ENV=production

ENV VITE_API_URL=$VITE_API_URL
ENV VITE_APP_ENV=$VITE_APP_ENV

COPY front_admin ./front_admin

WORKDIR /app/front_admin
Expand All @@ -50,4 +56,4 @@ RUN npm run build

FROM nginx:1.27-alpine AS front-admin-runtime

COPY --from=front-admin /app/front_admin/dist /usr/share/nginx/html
COPY --from=front-admin /app/front_admin/dist /usr/share/nginx/html
104 changes: 37 additions & 67 deletions frontend/src/domains/jobs/application/useJobsData.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import {
fetchJobFiles,
fetchJobsByFile,
fetchJobsByAPI,
runScraperRequest,
} from "@/domains/jobs/infrastructure/jobsApi";
import type { Job, JobFile, JobsMeta } from "@/domains/jobs/domain/job.types";
import type { Job, JobsResponse } from "@/domains/jobs/domain/job.types";
import { useCallback, useEffect, useState } from "react";

const EMPTY_META: JobsMeta = { file: "", modifiedAt: null, total: 0 };
export type JobsPaginationMeta = Omit<JobsResponse, "jobs">;

export function useJobsData(initialSelectedFile = "") {
const [files, setFiles] = useState<JobFile[]>([]);
const [selectedFile, setSelectedFile] = useState(initialSelectedFile);
const EMPTY_META: JobsPaginationMeta = {
total: 0,
hasNext: false,
hasPrev: false,
page: 0,
limit: 0,
totalPages: 0,
};

export function useJobsData(page: number = 1, limit: number = 5) {
const [jobs, setJobs] = useState<Job[]>([]);
const [meta, setMeta] = useState<JobsMeta>(EMPTY_META);
const [meta, setMeta] = useState<JobsPaginationMeta>(EMPTY_META);
const [loading, setLoading] = useState(false);
const [scraping, setScraping] = useState(false);
const [error, setError] = useState("");

const loadFiles = useCallback(async () => {
const foundFiles = await fetchJobFiles();
setFiles(foundFiles);
return foundFiles;
}, []);

const loadJobs = useCallback(async (fileName: string) => {
const loadJobs = useCallback(async () => {
setLoading(true);
setError("");

try {
const data = await fetchJobsByFile(fileName);
setJobs(data.jobs);
const data = await fetchJobsByAPI(page, limit);
setJobs(data.jobs ?? []);

setMeta({
file: data.file,
modifiedAt: data.modifiedAt,
total: data.total,
total: data.total ?? 0,
hasNext: data.hasNext ?? false,
hasPrev: data.hasPrev ?? false,
page: data.page ?? page,
limit: data.limit ?? limit,
totalPages: data.totalPages ?? 0,
});

if (data.file && data.file !== fileName) {
setSelectedFile(data.file);
}
} catch (err: unknown) {
setJobs([]);
setMeta(EMPTY_META);
Expand All @@ -50,57 +50,30 @@ export function useJobsData(initialSelectedFile = "") {
} finally {
setLoading(false);
}
}, []);
}, [page, limit]);

useEffect(() => {
async function initializeFiles() {
const foundFiles = await loadFiles();
if (foundFiles[0]?.file) {
setSelectedFile((current) => current || foundFiles[0].file);
}
}
let isMounted = true;

initializeFiles().catch(() => {
setError("Nao foi possivel listar arquivos .xlsx da pasta output.");
});
}, [loadFiles]);
const initialize = async () => {
if (!isMounted) return;
await loadJobs();
};

useEffect(() => {
const syncState = setTimeout(() => {
if (!selectedFile) {
setJobs([]);
setMeta(EMPTY_META);
return;
}
initialize();

loadJobs(selectedFile);
}, 0);

return () => clearTimeout(syncState);
}, [selectedFile, loadJobs]);
return () => {
isMounted = false;
};
}, [loadJobs]);

const triggerScraper = useCallback(async () => {
setScraping(true);
setError("");

try {
await runScraperRequest();
const foundFiles = await loadFiles();
const nextFile = foundFiles[0]?.file ?? "";

if (!nextFile) {
setSelectedFile("");
setJobs([]);
setMeta(EMPTY_META);
return;
}

if (nextFile !== selectedFile) {
setSelectedFile(nextFile);
return;
}

await loadJobs(nextFile);
await loadJobs();
} catch (err: unknown) {
setError(
err instanceof Error
Expand All @@ -110,12 +83,9 @@ export function useJobsData(initialSelectedFile = "") {
} finally {
setScraping(false);
}
}, [loadFiles, loadJobs, selectedFile]);
}, [loadJobs]);

return {
files,
selectedFile,
setSelectedFile,
jobs,
meta,
loading,
Expand Down
26 changes: 11 additions & 15 deletions frontend/src/domains/jobs/domain/job.types.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
export interface Job {
palavra?: string | null;
keyword?: string | null;
keywords?: string[] | null;
titulo?: string | null;
empresa?: string | null;
title?: string | null;
company?: string | null;
source?: string | null;
sources?: string[] | null;
local?: string | null;
link?: string | null;
}

export interface JobFile {
file: string;
location?: string | null;
url?: string | null;
}

export interface JobsMeta {
file: string;
modifiedAt: string | number | null;
hasNext: boolean;
hasPrev: boolean;
limit: number;
page: number;
total: number;
totalPages: number;
}

export interface JobsResponse {
export interface JobsResponse extends JobsMeta {
jobs: Job[];
file: string;
modifiedAt: string | number | null;
total: number;
}
50 changes: 13 additions & 37 deletions frontend/src/domains/jobs/infrastructure/jobsApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JobFile, JobsResponse } from "@/domains/jobs/domain/job.types";
import type { JobsResponse } from "@/domains/jobs/domain/job.types";

function normalizeBaseUrl(value: string | undefined): string {
return typeof value === "string" ? value.trim().replace(/\/+$/, "") : "";
Expand Down Expand Up @@ -57,38 +57,15 @@ function readMessage(payload: unknown): string | undefined {
return undefined;
}

export async function fetchJobFiles(): Promise<JobFile[]> {
const response = await fetch(buildApiUrl("/jobs/files"), {
credentials: "include",
});
const payload = (await readPayload(response)) as { files?: unknown } & Record<
string,
unknown
>;

if (!response.ok) {
throw buildError(
readMessage(payload),
"Falha ao listar arquivos de vagas.",
);
}

if (!Array.isArray(payload.files)) {
return [];
}
export async function fetchJobsByAPI(page?: number, limit?: number): Promise<JobsResponse> {
const queryParams = new URLSearchParams();
if (page !== undefined) queryParams.set("page", String(page));
if (limit !== undefined) queryParams.set("limit", String(limit));

return payload.files.filter(
(entry): entry is JobFile =>
!!entry &&
typeof entry === "object" &&
"file" in entry &&
typeof (entry as { file: unknown }).file === "string",
);
}
const queryString = queryParams.toString();
const urlPath = queryString ? `/jobs/search?${queryString}` : `/jobs/search`;

export async function fetchJobsByFile(fileName: string): Promise<JobsResponse> {
const suffix = fileName ? `?file=${encodeURIComponent(fileName)}` : "";
const response = await fetch(buildApiUrl(`/jobs${suffix}`), {
const response = await fetch(buildApiUrl(urlPath), {
credentials: "include",
});
const payload = (await readPayload(response)) as Record<string, unknown>;
Expand All @@ -99,13 +76,12 @@ export async function fetchJobsByFile(fileName: string): Promise<JobsResponse> {

return {
jobs: Array.isArray(payload.jobs) ? payload.jobs : [],
file: typeof payload.file === "string" ? payload.file : "",
modifiedAt:
typeof payload.modifiedAt === "string" ||
typeof payload.modifiedAt === "number"
? payload.modifiedAt
: null,
total: Number(payload.total || 0),
hasNext: Boolean(payload.hasNext),
hasPrev: Boolean(payload.hasPrev),
page: Number(payload.page || 0),
limit: Number(payload.limit || 0),
totalPages: Number(payload.totalPages || 0),
};
}

Expand Down
Loading
Loading