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,285 @@
import { NextRequest, NextResponse } from 'next/server';
import type { PoolClient, QueryResult } from 'pg';
import { requireAdmin } from '@/lib/admin-auth';
import { getDbClient } from '@/storage/database/local-db';
type DbRow = Record<string, unknown>;
async function safeQuery(client: PoolClient, label: string, sql: string, params: unknown[] = []): Promise<QueryResult<DbRow>> {
try {
return await client.query(sql, params);
} catch (error) {
console.error(`[admin/dashboard] ${label} failed:`, error);
return { rows: [], rowCount: 0, command: 'SELECT', oid: 0, fields: [] };
}
}
function numberValue(value: unknown): number {
const parsed = Number(value ?? 0);
return Number.isFinite(parsed) ? parsed : 0;
}
function firstRow(result: QueryResult<DbRow>): DbRow {
return result.rows[0] || {};
}
function statusCount(rows: DbRow[], status: string): number {
const row = rows.find(item => item.status === status);
return numberValue(row?.count);
}
export async function GET(request: NextRequest) {
const authError = await requireAdmin(request);
if (authError) return authError;
const client = await getDbClient();
try {
const [
platformResult,
userResult,
workResult,
taskStatusResult,
latestTaskResult,
orderStatusResult,
orderRevenueResult,
latestOrderResult,
storageResult,
logResult,
providerResult,
recommendationResult,
userApiKeyResult,
announcementResult,
] = await Promise.all([
safeQuery(client, 'platform summary', `
SELECT
COALESCE((SELECT total_visits FROM site_stats WHERE id = 1 LIMIT 1), 0)::bigint AS total_visits,
NOW() AS database_time
`),
safeQuery(client, 'user summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE COALESCE(is_active, true) = true)::int AS active,
COUNT(*) FILTER (WHERE COALESCE(is_active, true) = false)::int AS disabled,
COUNT(*) FILTER (WHERE COALESCE(role, 'user') IN ('admin', 'enterprise_admin'))::int AS admins,
COUNT(*) FILTER (
WHERE COALESCE(role, 'user') = 'vip'
OR COALESCE(membership_tier, 'free') NOT IN ('free', '')
)::int AS members,
COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '7 days')::int AS created_7d
FROM profiles
`),
safeQuery(client, 'work summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE is_public = true)::int AS public,
COUNT(*) FILTER (WHERE is_public = false)::int AS private,
COUNT(*) FILTER (WHERE status = 'completed')::int AS completed,
COUNT(*) FILTER (WHERE status = 'failed')::int AS failed,
COUNT(*) FILTER (WHERE result_url IS NOT NULL AND result_url <> '')::int AS with_result_url,
COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '7 days')::int AS created_7d,
COUNT(*) FILTER (WHERE type = 'text2img')::int AS text2img,
COUNT(*) FILTER (WHERE type = 'img2img')::int AS img2img,
COUNT(*) FILTER (WHERE type = 'text2video')::int AS text2video,
COUNT(*) FILTER (WHERE type = 'img2video')::int AS img2video
FROM works
`),
safeQuery(client, 'task status summary', `
SELECT status, COUNT(*)::int AS count
FROM generation_jobs
GROUP BY status
`),
safeQuery(client, 'latest tasks', `
SELECT id, type, status, error, created_at, updated_at
FROM generation_jobs
ORDER BY created_at DESC
LIMIT 6
`),
safeQuery(client, 'order status summary', `
SELECT status, COUNT(*)::int AS count
FROM orders
GROUP BY status
`),
safeQuery(client, 'order revenue summary', `
SELECT
COALESCE(SUM(amount) FILTER (WHERE status = 'paid'), 0)::numeric AS paid_revenue,
COALESCE(SUM(amount) FILTER (
WHERE status = 'paid' AND COALESCE(paid_at, created_at) >= NOW() - INTERVAL '7 days'
), 0)::numeric AS paid_revenue_7d
FROM orders
`),
safeQuery(client, 'latest orders', `
SELECT id, order_no, product_name, amount, status, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 6
`),
safeQuery(client, 'storage health', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE result_url IS NOT NULL AND result_url <> '')::int AS persisted
FROM works
`),
safeQuery(client, 'log health', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE level = 'error')::int AS errors,
COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '24 hours')::int AS created_24h
FROM platform_logs
`),
safeQuery(client, 'provider summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE is_active = true)::int AS active,
COUNT(*) FILTER (WHERE is_active = false)::int AS inactive,
COUNT(*) FILTER (WHERE type = 'image')::int AS image,
COUNT(*) FILTER (WHERE type = 'video')::int AS video,
COUNT(*) FILTER (WHERE type = 'text')::int AS text,
COUNT(*) FILTER (
WHERE is_active = true
AND (COALESCE(default_api_url, '') = '' OR COALESCE(default_model, '') = '')
)::int AS incomplete
FROM api_providers
`),
safeQuery(client, 'model recommendation summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE is_active = true)::int AS active
FROM model_recommendations
`),
safeQuery(client, 'user api key summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (WHERE is_active = true)::int AS active
FROM user_api_keys
`),
safeQuery(client, 'announcement summary', `
SELECT
COUNT(*)::int AS total,
COUNT(*) FILTER (
WHERE is_active = true
AND (starts_at IS NULL OR starts_at <= NOW())
AND (expires_at IS NULL OR expires_at >= NOW())
)::int AS active,
COUNT(*) FILTER (WHERE is_active = true AND starts_at > NOW())::int AS scheduled,
COUNT(*) FILTER (WHERE expires_at < NOW())::int AS expired
FROM announcements
`),
]);
const platform = firstRow(platformResult);
const users = firstRow(userResult);
const works = firstRow(workResult);
const orderRevenue = firstRow(orderRevenueResult);
const storage = firstRow(storageResult);
const logs = firstRow(logResult);
const providers = firstRow(providerResult);
const recommendations = firstRow(recommendationResult);
const userApiKeys = firstRow(userApiKeyResult);
const announcements = firstRow(announcementResult);
const taskRows = taskStatusResult.rows;
const orderRows = orderStatusResult.rows;
const totalTasks = taskRows.reduce((sum, row) => sum + numberValue(row.count), 0);
const totalOrders = orderRows.reduce((sum, row) => sum + numberValue(row.count), 0);
const totalWorks = numberValue(works.total);
return NextResponse.json({
generatedAt: new Date().toISOString(),
platform: {
totalVisits: numberValue(platform.total_visits),
databaseTime: platform.database_time || null,
},
users: {
total: numberValue(users.total),
active: numberValue(users.active),
disabled: numberValue(users.disabled),
admins: numberValue(users.admins),
members: numberValue(users.members),
created7d: numberValue(users.created_7d),
},
works: {
total: totalWorks,
public: numberValue(works.public),
private: numberValue(works.private),
completed: numberValue(works.completed),
failed: numberValue(works.failed),
withResultUrl: numberValue(works.with_result_url),
created7d: numberValue(works.created_7d),
resultUrlCoverage: totalWorks > 0 ? numberValue(works.with_result_url) / totalWorks : 1,
byType: {
text2img: numberValue(works.text2img),
img2img: numberValue(works.img2img),
text2video: numberValue(works.text2video),
img2video: numberValue(works.img2video),
},
},
tasks: {
total: totalTasks,
queued: statusCount(taskRows, 'queued'),
running: statusCount(taskRows, 'running'),
succeeded: statusCount(taskRows, 'succeeded'),
failed: statusCount(taskRows, 'failed'),
latest: latestTaskResult.rows.map(row => ({
id: String(row.id || ''),
type: String(row.type || ''),
status: String(row.status || ''),
error: row.error ? String(row.error) : null,
createdAt: row.created_at || null,
updatedAt: row.updated_at || null,
})),
},
orders: {
total: totalOrders,
pending: statusCount(orderRows, 'pending'),
paid: statusCount(orderRows, 'paid'),
cancelled: statusCount(orderRows, 'cancelled'),
refunded: statusCount(orderRows, 'refunded'),
paidRevenue: numberValue(orderRevenue.paid_revenue),
paidRevenue7d: numberValue(orderRevenue.paid_revenue_7d),
latest: latestOrderResult.rows.map(row => ({
id: String(row.id || ''),
orderNo: String(row.order_no || ''),
productName: String(row.product_name || ''),
amount: numberValue(row.amount),
status: String(row.status || ''),
createdAt: row.created_at || null,
})),
},
providers: {
total: numberValue(providers.total),
active: numberValue(providers.active),
inactive: numberValue(providers.inactive),
image: numberValue(providers.image),
video: numberValue(providers.video),
text: numberValue(providers.text),
incomplete: numberValue(providers.incomplete),
recommendationsTotal: numberValue(recommendations.total),
recommendationsActive: numberValue(recommendations.active),
userApiKeysTotal: numberValue(userApiKeys.total),
userApiKeysActive: numberValue(userApiKeys.active),
},
announcements: {
total: numberValue(announcements.total),
active: numberValue(announcements.active),
scheduled: numberValue(announcements.scheduled),
expired: numberValue(announcements.expired),
},
system: {
apiHealth: true,
databaseHealth: true,
storageHealth: Boolean(process.env.LOCAL_STORAGE_DIR),
storageDirConfigured: Boolean(process.env.LOCAL_STORAGE_DIR),
worksPersisted: numberValue(storage.persisted),
worksTotal: numberValue(storage.total),
logsTotal: numberValue(logs.total),
logsErrors: numberValue(logs.errors),
logsCreated24h: numberValue(logs.created_24h),
},
});
} catch (error) {
console.error('[admin/dashboard] GET error:', error);
return NextResponse.json({ error: '获取仪表盘数据失败' }, { status: 500 });
} finally {
client.release();
}
}