From 2e34cd5b4b7a7ef8dbf4b662836b506f5722b3e7 Mon Sep 17 00:00:00 2001 From: William Tso Date: Thu, 17 Apr 2025 23:48:46 +0800 Subject: [PATCH] activity csv data --- app/api/activities/route.ts | 145 +++++++++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 25 deletions(-) diff --git a/app/api/activities/route.ts b/app/api/activities/route.ts index 809dfaa..e9e4fce 100644 --- a/app/api/activities/route.ts +++ b/app/api/activities/route.ts @@ -2,43 +2,67 @@ import { NextRequest, NextResponse } from 'next/server'; import { getEvents } from '@/lib/analytics'; import { ApiResponse } from '@/lib/types'; +// 扩展Event类型以包含所需字段 +interface EventWithFullPath extends Record { + 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) { try { const { searchParams } = new URL(request.url); - // Get required parameters + // Get parameters const slug = searchParams.get('slug'); const domain = searchParams.get('domain'); - - // 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'); + const format = searchParams.get('format'); // Optional date range parameters const startTime = searchParams.get('startTime') || 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({ - linkSlug: slug, + linkSlug: slug || undefined, page, pageSize, startTime, @@ -47,6 +71,77 @@ export async function GET(request: NextRequest) { 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 const processedEvents = events.map(event => { // Parse JSON strings to objects safely