Files
shorturl-analytics/app/api/events/track/route.ts
2025-04-03 16:27:04 +08:00

139 lines
5.2 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 { Event } from '../../types';
import { v4 as uuid } from 'uuid';
import clickhouse from '@/lib/clickhouse';
// 将时间格式化为ClickHouse兼容的格式YYYY-MM-DD HH:MM:SS.SSS
const formatDateTime = (date: Date) => {
return date.toISOString().replace('T', ' ').replace('Z', '');
};
// Handler for POST request to track events
export async function POST(req: NextRequest) {
try {
// Parse request body
const eventData = await req.json();
// Validate required fields
if (!eventData.event_type) {
return NextResponse.json(
{ error: 'Missing required field: event_type' },
{ status: 400 }
);
}
// 获取当前时间并格式化
const currentTime = formatDateTime(new Date());
// Set default values for required fields if missing
const event: Event = {
// Core event fields
event_id: eventData.event_id || uuid(),
event_time: eventData.event_time ? formatDateTime(new Date(eventData.event_time)) : currentTime,
event_type: eventData.event_type,
event_attributes: eventData.event_attributes || '{}',
// Link information
link_id: eventData.link_id || '',
link_slug: eventData.link_slug || '',
link_label: eventData.link_label || '',
link_title: eventData.link_title || '',
link_original_url: eventData.link_original_url || '',
link_attributes: eventData.link_attributes || '{}',
link_created_at: eventData.link_created_at ? formatDateTime(new Date(eventData.link_created_at)) : currentTime,
link_expires_at: eventData.link_expires_at ? formatDateTime(new Date(eventData.link_expires_at)) : null,
link_tags: eventData.link_tags || '[]',
// User information
user_id: eventData.user_id || '',
user_name: eventData.user_name || '',
user_email: eventData.user_email || '',
user_attributes: eventData.user_attributes || '{}',
// Team information
team_id: eventData.team_id || '',
team_name: eventData.team_name || '',
team_attributes: eventData.team_attributes || '{}',
// Project information
project_id: eventData.project_id || '',
project_name: eventData.project_name || '',
project_attributes: eventData.project_attributes || '{}',
// QR code information
qr_code_id: eventData.qr_code_id || '',
qr_code_name: eventData.qr_code_name || '',
qr_code_attributes: eventData.qr_code_attributes || '{}',
// Visitor information
visitor_id: eventData.visitor_id || uuid(),
session_id: eventData.session_id || uuid(),
ip_address: eventData.ip_address || req.headers.get('x-forwarded-for')?.toString() || '',
country: eventData.country || '',
city: eventData.city || '',
device_type: eventData.device_type || '',
browser: eventData.browser || '',
os: eventData.os || '',
user_agent: eventData.user_agent || req.headers.get('user-agent')?.toString() || '',
// Referrer information
referrer: eventData.referrer || req.headers.get('referer')?.toString() || '',
utm_source: eventData.utm_source || '',
utm_medium: eventData.utm_medium || '',
utm_campaign: eventData.utm_campaign || '',
utm_term: eventData.utm_term || '',
utm_content: eventData.utm_content || '',
// Interaction information
time_spent_sec: eventData.time_spent_sec || 0,
is_bounce: eventData.is_bounce !== undefined ? eventData.is_bounce : true,
is_qr_scan: eventData.is_qr_scan !== undefined ? eventData.is_qr_scan : false,
conversion_type: eventData.conversion_type || '',
conversion_value: eventData.conversion_value || 0,
};
// 确保JSON字符串字段的正确处理
if (typeof event.event_attributes === 'object') {
event.event_attributes = JSON.stringify(event.event_attributes);
}
if (typeof event.link_attributes === 'object') {
event.link_attributes = JSON.stringify(event.link_attributes);
}
if (typeof event.user_attributes === 'object') {
event.user_attributes = JSON.stringify(event.user_attributes);
}
if (typeof event.team_attributes === 'object') {
event.team_attributes = JSON.stringify(event.team_attributes);
}
if (typeof event.project_attributes === 'object') {
event.project_attributes = JSON.stringify(event.project_attributes);
}
if (typeof event.qr_code_attributes === 'object') {
event.qr_code_attributes = JSON.stringify(event.qr_code_attributes);
}
if (typeof event.link_tags === 'object') {
event.link_tags = JSON.stringify(event.link_tags);
}
// Insert event into ClickHouse
await clickhouse.insert({
table: 'events',
values: [event],
format: 'JSONEachRow',
});
// Return success response
return NextResponse.json({
success: true,
message: 'Event tracked successfully',
event_id: event.event_id
}, { status: 201 });
} catch (error) {
console.error('Error tracking event:', error);
return NextResponse.json(
{ error: 'Failed to track event', details: (error as Error).message },
{ status: 500 }
);
}
}