Scanner: Stellenanzeigen aus Jobbörsen (DACH, max. 21 Tage)
Neuer Menüpunkt Scanner mit Dashboard und Suchagenten, Zeitplan und API-Schlüssel unter Einstellungen -> Scanner. - Quellen: Agentur für Arbeit (API), StepStone, karriere.at, freelancermap (HTML) sowie Adzuna und Jooble (kostenlose Schlüssel) - Quote = gefundene Schlagwörter, Titel zählt doppelt; keine KI - Abgleich mit icon_companies, bekannte Firmen oben mit Abzeichen - Zeitplan Vorgabe stündlich Mo-Fr 06-20 Uhr, einstellbar - Sperre gegen parallele Läufe wie im perinco-Scanner Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6fe5b18c11
commit
1b82f580db
+102
@@ -432,3 +432,105 @@ export interface DashboardData {
|
||||
export const dashboardApi = {
|
||||
load: () => get<DashboardData>('/dashboard'),
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------- Scanner */
|
||||
|
||||
export type ScannerSourceId = 'ba' | 'stepstone' | 'karriere' | 'adzuna' | 'jooble' | 'freelancermap';
|
||||
export type ScannerMatchStatus = 'neu' | 'gesehen' | 'in_bearbeitung' | 'verworfen';
|
||||
|
||||
export interface ScannerAgent {
|
||||
id: number;
|
||||
name: string;
|
||||
keywords: string[];
|
||||
sources: ScannerSourceId[];
|
||||
excludes: string[];
|
||||
is_active: boolean;
|
||||
matches: number;
|
||||
}
|
||||
|
||||
export interface ScannerSchedule {
|
||||
active: boolean;
|
||||
/** ISO-Wochentage, 1 = Montag */
|
||||
days: number[];
|
||||
from: string;
|
||||
to: string;
|
||||
/** Minuten */
|
||||
interval: number;
|
||||
/** höchstes Alter einer Anzeige in Tagen */
|
||||
maxAge: number;
|
||||
}
|
||||
|
||||
export interface ScannerSource {
|
||||
id: ScannerSourceId;
|
||||
label: string;
|
||||
countries: string;
|
||||
ready: boolean;
|
||||
needsKey: 'adzuna' | 'jooble' | null;
|
||||
last: { ok: boolean; count: number; errors: string[] } | null;
|
||||
}
|
||||
|
||||
export interface ScannerOverview {
|
||||
stats: { agents: number; jobs: number; matches: number; fresh: number; known: number };
|
||||
sources: ScannerSource[];
|
||||
schedule: ScannerSchedule;
|
||||
lastScanAt: string | null;
|
||||
lastRun: {
|
||||
id: number;
|
||||
trigger: 'auto' | 'manual';
|
||||
started_at: string;
|
||||
finished_at: string;
|
||||
jobs_found: number;
|
||||
matches_new: number;
|
||||
} | null;
|
||||
status: { running: boolean; since: string | null };
|
||||
}
|
||||
|
||||
export interface ScannerMatch {
|
||||
id: number;
|
||||
score: number;
|
||||
hits: string[];
|
||||
status: ScannerMatchStatus;
|
||||
created_at: string;
|
||||
agent_id: number;
|
||||
agent_name: string;
|
||||
job_id: number;
|
||||
source: ScannerSourceId;
|
||||
title: string;
|
||||
company: string | null;
|
||||
location: string | null;
|
||||
country: string | null;
|
||||
employment_type: string | null;
|
||||
url: string;
|
||||
description: string | null;
|
||||
published_at: string | null;
|
||||
company_id: number | null;
|
||||
company_name: string | null;
|
||||
is_customer: boolean | null;
|
||||
known: boolean;
|
||||
}
|
||||
|
||||
export interface ScannerSecrets {
|
||||
adzunaId: string | null;
|
||||
adzunaKey: string | null;
|
||||
joobleKey: string | null;
|
||||
}
|
||||
|
||||
export type ScannerAgentInput = Pick<ScannerAgent, 'name' | 'keywords' | 'sources' | 'excludes' | 'is_active'>;
|
||||
|
||||
const put = <T>(path: string, body: unknown) =>
|
||||
apiRequest<T>(path, { method: 'PUT', body: JSON.stringify(body) });
|
||||
|
||||
export const scannerApi = {
|
||||
overview: () => get<ScannerOverview>('/scanner/overview'),
|
||||
agents: () => get<{ agents: ScannerAgent[] }>('/scanner/agents'),
|
||||
createAgent: (agent: ScannerAgentInput) => post<{ agent: ScannerAgent }>('/scanner/agents', agent),
|
||||
updateAgent: (id: number, agent: ScannerAgentInput) => put<{ agent: ScannerAgent }>(`/scanner/agents/${id}`, agent),
|
||||
removeAgent: (id: number) => del<{ ok: true }>(`/scanner/agents/${id}`),
|
||||
matches: (params: Record<string, unknown> = {}) =>
|
||||
get<{ total: number; matches: ScannerMatch[] }>(`/scanner/matches${query(params)}`),
|
||||
setStatus: (id: number, status: ScannerMatchStatus) => patch<{ ok: true }>(`/scanner/matches/${id}`, { status }),
|
||||
scan: () => post<{ started: true }>('/scanner/scan'),
|
||||
settings: () => get<{ settings: ScannerSchedule; secrets: ScannerSecrets }>('/scanner/settings'),
|
||||
saveSettings: (changes: Partial<ScannerSchedule> & Partial<Record<keyof ScannerSecrets, string>>) =>
|
||||
patch<{ settings: ScannerSchedule; secrets: ScannerSecrets }>('/scanner/settings', changes),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user