115 lines
3.0 KiB
JavaScript
115 lines
3.0 KiB
JavaScript
// Website Events Tracking API Endpoint - Neon Database
|
|
// Tracks search, cart, and component view events from the website
|
|
|
|
import { neon } from '@neondatabase/serverless';
|
|
|
|
function getNeonClient() {
|
|
const connectionString = process.env.NEON_DATABASE_URL;
|
|
if (!connectionString) {
|
|
throw new Error('NEON_DATABASE_URL not configured');
|
|
}
|
|
return neon(connectionString);
|
|
}
|
|
|
|
const VALID_EVENT_TYPES = [
|
|
'search',
|
|
'cart_add',
|
|
'cart_remove',
|
|
'cart_checkout',
|
|
'component_view',
|
|
'copy_command'
|
|
];
|
|
|
|
const MAX_EVENTS_PER_BATCH = 50;
|
|
|
|
function validateEventsData(data) {
|
|
const { events } = data;
|
|
|
|
if (!events || !Array.isArray(events) || events.length === 0) {
|
|
return { valid: false, error: 'events array is required and must not be empty' };
|
|
}
|
|
|
|
if (events.length > MAX_EVENTS_PER_BATCH) {
|
|
return { valid: false, error: `Maximum ${MAX_EVENTS_PER_BATCH} events per batch` };
|
|
}
|
|
|
|
for (const event of events) {
|
|
if (!event.event_type || !VALID_EVENT_TYPES.includes(event.event_type)) {
|
|
return { valid: false, error: `Invalid event_type: ${event.event_type}` };
|
|
}
|
|
}
|
|
|
|
return { valid: true };
|
|
}
|
|
|
|
export default async function handler(req, res) {
|
|
// CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
return res.status(200).json({ ok: true });
|
|
}
|
|
|
|
if (req.method !== 'POST') {
|
|
return res.status(405).json({ error: 'Method not allowed', allowed: ['POST'] });
|
|
}
|
|
|
|
try {
|
|
const {
|
|
events,
|
|
session_id,
|
|
visitor_id,
|
|
screen_width,
|
|
referrer
|
|
} = req.body;
|
|
|
|
const validation = validateEventsData({ events });
|
|
if (!validation.valid) {
|
|
return res.status(400).json({ error: validation.error });
|
|
}
|
|
|
|
// Extract country from Vercel header
|
|
const country = req.headers['x-vercel-ip-country'] || null;
|
|
|
|
const sql = getNeonClient();
|
|
|
|
// Insert each event
|
|
let inserted = 0;
|
|
for (const event of events) {
|
|
await sql`
|
|
INSERT INTO website_events (
|
|
event_type, event_data, page_path,
|
|
referrer, session_id, visitor_id,
|
|
country, screen_width
|
|
) VALUES (
|
|
${event.event_type},
|
|
${event.event_data ? JSON.stringify(event.event_data) : null},
|
|
${event.page_path || null},
|
|
${referrer ? referrer.substring(0, 1000) : null},
|
|
${session_id || null},
|
|
${visitor_id || null},
|
|
${country},
|
|
${screen_width || null}
|
|
)
|
|
`;
|
|
inserted++;
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: `${inserted} events tracked`,
|
|
data: { count: inserted, timestamp: new Date().toISOString() }
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Website events tracking error:', error);
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: 'Failed to track website events',
|
|
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
});
|
|
}
|
|
}
|