import express from 'express'; import { realtimeAnalytics } from '../services/realtimeAnalytics.js'; import { validateRequest } from '../middleware/validateRequest.js'; import Joi from 'joi'; const router = express.Router(); // Validation schemas const recordMetricSchema = Joi.object({ accountId: Joi.string().required(), metricType: Joi.string().required(), value: Joi.number().default(1), metadata: Joi.object().optional() }); const getMetricsSchema = Joi.object({ metrics: Joi.array().items(Joi.string()).required(), timeRange: Joi.string().valid('minute', 'hour', 'day').default('hour') }); const funnelSchema = Joi.object({ steps: Joi.array().items(Joi.object({ name: Joi.string().required(), metric: Joi.string().required() })).required(), timeRange: Joi.string().valid('minute', 'hour', 'day').default('day') }); // Record a metric router.post('/metric', validateRequest(recordMetricSchema), async (req, res, next) => { try { const { accountId, metricType, value, metadata } = req.body; await realtimeAnalytics.recordMetric(accountId, metricType, value, metadata); res.json({ success: true, message: 'Metric recorded' }); } catch (error) { next(error); } }); // Get real-time metrics router.get('/:accountId/metrics', validateRequest(getMetricsSchema, 'query'), async (req, res, next) => { try { const { accountId } = req.params; const { metrics, timeRange } = req.query; const data = await realtimeAnalytics.getRealtimeMetrics( accountId, metrics, timeRange ); res.json({ accountId, timeRange, metrics: data, timestamp: new Date() }); } catch (error) { next(error); } }); // Get dashboard data router.get('/:accountId/dashboard', async (req, res, next) => { try { const { accountId } = req.params; const dashboardData = await realtimeAnalytics.getDashboardData(accountId); res.json(dashboardData); } catch (error) { next(error); } }); // Get funnel analytics router.post('/:accountId/funnel', validateRequest(funnelSchema), async (req, res, next) => { try { const { accountId } = req.params; const { steps, timeRange } = req.body; const funnelData = await realtimeAnalytics.getFunnelAnalytics( accountId, steps, timeRange ); res.json({ accountId, timeRange, funnel: funnelData, timestamp: new Date() }); } catch (error) { next(error); } }); // Get cohort analytics router.get('/:accountId/cohorts', async (req, res, next) => { try { const { accountId } = req.params; const { cohortType = 'new_users', metric = 'active_users', periods = 7 } = req.query; const cohortData = await realtimeAnalytics.getCohortAnalytics( accountId, cohortType, metric, parseInt(periods) ); res.json({ accountId, cohortType, metric, cohorts: cohortData, timestamp: new Date() }); } catch (error) { next(error); } }); // WebSocket endpoint for real-time streaming router.ws('/:accountId/stream', (ws, req) => { const { accountId } = req.params; let unsubscribe; ws.on('message', (msg) => { try { const { action, metrics } = JSON.parse(msg); if (action === 'subscribe' && metrics) { // Set up metric streaming unsubscribe = realtimeAnalytics.streamMetrics( accountId, metrics, (data) => { ws.send(JSON.stringify({ type: 'metric', data })); } ); ws.send(JSON.stringify({ type: 'subscribed', metrics })); } else if (action === 'unsubscribe' && unsubscribe) { unsubscribe(); unsubscribe = null; ws.send(JSON.stringify({ type: 'unsubscribed' })); } } catch (error) { ws.send(JSON.stringify({ type: 'error', message: error.message })); } }); ws.on('close', () => { if (unsubscribe) { unsubscribe(); } }); }); export default router;