diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts new file mode 100644 index 0000000..217e9ea --- /dev/null +++ b/src/app/api/events/route.ts @@ -0,0 +1,112 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { AIEventInputSchema, AIEventQuerySchema } from '@/lib/validations'; +import crypto from 'crypto'; + +export async function POST(request: NextRequest) { + // 1. API Key 验证 + const apiKey = request.headers.get('X-API-Key'); + const expectedKey = process.env.WEBHOOK_API_KEY; + + if (!apiKey || !expectedKey || !crypto.timingSafeEqual( + Buffer.from(apiKey), + Buffer.from(expectedKey) + )) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // 2. 解析请求体 + let body: unknown; + try { + body = await request.json(); + } catch (error) { + return NextResponse.json( + { error: 'Invalid JSON' }, + { status: 400 } + ); + } + + // 3. 验证数据 + const validationResult = AIEventInputSchema.array().safeParse(body); + if (!validationResult.success) { + return NextResponse.json( + { + error: 'Validation failed', + details: validationResult.error.errors, + }, + { status: 400 } + ); + } + + // 4. 创建事件 + try { + const result = await prisma.aIEvent.createMany({ + data: validationResult.data, + skipDuplicates: true, + }); + + return NextResponse.json( + { + created: result.count, + total: validationResult.data.length, + }, + { status: 201 } + ); + } catch (error) { + console.error('Failed to create AI events:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +} + +export async function GET(request: NextRequest) { + // 1. 解析查询参数 + const searchParams = request.nextUrl.searchParams; + const queryParams = { + year: searchParams.get('year'), + limit: searchParams.get('limit'), + offset: searchParams.get('offset'), + }; + + // 2. 验证查询参数 + const validationResult = AIEventQuerySchema.safeParse(queryParams); + if (!validationResult.success) { + return NextResponse.json( + { + error: 'Invalid query parameters', + details: validationResult.error.errors, + }, + { status: 400 } + ); + } + + // 3. 获取事件 + try { + const events = await prisma.aIEvent.findMany({ + where: validationResult.data.year + ? { + eventDate: { + gte: new Date(`${validationResult.data.year}-01-01T00:00:00Z`), + lte: new Date(`${validationResult.data.year}-12-31T23:59:59Z`), + }, + } + : undefined, + orderBy: { eventDate: 'desc' }, + take: validationResult.data.limit || 100, + skip: validationResult.data.offset || 0, + }); + + return NextResponse.json({ events }); + } catch (error) { + console.error('Failed to fetch AI events:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +}