Files
novalon-website/src/app/api/admin/stats/route.ts
T
zhangxiang d5d04aa96d feat: implement frontend-backend encrypted communication via AES-256-GCM
参考 novavis-authority 的加解密方案,实现前后端通信的应用层加密:
- 重写 src/lib/crypto.ts 使用 Web Crypto API(浏览器兼容),PBKDF2+AES-256-GCM
- 新增 src/lib/crypto-server.ts 服务端加解密工具(Node.js crypto)
- 新增 src/lib/api-crypto.ts API 路由中间件 withCrypto(),自动解密请求体/加密响应体
- 更新 src/lib/admin-api.ts 自动加密所有请求/解密响应
- 所有 11 个 admin API 路由文件已应用 withCrypto 包装器
- 更新 .env 文件,添加 NEXT_PUBLIC_ENCRYPTION_SECRET 和 ENCRYPTION_SECRET
2026-08-02 09:11:36 +08:00

83 lines
2.4 KiB
TypeScript

import { NextRequest } from 'next/server';
import { prisma } from '@/lib/db';
import { authenticateRequest } from '@/lib/auth';
import { success, unauthorized, internalError } from '@/lib/api-response';
import { withCrypto } from '@/lib/api-crypto';
// GET /api/admin/stats - 获取仪表盘统计数据
export const GET = withCrypto(async (request: NextRequest) => {
const user = authenticateRequest(request);
if (!user) return unauthorized();
try {
const [modelCount, itemCount, zoneCount, userCount, pendingReviewCount, unreadCount] = await Promise.all([
prisma.contentModel.count(),
prisma.contentItem.count(),
prisma.contentZone.count(),
prisma.user.count({ where: { status: 1 } }),
prisma.contentItem.count({ where: { status: 'review' } }),
prisma.notification.count({ where: { userId: user.userId, read: false } }),
]);
// 获取每个内容模型的条目数
const modelItemCounts = await prisma.contentItem.groupBy({
by: ['modelCode'],
_count: { modelCode: true },
});
// 获取不同状态的条目数
const statusCounts = await prisma.contentItem.groupBy({
by: ['status'],
_count: { status: true },
});
// 获取最近 5 条通知
const recentNotifications = await prisma.notification.findMany({
where: { userId: user.userId },
orderBy: { createdAt: 'desc' },
take: 5,
select: {
id: true,
type: true,
title: true,
read: true,
createdAt: true,
},
});
// 获取最近 5 条更新的内容
const recentItems = await prisma.contentItem.findMany({
orderBy: { updatedAt: 'desc' },
take: 5,
select: {
id: true,
title: true,
modelCode: true,
status: true,
updatedAt: true,
},
});
return success({
models: modelCount,
items: itemCount,
zones: zoneCount,
activeUsers: userCount,
pendingReviews: pendingReviewCount,
unreadNotifications: unreadCount,
modelItemCounts: modelItemCounts.map((m) => ({
modelCode: m.modelCode,
count: m._count.modelCode,
})),
statusCounts: statusCounts.map((s) => ({
status: s.status,
count: s._count.status,
})),
recentNotifications,
recentItems,
});
} catch (error) {
console.error('Get stats error:', error);
return internalError();
}
});