Skip to content

Commit d555281

Browse files
committed
fix: resolve TypeScript errors and remove Vercel Cron limit
- Fix http-exception.filter.ts type errors for Vercel's TS 5.9 - Use explicit interface types instead of Express types - Remove crons config (Vercel free plan limit reached) - Reset endpoint still works via manual trigger with CRON_SECRET
1 parent b670579 commit d555281

2 files changed

Lines changed: 214 additions & 18 deletions

File tree

api/cron/reset-db.ts

Lines changed: 202 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,217 @@
11
import type { VercelRequest, VercelResponse } from '@vercel/node';
2-
import { execSync } from 'child_process';
2+
import { PrismaClient } from '@prisma/client';
3+
import * as bcrypt from 'bcryptjs';
34

4-
export default async function handler(req: VercelRequest, res: VercelResponse) {
5-
// Verify the request is from Vercel Cron
5+
const prisma = new PrismaClient();
6+
7+
// 验证 Vercel Cron 请求
8+
function isValidCronRequest(req: VercelRequest): boolean {
9+
// 方式1: 验证 Vercel Cron 的 Authorization header
610
const authHeader = req.headers.authorization;
7-
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
8-
return res.status(401).json({ error: 'Unauthorized' });
11+
const cronSecret = process.env.CRON_SECRET;
12+
13+
if (!cronSecret) {
14+
console.error('CRON_SECRET not configured');
15+
return false;
916
}
1017

11-
try {
12-
console.log('🔄 Starting database reset...');
18+
if (authHeader !== `Bearer ${cronSecret}`) {
19+
return false;
20+
}
21+
22+
// 方式2: 验证请求来源 (Vercel Cron 内部调用会带有这个 header)
23+
const userAgent = req.headers['user-agent'];
24+
if (!userAgent?.includes('vercel-cron')) {
25+
// 如果不是来自 Vercel Cron,需要额外验证
26+
const forwardedFor = req.headers['x-forwarded-for'];
27+
const vercelId = req.headers['x-vercel-id'];
28+
29+
// 确保请求来自 Vercel 内部
30+
if (!vercelId) {
31+
console.warn('Request not from Vercel internal');
32+
// 在生产环境中,可以选择拒绝非 Vercel 内部请求
33+
// return false;
34+
}
35+
}
36+
37+
return true;
38+
}
39+
40+
async function resetDatabase(): Promise<void> {
41+
// 清除所有数据(按依赖顺序)
42+
await prisma.refreshToken.deleteMany();
43+
await prisma.activityLog.deleteMany();
44+
await prisma.notification.deleteMany();
45+
await prisma.message.deleteMany();
46+
await prisma.conversationParticipant.deleteMany();
47+
await prisma.conversation.deleteMany();
48+
await prisma.eventReminder.deleteMany();
49+
await prisma.eventAttendee.deleteMany();
50+
await prisma.calendarEvent.deleteMany();
51+
await prisma.file.deleteMany();
52+
await prisma.folder.deleteMany();
53+
await prisma.documentTag.deleteMany();
54+
await prisma.tag.deleteMany();
55+
await prisma.documentShare.deleteMany();
56+
await prisma.document.deleteMany();
57+
await prisma.teamMember.deleteMany();
58+
await prisma.team.deleteMany();
59+
await prisma.userRole.deleteMany();
60+
await prisma.rolePermission.deleteMany();
61+
await prisma.permission.deleteMany();
62+
await prisma.role.deleteMany();
63+
await prisma.user.deleteMany();
64+
65+
// 创建权限
66+
const permissions = await Promise.all([
67+
prisma.permission.create({ data: { action: '*', resource: '*', description: 'Full system access' } }),
68+
prisma.permission.create({ data: { action: 'users:*', resource: 'users', description: 'Full user management' } }),
69+
prisma.permission.create({ data: { action: 'users:read', resource: 'users', description: 'View users' } }),
70+
prisma.permission.create({ data: { action: 'documents:*', resource: 'documents', description: 'Full document management' } }),
71+
prisma.permission.create({ data: { action: 'documents:read', resource: 'documents', description: 'View documents' } }),
72+
prisma.permission.create({ data: { action: 'teams:read', resource: 'teams', description: 'View teams' } }),
73+
prisma.permission.create({ data: { action: 'dashboard:read', resource: 'dashboard', description: 'View dashboard' } }),
74+
]);
75+
76+
// 创建角色
77+
const adminRole = await prisma.role.create({
78+
data: { name: 'admin', label: '系统管理员', description: '拥有系统全部权限' },
79+
});
80+
const userRole = await prisma.role.create({
81+
data: { name: 'user', label: '普通用户', description: '基本操作权限' },
82+
});
83+
84+
// 分配权限
85+
await prisma.rolePermission.create({ data: { roleId: adminRole.id, permissionId: permissions[0].id } });
86+
await prisma.rolePermission.createMany({
87+
data: [
88+
{ roleId: userRole.id, permissionId: permissions[2].id },
89+
{ roleId: userRole.id, permissionId: permissions[4].id },
90+
{ roleId: userRole.id, permissionId: permissions[5].id },
91+
{ roleId: userRole.id, permissionId: permissions[6].id },
92+
],
93+
});
94+
95+
// 创建用户
96+
const hashedPassword = await bcrypt.hash('Demo@123', 10);
97+
98+
const adminUser = await prisma.user.create({
99+
data: {
100+
email: 'admin@halolight.cn',
101+
username: 'admin',
102+
password: hashedPassword,
103+
name: '系统管理员',
104+
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=admin',
105+
status: 'ACTIVE',
106+
department: '技术部',
107+
position: 'CTO',
108+
},
109+
});
13110

14-
// Run prisma seed
15-
execSync('npx prisma db seed', {
16-
cwd: process.cwd(),
17-
stdio: 'inherit',
18-
env: {
19-
...process.env,
20-
DATABASE_URL: process.env.DATABASE_URL,
111+
const demoUser = await prisma.user.create({
112+
data: {
113+
email: 'demo@halolight.cn',
114+
username: 'demo',
115+
password: hashedPassword,
116+
name: '演示用户',
117+
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=demo',
118+
status: 'ACTIVE',
119+
department: '研发部',
120+
position: '前端工程师',
121+
},
122+
});
123+
124+
// 分配角色
125+
await prisma.userRole.createMany({
126+
data: [
127+
{ userId: adminUser.id, roleId: adminRole.id },
128+
{ userId: demoUser.id, roleId: userRole.id },
129+
],
130+
});
131+
132+
// 创建团队
133+
const devTeam = await prisma.team.create({
134+
data: {
135+
name: '研发团队',
136+
description: '负责产品研发和技术创新',
137+
ownerId: adminUser.id,
138+
},
139+
});
140+
141+
await prisma.teamMember.createMany({
142+
data: [
143+
{ teamId: devTeam.id, userId: adminUser.id, roleId: adminRole.id },
144+
{ teamId: devTeam.id, userId: demoUser.id, roleId: userRole.id },
145+
],
146+
});
147+
148+
// 创建文档
149+
await prisma.document.create({
150+
data: {
151+
title: 'HaloLight API 使用指南',
152+
content: '# HaloLight API 使用指南\n\n欢迎使用 HaloLight API...',
153+
type: 'markdown',
154+
size: BigInt(2048),
155+
views: 128,
156+
ownerId: adminUser.id,
157+
teamId: devTeam.id,
158+
},
159+
});
160+
161+
// 创建通知
162+
await prisma.notification.createMany({
163+
data: [
164+
{
165+
userId: demoUser.id,
166+
type: 'system',
167+
title: '欢迎使用 HaloLight',
168+
content: '感谢您使用 HaloLight 管理后台,这是演示环境,每天自动重置数据。',
169+
read: false,
170+
},
171+
{
172+
userId: adminUser.id,
173+
type: 'system',
174+
title: '系统初始化完成',
175+
content: '数据库已重置为初始状态。',
176+
read: false,
21177
},
178+
],
179+
});
180+
}
181+
182+
export default async function handler(req: VercelRequest, res: VercelResponse) {
183+
// 只允许 POST 或 GET 请求(Vercel Cron 使用 GET)
184+
if (req.method !== 'GET' && req.method !== 'POST') {
185+
return res.status(405).json({ error: 'Method not allowed' });
186+
}
187+
188+
// 验证请求合法性
189+
if (!isValidCronRequest(req)) {
190+
console.error('Unauthorized cron request attempt');
191+
return res.status(401).json({
192+
error: 'Unauthorized',
193+
message: 'Invalid or missing CRON_SECRET',
22194
});
195+
}
196+
197+
try {
198+
console.log('🔄 Starting database reset...');
199+
const startTime = Date.now();
200+
201+
await resetDatabase();
23202

24-
console.log('✅ Database reset completed');
203+
const duration = Date.now() - startTime;
204+
console.log(`✅ Database reset completed in ${duration}ms`);
25205

26206
return res.status(200).json({
27207
success: true,
28208
message: 'Database reset completed successfully',
209+
duration: `${duration}ms`,
29210
timestamp: new Date().toISOString(),
211+
accounts: [
212+
{ email: 'admin@halolight.cn', password: 'Demo@123', role: 'admin' },
213+
{ email: 'demo@halolight.cn', password: 'Demo@123', role: 'user' },
214+
],
30215
});
31216
} catch (error) {
32217
console.error('❌ Database reset failed:', error);
@@ -35,5 +220,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
35220
error: 'Database reset failed',
36221
message: error instanceof Error ? error.message : 'Unknown error',
37222
});
223+
} finally {
224+
await prisma.$disconnect();
38225
}
39226
}

src/common/filters/http-exception.filter.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@ import {
55
HttpException,
66
Logger,
77
} from '@nestjs/common';
8-
import { Request, Response } from 'express';
8+
9+
interface HttpRequest {
10+
url: string;
11+
method: string;
12+
}
13+
14+
interface HttpResponse {
15+
status: (code: number) => HttpResponse;
16+
json: (body: unknown) => void;
17+
}
918

1019
@Catch(HttpException)
1120
export class HttpExceptionFilter implements ExceptionFilter {
1221
private readonly logger = new Logger(HttpExceptionFilter.name);
1322

1423
catch(exception: HttpException, host: ArgumentsHost) {
1524
const ctx = host.switchToHttp();
16-
const response = ctx.getResponse<Response>();
17-
const request = ctx.getRequest<Request>();
25+
const response = ctx.getResponse<HttpResponse>();
26+
const request = ctx.getRequest<HttpRequest>();
1827
const status = exception.getStatus();
1928
const exceptionResponse = exception.getResponse();
2029

0 commit comments

Comments
 (0)