480 lines
14 KiB
JavaScript
480 lines
14 KiB
JavaScript
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 smart revenue projection
|
|
router.get('/projection', async (req, res) => {
|
|
try {
|
|
const { timeRange, startDate, endDate } = req.query;
|
|
console.log('[Events Route] Projection 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()
|
|
};
|
|
|
|
// Try to get from cache first with a short TTL
|
|
const cacheKey = redisService._getCacheKey('projection', params);
|
|
const cachedData = await redisService.get(cacheKey);
|
|
|
|
if (cachedData) {
|
|
console.log('[Events Route] Cache hit for projection');
|
|
return res.json(cachedData);
|
|
}
|
|
|
|
console.log('[Events Route] Calculating smart projection with params:', params);
|
|
const projection = await eventsService.calculateSmartProjection(params);
|
|
|
|
// Cache the results with a short TTL (5 minutes)
|
|
await redisService.set(cacheKey, projection, 300);
|
|
|
|
res.json(projection);
|
|
} catch (error) {
|
|
console.error("[Events Route] Error calculating projection:", 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;
|
|
}
|