Initial miaojingAI project with image resolution guard

This commit is contained in:
FengLee
2026-05-09 11:32:34 +08:00
commit d499020d4e
264 changed files with 54160 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
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();
}
}