Files
shorturl-analytics/app/api/events/route.ts
2025-03-26 11:26:53 +08:00

124 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 });
}
}