124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
||
import type { ApiResponse, EventsQueryParams, EventType, Event } from '@/lib/types';
|
||
import {
|
||
getEvents,
|
||
getEventsSummary,
|
||
getTimeSeriesData,
|
||
getGeoAnalytics,
|
||
getDeviceAnalytics
|
||
} from '@/lib/analytics';
|
||
import clickhouse from '@/lib/clickhouse';
|
||
import { v4 as uuidv4 } from 'uuid';
|
||
|
||
// 获取事件列表
|
||
export async function GET(request: NextRequest) {
|
||
try {
|
||
const searchParams = request.nextUrl.searchParams;
|
||
|
||
const params: EventsQueryParams = {
|
||
startTime: searchParams.get('startTime') || undefined,
|
||
endTime: searchParams.get('endTime') || undefined,
|
||
eventType: searchParams.get('eventType') as EventType || undefined,
|
||
linkId: searchParams.get('linkId') || undefined,
|
||
linkSlug: searchParams.get('linkSlug') || undefined,
|
||
userId: searchParams.get('userId') || undefined,
|
||
teamId: searchParams.get('teamId') || undefined,
|
||
projectId: searchParams.get('projectId') || undefined,
|
||
page: searchParams.has('page') ? parseInt(searchParams.get('page')!, 10) : 1,
|
||
pageSize: searchParams.has('pageSize') ? parseInt(searchParams.get('pageSize')!, 10) : 20,
|
||
sortBy: searchParams.get('sortBy') || undefined,
|
||
sortOrder: (searchParams.get('sortOrder') as 'asc' | 'desc') || undefined
|
||
};
|
||
|
||
const { events, total } = await getEvents(params);
|
||
|
||
const response: ApiResponse<typeof events> = {
|
||
success: true,
|
||
data: events,
|
||
meta: {
|
||
total,
|
||
page: params.page,
|
||
pageSize: params.pageSize
|
||
}
|
||
};
|
||
|
||
return NextResponse.json(response);
|
||
} catch (error) {
|
||
const response: ApiResponse<null> = {
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||
};
|
||
return NextResponse.json(response, { status: 500 });
|
||
}
|
||
}
|
||
|
||
// 写入事件数据
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
// 解析请求体
|
||
const eventData = await request.json();
|
||
|
||
// 验证必填字段
|
||
const requiredFields = ['event_type', 'link_id', 'visitor_id'];
|
||
for (const field of requiredFields) {
|
||
if (!eventData[field]) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: `Missing required field: ${field}`
|
||
}, { status: 400 });
|
||
}
|
||
}
|
||
|
||
// 生成事件ID和时间戳(如果未提供)
|
||
const event: Record<string, any> = {
|
||
...eventData,
|
||
event_id: eventData.event_id || uuidv4(),
|
||
event_time: eventData.event_time || new Date().toISOString()
|
||
};
|
||
|
||
// 处理JSON字段(ClickHouse要求JSON字段为字符串)
|
||
const jsonFields = [
|
||
'event_attributes',
|
||
'link_attributes',
|
||
'user_attributes',
|
||
'team_attributes',
|
||
'project_attributes',
|
||
'qr_code_attributes'
|
||
];
|
||
|
||
// 处理所有JSON对象字段
|
||
jsonFields.forEach(field => {
|
||
if (event[field] && typeof event[field] === 'object') {
|
||
event[field] = JSON.stringify(event[field]);
|
||
}
|
||
});
|
||
|
||
// 处理数组字段
|
||
if (event.link_tags && Array.isArray(event.link_tags)) {
|
||
event.link_tags = JSON.stringify(event.link_tags);
|
||
}
|
||
|
||
// 使用ClickHouse客户端插入数据
|
||
await clickhouse.insert({
|
||
table: 'events',
|
||
values: [event],
|
||
format: 'JSONEachRow'
|
||
});
|
||
|
||
const response: ApiResponse<{ event_id: string }> = {
|
||
success: true,
|
||
data: { event_id: event.event_id as string }
|
||
};
|
||
|
||
return NextResponse.json(response, { status: 201 });
|
||
} catch (error) {
|
||
console.error('Error inserting event:', error);
|
||
|
||
const response: ApiResponse<null> = {
|
||
success: false,
|
||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||
};
|
||
|
||
return NextResponse.json(response, { status: 500 });
|
||
}
|
||
}
|