import express from 'express'; import { EventsService } from '../services/events.service.js'; import { TimeManager } from '../utils/time.utils.js'; import { RedisService } from '../services/redis.service.js'; // Import METRIC_IDS from events service const METRIC_IDS = { PLACED_ORDER: 'Y8cqcF', SHIPPED_ORDER: 'VExpdL', ACCOUNT_CREATED: 'TeeypV', CANCELED_ORDER: 'YjVMNg', NEW_BLOG_POST: 'YcxeDr', PAYMENT_REFUNDED: 'R7XUYh' }; export function createEventsRouter(apiKey, apiRevision) { const router = express.Router(); const timeManager = new TimeManager(); const eventsService = new EventsService(apiKey, apiRevision); const redisService = new RedisService(); // Get events with optional filtering router.get('/', async (req, res) => { try { const params = { pageSize: parseInt(req.query.pageSize) || 50, sort: req.query.sort || '-datetime', metricId: req.query.metricId, startDate: req.query.startDate, endDate: req.query.endDate, pageCursor: req.query.pageCursor, fields: {} }; // Parse fields parameter if provided if (req.query.fields) { try { params.fields = JSON.parse(req.query.fields); } catch (e) { console.warn('[Events Route] Invalid fields parameter:', e); } } console.log('[Events Route] Fetching events with params:', params); const data = await eventsService.getEvents(params); console.log('[Events Route] Success:', { count: data.data?.length || 0, included: data.included?.length || 0 }); res.json(data); } catch (error) { console.error('[Events Route] Error:', error); res.status(500).json({ status: 'error', message: error.message, details: error.response?.data || null }); } }); // Get events by time range router.get('/by-time/:timeRange', async (req, res) => { try { const { timeRange } = req.params; const { metricId, startDate, endDate } = req.query; let result; if (timeRange === 'custom') { if (!startDate || !endDate) { return res.status(400).json({ error: 'Custom range requires startDate and endDate' }); } const range = timeManager.getCustomRange(startDate, endDate); if (!range) { return res.status(400).json({ error: 'Invalid date range' }); } result = await eventsService.getEvents({ metricId, startDate: range.start.toISO(), endDate: range.end.toISO() }); } else { result = await eventsService.getEventsByTimeRange( timeRange, { metricId } ); } res.json(result); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Get comprehensive statistics for a time period router.get('/stats', async (req, res) => { try { const { timeRange, startDate, endDate } = req.query; console.log('[Events Route] Stats request:', { timeRange, startDate, endDate }); let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else if (timeRange) { range = timeManager.getDateRange(timeRange); } else { return res.status(400).json({ error: 'Must provide either timeRange or startDate and endDate' }); } if (!range) { return res.status(400).json({ error: 'Invalid time range' }); } const params = { timeRange, startDate: range.start.toISO(), endDate: range.end.toISO() }; console.log('[Events Route] Calculating period stats with params:', params); const stats = await eventsService.calculatePeriodStats(params); console.log('[Events Route] Stats response:', { timeRange: { start: range.start.toISO(), end: range.end.toISO() }, shippedCount: stats?.shipping?.shippedCount, totalOrders: stats?.orderCount }); res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, stats }); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Add new route for detailed stats router.get('/stats/details', async (req, res) => { try { const { timeRange, startDate, endDate, metric, daily = false } = req.query; let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else if (timeRange) { range = timeManager.getDateRange(timeRange); } else { return res.status(400).json({ error: 'Must provide either timeRange or startDate and endDate' }); } if (!range) { return res.status(400).json({ error: 'Invalid time range' }); } const params = { timeRange, startDate: range.start.toISO(), endDate: range.end.toISO(), metric, daily: daily === 'true' || daily === true }; // Try to get from cache first const cacheKey = redisService._getCacheKey('stats:details', params); const cachedData = await redisService.get(cacheKey); if (cachedData) { console.log('[Events Route] Cache hit for detailed stats'); return res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, stats: cachedData }); } const stats = await eventsService.calculateDetailedStats(params); // Cache the results const ttl = redisService._getTTL(timeRange); await redisService.set(cacheKey, stats, ttl); res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, stats }); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Get product statistics for a time period router.get('/products', async (req, res) => { try { const { timeRange, startDate, endDate } = req.query; let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else if (timeRange) { range = timeManager.getDateRange(timeRange); } else { return res.status(400).json({ error: 'Must provide either timeRange or startDate and endDate' }); } if (!range) { return res.status(400).json({ error: 'Invalid time range' }); } const params = { timeRange, startDate: range.start.toISO(), endDate: range.end.toISO() }; // Try to get from cache first const cacheKey = redisService._getCacheKey('events', params); const cachedData = await redisService.getEventData('products', params); if (cachedData) { console.log('[Events Route] Cache hit for products'); return res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, stats: { products: cachedData } }); } const stats = await eventsService.calculatePeriodStats(params); res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, stats }); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Get event feed (multiple event types sorted by time) router.get('/feed', async (req, res) => { try { const { timeRange, startDate, endDate, metricIds } = req.query; let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else if (timeRange) { range = timeManager.getDateRange(timeRange); } else { return res.status(400).json({ error: 'Must provide either timeRange or startDate and endDate' }); } if (!range) { return res.status(400).json({ error: 'Invalid time range' }); } const params = { timeRange, startDate: range.start.toISO(), endDate: range.end.toISO(), metricIds: metricIds ? JSON.parse(metricIds) : null }; const result = await eventsService.getMultiMetricEvents(params); res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, ...result }); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Get aggregated events data router.get('/aggregate', async (req, res) => { try { const { timeRange, startDate, endDate, interval = 'day', metricId, property } = req.query; let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else if (timeRange) { range = timeManager.getDateRange(timeRange); } else { return res.status(400).json({ error: 'Must provide either timeRange or startDate and endDate' }); } if (!range) { return res.status(400).json({ error: 'Invalid time range' }); } const params = { timeRange, startDate: range.start.toISO(), endDate: range.end.toISO(), metricId, interval, property }; const result = await eventsService.getEvents(params); const groupedData = timeManager.groupEventsByInterval(result.data, interval, property); res.json({ timeRange: { start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }, data: groupedData }); } catch (error) { console.error("[Events Route] Error:", error); res.status(500).json({ error: error.message }); } }); // Get date range for a given time period router.get("/dateRange", async (req, res) => { try { const { timeRange, startDate, endDate } = req.query; let range; if (startDate && endDate) { range = timeManager.getCustomRange(startDate, endDate); } else { range = timeManager.getDateRange(timeRange || 'today'); } if (!range) { return res.status(400).json({ error: "Invalid time range parameters" }); } res.json({ start: range.start.toISO(), end: range.end.toISO(), displayStart: timeManager.formatForDisplay(range.start), displayEnd: timeManager.formatForDisplay(range.end) }); } catch (error) { console.error('Error getting date range:', error); res.status(500).json({ error: "Failed to get date range" }); } }); // Clear cache for a specific time range router.post("/clearCache", async (req, res) => { try { const { timeRange, startDate, endDate } = req.body; await redisService.clearCache({ timeRange, startDate, endDate }); res.json({ message: "Cache cleared successfully" }); } catch (error) { console.error('Error clearing cache:', error); res.status(500).json({ error: "Failed to clear cache" }); } }); // Add new batch metrics endpoint router.get('/batch', async (req, res) => { try { const { timeRange, startDate, endDate, metrics } = req.query; // Parse metrics array from query const metricsList = metrics ? JSON.parse(metrics) : []; const params = timeRange === 'custom' ? { startDate, endDate, metrics: metricsList } : { timeRange, metrics: metricsList }; const results = await eventsService.getBatchMetrics(params); res.json(results); } catch (error) { console.error('[Events Route] Error in batch request:', error); res.status(500).json({ error: error.message }); } }); return router; }