openreader/src/app/api/admin/tasks/[key]/route.ts
Richard R 16cada6129 feat(tasks): introduce scheduled task engine and admin UI for background jobs
Add a general-purpose scheduled task system with a persistent registry and status tracking, supporting background maintenance jobs such as orphaned blob reaping, expired upload cleanup, job event pruning, and TTS usage retention. Implement a new `scheduled_tasks` table, task engine, and handlers for each maintenance operation. Integrate an admin UI panel for monitoring, manual runs, and configuration of tasks. Update document and user data cleanup flows to delegate shared blob and preview deletion to the scheduled reaper. Add Vercel cron integration for serverless environments.

BREAKING CHANGE: Document and user storage cleanup now relies on background scheduled tasks for shared blob and preview deletion; immediate inline deletion is no longer performed.
2026-06-07 05:29:29 -06:00

39 lines
1.3 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { requireAdminContext } from '@/lib/server/auth/admin';
import { updateTask } from '@/lib/server/tasks/engine';
import { TASK_REGISTRY } from '@/lib/server/tasks/registry';
export const dynamic = 'force-dynamic';
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ key: string }> },
): Promise<Response> {
const ctx = await requireAdminContext(req);
if (ctx instanceof Response) return ctx;
const { key } = await params;
if (!(key in TASK_REGISTRY)) {
return NextResponse.json({ error: 'Unknown task' }, { status: 404 });
}
let body: unknown;
try {
body = await req.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
}
if (!body || typeof body !== 'object') {
return NextResponse.json({ error: 'Expected JSON object' }, { status: 400 });
}
const { enabled, intervalMs } = body as { enabled?: unknown; intervalMs?: unknown };
const patch: { enabled?: boolean; intervalMs?: number } = {};
if (typeof enabled === 'boolean') patch.enabled = enabled;
if (typeof intervalMs === 'number' && Number.isFinite(intervalMs) && intervalMs > 0) {
patch.intervalMs = Math.floor(intervalMs);
}
await updateTask(key, patch);
return NextResponse.json({ ok: true });
}