activity csv data
This commit is contained in:
@@ -2,43 +2,67 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
import { getEvents } from '@/lib/analytics';
|
import { getEvents } from '@/lib/analytics';
|
||||||
import { ApiResponse } from '@/lib/types';
|
import { ApiResponse } from '@/lib/types';
|
||||||
|
|
||||||
|
// 扩展Event类型以包含所需字段
|
||||||
|
interface EventWithFullPath extends Record<string, any> {
|
||||||
|
event_id?: string;
|
||||||
|
event_time?: string;
|
||||||
|
event_type?: string;
|
||||||
|
visitor_id?: string;
|
||||||
|
ip_address?: string;
|
||||||
|
req_full_path?: string;
|
||||||
|
referrer?: string;
|
||||||
|
// 其他可能的字段
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|
||||||
// Get required parameters
|
// Get parameters
|
||||||
const slug = searchParams.get('slug');
|
const slug = searchParams.get('slug');
|
||||||
const domain = searchParams.get('domain');
|
const domain = searchParams.get('domain');
|
||||||
|
const format = searchParams.get('format');
|
||||||
// If slug or domain is missing, return an error
|
|
||||||
if (!slug || !domain) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: false,
|
|
||||||
error: 'Missing required parameters: slug and domain are required'
|
|
||||||
}, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the shortUrl from domain and slug
|
|
||||||
const shortUrl = `https://${domain}/${slug}`;
|
|
||||||
|
|
||||||
// Log the request for debugging
|
|
||||||
console.log('Activities API received parameters:', {
|
|
||||||
slug,
|
|
||||||
domain,
|
|
||||||
shortUrl
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set default page size and page
|
|
||||||
const page = parseInt(searchParams.get('page') || '1');
|
|
||||||
const pageSize = parseInt(searchParams.get('pageSize') || '50');
|
|
||||||
|
|
||||||
// Optional date range parameters
|
// Optional date range parameters
|
||||||
const startTime = searchParams.get('startTime') || undefined;
|
const startTime = searchParams.get('startTime') || undefined;
|
||||||
const endTime = searchParams.get('endTime') || undefined;
|
const endTime = searchParams.get('endTime') || undefined;
|
||||||
|
|
||||||
// Get events for the specified shortUrl
|
// 修改验证逻辑,允许只使用时间范围
|
||||||
|
// 现在只需要确保有足够的过滤条件
|
||||||
|
if ((!slug && !domain) && (!startTime && !endTime)) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: 'Missing filter parameters: provide either slug/domain or date range'
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the shortUrl from domain and slug if both are provided
|
||||||
|
let shortUrl = undefined;
|
||||||
|
if (slug && domain) {
|
||||||
|
shortUrl = `https://${domain}/${slug}`;
|
||||||
|
|
||||||
|
// Log the request for debugging
|
||||||
|
console.log('Activities API received parameters:', {
|
||||||
|
slug,
|
||||||
|
domain,
|
||||||
|
shortUrl,
|
||||||
|
startTime,
|
||||||
|
endTime
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Activities API using time range filter:', {
|
||||||
|
startTime,
|
||||||
|
endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default page size and page
|
||||||
|
const page = parseInt(searchParams.get('page') || '1');
|
||||||
|
const pageSize = parseInt(searchParams.get('pageSize') || '50');
|
||||||
|
|
||||||
|
// Get events for the specified filters
|
||||||
const { events, total } = await getEvents({
|
const { events, total } = await getEvents({
|
||||||
linkSlug: slug,
|
linkSlug: slug || undefined,
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
startTime,
|
startTime,
|
||||||
@@ -47,6 +71,77 @@ export async function GET(request: NextRequest) {
|
|||||||
sortOrder: 'desc'
|
sortOrder: 'desc'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If format=csv, return CSV format data
|
||||||
|
if (format === 'csv') {
|
||||||
|
// CSV header line
|
||||||
|
let csvContent = 'time,activity,campaign,clientId,originPath\n';
|
||||||
|
|
||||||
|
// Helper function to extract utm_campaign from URL
|
||||||
|
const extractUtmCampaign = (url: string | null | undefined): string => {
|
||||||
|
if (!url) return 'demo';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to parse URL and extract utm_campaign parameter
|
||||||
|
const urlObj = new URL(url.startsWith('http') ? url : `https://example.com${url}`);
|
||||||
|
const campaign = urlObj.searchParams.get('utm_campaign');
|
||||||
|
if (campaign) return campaign;
|
||||||
|
|
||||||
|
// If utm_campaign is not found or URL parsing fails, use regex as fallback
|
||||||
|
const campaignMatch = url.match(/[?&]utm_campaign=([^&]+)/i);
|
||||||
|
if (campaignMatch && campaignMatch[1]) return campaignMatch[1];
|
||||||
|
} catch (_) {
|
||||||
|
// If URL parsing fails, try regex directly
|
||||||
|
const campaignMatch = url.match(/[?&]utm_campaign=([^&]+)/i);
|
||||||
|
if (campaignMatch && campaignMatch[1]) return campaignMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'demo'; // Default value
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process each event record
|
||||||
|
events.forEach(event => {
|
||||||
|
// 使用类型断言处理扩展字段
|
||||||
|
const eventWithFullPath = event as unknown as EventWithFullPath;
|
||||||
|
|
||||||
|
// Get the full URL from appropriate field
|
||||||
|
// Try different possible fields that might contain the URL
|
||||||
|
const fullUrl = eventWithFullPath.req_full_path || eventWithFullPath.referrer || '';
|
||||||
|
|
||||||
|
// Extract campaign from URL
|
||||||
|
const campaign = extractUtmCampaign(fullUrl);
|
||||||
|
|
||||||
|
// Format time
|
||||||
|
const time = eventWithFullPath.event_time ?
|
||||||
|
new Date(eventWithFullPath.event_time).toISOString().replace('T', ' ').slice(0, 19) :
|
||||||
|
'';
|
||||||
|
|
||||||
|
// Determine activity (event_type)
|
||||||
|
const activity = eventWithFullPath.event_type || '';
|
||||||
|
|
||||||
|
// Client ID (possibly part of visitor_id)
|
||||||
|
const clientId = eventWithFullPath.visitor_id?.split('-')[0] || 'undefined';
|
||||||
|
|
||||||
|
// Original path (use full URL field)
|
||||||
|
const originPath = fullUrl || 'undefined';
|
||||||
|
|
||||||
|
// Add to CSV content
|
||||||
|
csvContent += `${time},${activity},${campaign},${clientId},${originPath}\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate filename based on available parameters
|
||||||
|
const filename = slug
|
||||||
|
? `activities-${slug}.csv`
|
||||||
|
: `activities-${new Date().toISOString().slice(0,10)}.csv`;
|
||||||
|
|
||||||
|
// Return CSV response
|
||||||
|
return new NextResponse(csvContent, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
'Content-Disposition': `attachment; filename="${filename}"`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Process the events to extract useful information
|
// Process the events to extract useful information
|
||||||
const processedEvents = events.map(event => {
|
const processedEvents = events.map(event => {
|
||||||
// Parse JSON strings to objects safely
|
// Parse JSON strings to objects safely
|
||||||
|
|||||||
Reference in New Issue
Block a user