124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAdmin } from '@/lib/admin-auth';
|
|
import { getDbClient } from '@/storage/database/local-db';
|
|
import { markStaleRunningJobs } from '@/lib/generation-job-worker';
|
|
import { ensureGenerationJobRuntimeSchema } from '@/lib/generation-job-estimates';
|
|
import { writePlatformLog } from '@/lib/platform-logs';
|
|
|
|
const STATUSES = new Set(['queued', 'running', 'succeeded', 'failed']);
|
|
const CLEANUP_STATUSES = new Set(['failed', 'succeeded']);
|
|
|
|
function intParam(value: string | null, fallback: number, min: number, max: number) {
|
|
const parsed = Number.parseInt(value || '', 10);
|
|
if (!Number.isFinite(parsed)) return fallback;
|
|
return Math.min(max, Math.max(min, parsed));
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const authError = await requireAdmin(request);
|
|
if (authError) return authError;
|
|
|
|
await markStaleRunningJobs();
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const status = searchParams.get('status') || '';
|
|
const userSearch = (searchParams.get('user') || searchParams.get('userSearch') || '').trim();
|
|
const page = intParam(searchParams.get('page'), 1, 1, 100000);
|
|
const pageSize = intParam(searchParams.get('pageSize'), 20, 1, 100);
|
|
const offset = (page - 1) * pageSize;
|
|
|
|
if (status && !STATUSES.has(status)) {
|
|
return NextResponse.json({ error: '任务状态无效' }, { status: 400 });
|
|
}
|
|
|
|
const client = await getDbClient();
|
|
try {
|
|
await ensureGenerationJobRuntimeSchema(client);
|
|
const whereClauses: string[] = [];
|
|
const params: unknown[] = [];
|
|
if (status) {
|
|
params.push(status);
|
|
whereClauses.push(`j.status = $${params.length}`);
|
|
}
|
|
if (userSearch) {
|
|
params.push(`%${userSearch.toLowerCase()}%`);
|
|
whereClauses.push(`(
|
|
j.user_id::text LIKE $${params.length}
|
|
OR LOWER(COALESCE(p.email, '')) LIKE $${params.length}
|
|
OR LOWER(COALESCE(p.nickname, '')) LIKE $${params.length}
|
|
)`);
|
|
}
|
|
const whereSql = whereClauses.length ? `WHERE ${whereClauses.join(' AND ')}` : '';
|
|
const countResult = await client.query(
|
|
`SELECT COUNT(*)::int AS total
|
|
FROM generation_jobs j
|
|
LEFT JOIN profiles p ON p.id = j.user_id
|
|
${whereSql}`,
|
|
params,
|
|
);
|
|
const rowsResult = await client.query(
|
|
`SELECT j.id, j.user_id, p.email AS user_email, p.nickname AS user_nickname,
|
|
j.type, j.status, j.error, j.created_at, j.started_at, j.finished_at, j.updated_at
|
|
FROM generation_jobs j
|
|
LEFT JOIN profiles p ON p.id = j.user_id
|
|
${whereSql}
|
|
ORDER BY j.created_at DESC
|
|
LIMIT $${params.length + 1}
|
|
OFFSET $${params.length + 2}`,
|
|
[...params, pageSize, offset],
|
|
);
|
|
|
|
const total = countResult.rows[0]?.total || 0;
|
|
return NextResponse.json({
|
|
jobs: rowsResult.rows,
|
|
total,
|
|
page,
|
|
pageSize,
|
|
totalPages: Math.max(1, Math.ceil(total / pageSize)),
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
export async function DELETE(request: NextRequest) {
|
|
const authError = await requireAdmin(request);
|
|
if (authError) return authError;
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const status = searchParams.get('status') || 'failed';
|
|
const olderThanDays = intParam(searchParams.get('olderThanDays'), 7, 0, 3650);
|
|
|
|
if (!CLEANUP_STATUSES.has(status)) {
|
|
return NextResponse.json(
|
|
{ error: '只允许清理失败或已完成任务' },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const client = await getDbClient();
|
|
try {
|
|
const result = await client.query(
|
|
`DELETE FROM generation_jobs
|
|
WHERE status = $1
|
|
AND updated_at < NOW() - ($2::int * INTERVAL '1 day')`,
|
|
[status, olderThanDays],
|
|
);
|
|
void writePlatformLog({
|
|
type: 'admin',
|
|
level: 'warning',
|
|
action: 'generation_jobs_cleanup',
|
|
message: `管理员清理了${status === 'failed' ? '失败' : '已完成'}生成任务`,
|
|
targetType: 'generation_jobs',
|
|
metadata: { status, olderThanDays, deleted: result.rowCount || 0 },
|
|
request,
|
|
});
|
|
return NextResponse.json({
|
|
success: true,
|
|
deleted: result.rowCount || 0,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|