Stage 10: external blocklist importer — backend + frontend

- blocklist_repo.py: CRUD for blocklist_sources table
- import_log_repo.py: add/list/get-last log entries
- blocklist_service.py: source CRUD, preview, import (download/validate/ban),
  import_all, schedule get/set/info
- blocklist_import.py: APScheduler task (hourly/daily/weekly schedule triggers)
- blocklist.py router: 9 endpoints (list/create/update/delete/preview/import/
  schedule-get+put/log)
- blocklist.py models: ScheduleFrequency (StrEnum), ScheduleConfig, ScheduleInfo,
  ImportSourceResult, ImportRunResult, PreviewResponse
- 59 new tests (18 repo + 19 service + 22 router); 374 total pass
- ruff clean, mypy clean for Stage 10 files
- types/blocklist.ts, api/blocklist.ts, hooks/useBlocklist.ts
- BlocklistsPage.tsx: source management, schedule picker, import log table
- Frontend tsc + ESLint clean
This commit is contained in:
2026-03-01 15:33:24 +01:00
parent b8f3a1c562
commit 1efa0e973b
15 changed files with 3771 additions and 53 deletions

View File

@@ -0,0 +1,97 @@
/**
* API functions for the blocklist management endpoints.
*/
import { del, get, post, put } from "./client";
import { ENDPOINTS } from "./endpoints";
import type {
BlocklistListResponse,
BlocklistSource,
BlocklistSourceCreate,
BlocklistSourceUpdate,
ImportLogListResponse,
ImportRunResult,
PreviewResponse,
ScheduleConfig,
ScheduleInfo,
} from "../types/blocklist";
// ---------------------------------------------------------------------------
// Sources
// ---------------------------------------------------------------------------
/** Fetch all configured blocklist sources. */
export async function fetchBlocklists(): Promise<BlocklistListResponse> {
return get<BlocklistListResponse>(ENDPOINTS.blocklists);
}
/** Create a new blocklist source. */
export async function createBlocklist(
payload: BlocklistSourceCreate,
): Promise<BlocklistSource> {
return post<BlocklistSource>(ENDPOINTS.blocklists, payload);
}
/** Update a blocklist source. */
export async function updateBlocklist(
id: number,
payload: BlocklistSourceUpdate,
): Promise<BlocklistSource> {
return put<BlocklistSource>(ENDPOINTS.blocklist(id), payload);
}
/** Delete a blocklist source. */
export async function deleteBlocklist(id: number): Promise<void> {
await del<undefined>(ENDPOINTS.blocklist(id));
}
// ---------------------------------------------------------------------------
// Preview
// ---------------------------------------------------------------------------
/** Preview the contents of a blocklist source URL. */
export async function previewBlocklist(id: number): Promise<PreviewResponse> {
return get<PreviewResponse>(ENDPOINTS.blocklistPreview(id));
}
// ---------------------------------------------------------------------------
// Import
// ---------------------------------------------------------------------------
/** Trigger a manual import of all enabled sources. */
export async function runImportNow(): Promise<ImportRunResult> {
return post<ImportRunResult>(ENDPOINTS.blocklistsImport, {});
}
// ---------------------------------------------------------------------------
// Schedule
// ---------------------------------------------------------------------------
/** Fetch the current schedule config and next/last run times. */
export async function fetchSchedule(): Promise<ScheduleInfo> {
return get<ScheduleInfo>(ENDPOINTS.blocklistsSchedule);
}
/** Update the import schedule. */
export async function updateSchedule(config: ScheduleConfig): Promise<ScheduleInfo> {
return put<ScheduleInfo>(ENDPOINTS.blocklistsSchedule, config);
}
// ---------------------------------------------------------------------------
// Import log
// ---------------------------------------------------------------------------
/** Fetch a paginated import log. */
export async function fetchImportLog(
page = 1,
pageSize = 50,
sourceId?: number,
): Promise<ImportLogListResponse> {
const params = new URLSearchParams();
params.set("page", String(page));
params.set("page_size", String(pageSize));
if (sourceId !== undefined) params.set("source_id", String(sourceId));
return get<ImportLogListResponse>(
`${ENDPOINTS.blocklistsLog}?${params.toString()}`,
);
}