254 lines
7.7 KiB
JavaScript
254 lines
7.7 KiB
JavaScript
const express = require('express');
|
|
const { BetaAnalyticsDataClient } = require('@google-analytics/data');
|
|
const router = express.Router();
|
|
const logger = require('../utils/logger');
|
|
|
|
// Initialize GA4 client
|
|
const analyticsClient = new BetaAnalyticsDataClient({
|
|
credentials: JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON)
|
|
});
|
|
|
|
const propertyId = process.env.GA_PROPERTY_ID;
|
|
|
|
// Cache durations
|
|
const CACHE_DURATIONS = {
|
|
REALTIME_BASIC: 60, // 1 minute
|
|
REALTIME_DETAILED: 300, // 5 minutes
|
|
BASIC_METRICS: 3600, // 1 hour
|
|
USER_BEHAVIOR: 3600 // 1 hour
|
|
};
|
|
|
|
// Basic metrics endpoint
|
|
router.get('/metrics', async (req, res) => {
|
|
try {
|
|
const { startDate = '7daysAgo' } = req.query;
|
|
const cacheKey = `analytics:basic_metrics:${startDate}`;
|
|
|
|
// Check Redis cache
|
|
const cachedData = await req.redisClient.get(cacheKey);
|
|
if (cachedData) {
|
|
logger.info('Returning cached basic metrics data');
|
|
return res.json({ success: true, data: JSON.parse(cachedData) });
|
|
}
|
|
|
|
// Fetch from GA4
|
|
const [response] = await analyticsClient.runReport({
|
|
property: `properties/${propertyId}`,
|
|
dateRanges: [{ startDate, endDate: 'today' }],
|
|
dimensions: [{ name: 'date' }],
|
|
metrics: [
|
|
{ name: 'activeUsers' },
|
|
{ name: 'newUsers' },
|
|
{ name: 'averageSessionDuration' },
|
|
{ name: 'screenPageViews' },
|
|
{ name: 'bounceRate' },
|
|
{ name: 'conversions' }
|
|
],
|
|
returnPropertyQuota: true
|
|
});
|
|
|
|
// Cache the response
|
|
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
|
EX: CACHE_DURATIONS.BASIC_METRICS
|
|
});
|
|
|
|
res.json({ success: true, data: response });
|
|
} catch (error) {
|
|
logger.error('Error fetching basic metrics:', error);
|
|
res.status(500).json({ success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
// Realtime basic data endpoint
|
|
router.get('/realtime/basic', async (req, res) => {
|
|
try {
|
|
const cacheKey = 'analytics:realtime:basic';
|
|
|
|
// Check Redis cache
|
|
const cachedData = await req.redisClient.get(cacheKey);
|
|
if (cachedData) {
|
|
logger.info('Returning cached realtime basic data');
|
|
return res.json({ success: true, data: JSON.parse(cachedData) });
|
|
}
|
|
|
|
// Fetch active users
|
|
const [userResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
metrics: [{ name: 'activeUsers' }],
|
|
returnPropertyQuota: true
|
|
});
|
|
|
|
// Fetch last 5 minutes
|
|
const [fiveMinResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
metrics: [{ name: 'activeUsers' }],
|
|
minuteRanges: [{ startMinutesAgo: 5, endMinutesAgo: 0 }]
|
|
});
|
|
|
|
// Fetch time series data
|
|
const [timeSeriesResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
dimensions: [{ name: 'minutesAgo' }],
|
|
metrics: [{ name: 'activeUsers' }]
|
|
});
|
|
|
|
const response = {
|
|
userResponse,
|
|
fiveMinResponse,
|
|
timeSeriesResponse,
|
|
quotaInfo: {
|
|
projectHourly: userResponse.propertyQuota.tokensPerProjectPerHour,
|
|
daily: userResponse.propertyQuota.tokensPerDay,
|
|
serverErrors: userResponse.propertyQuota.serverErrorsPerProjectPerHour,
|
|
thresholdedRequests: userResponse.propertyQuota.potentiallyThresholdedRequestsPerHour
|
|
}
|
|
};
|
|
|
|
// Cache the response
|
|
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
|
EX: CACHE_DURATIONS.REALTIME_BASIC
|
|
});
|
|
|
|
res.json({ success: true, data: response });
|
|
} catch (error) {
|
|
logger.error('Error fetching realtime basic data:', error);
|
|
res.status(500).json({ success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
// Realtime detailed data endpoint
|
|
router.get('/realtime/detailed', async (req, res) => {
|
|
try {
|
|
const cacheKey = 'analytics:realtime:detailed';
|
|
|
|
// Check Redis cache
|
|
const cachedData = await req.redisClient.get(cacheKey);
|
|
if (cachedData) {
|
|
logger.info('Returning cached realtime detailed data');
|
|
return res.json({ success: true, data: JSON.parse(cachedData) });
|
|
}
|
|
|
|
// Fetch current pages
|
|
const [pageResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
dimensions: [{ name: 'unifiedScreenName' }],
|
|
metrics: [{ name: 'screenPageViews' }],
|
|
orderBy: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
|
limit: 25
|
|
});
|
|
|
|
// Fetch events
|
|
const [eventResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
dimensions: [{ name: 'eventName' }],
|
|
metrics: [{ name: 'eventCount' }],
|
|
orderBy: [{ metric: { metricName: 'eventCount' }, desc: true }],
|
|
limit: 25
|
|
});
|
|
|
|
// Fetch device categories
|
|
const [deviceResponse] = await analyticsClient.runRealtimeReport({
|
|
property: `properties/${propertyId}`,
|
|
dimensions: [{ name: 'deviceCategory' }],
|
|
metrics: [{ name: 'activeUsers' }],
|
|
orderBy: [{ metric: { metricName: 'activeUsers' }, desc: true }],
|
|
limit: 10,
|
|
returnPropertyQuota: true
|
|
});
|
|
|
|
const response = {
|
|
pageResponse,
|
|
eventResponse,
|
|
sourceResponse: deviceResponse
|
|
};
|
|
|
|
// Cache the response
|
|
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
|
EX: CACHE_DURATIONS.REALTIME_DETAILED
|
|
});
|
|
|
|
res.json({ success: true, data: response });
|
|
} catch (error) {
|
|
logger.error('Error fetching realtime detailed data:', error);
|
|
res.status(500).json({ success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
// User behavior endpoint
|
|
router.get('/user-behavior', async (req, res) => {
|
|
try {
|
|
const { timeRange = '30' } = req.query;
|
|
const cacheKey = `analytics:user_behavior:${timeRange}`;
|
|
|
|
// Check Redis cache
|
|
const cachedData = await req.redisClient.get(cacheKey);
|
|
if (cachedData) {
|
|
logger.info('Returning cached user behavior data');
|
|
return res.json({ success: true, data: JSON.parse(cachedData) });
|
|
}
|
|
|
|
// Fetch page data
|
|
const [pageResponse] = await analyticsClient.runReport({
|
|
property: `properties/${propertyId}`,
|
|
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
|
dimensions: [{ name: 'pagePath' }],
|
|
metrics: [
|
|
{ name: 'screenPageViews' },
|
|
{ name: 'averageSessionDuration' },
|
|
{ name: 'bounceRate' },
|
|
{ name: 'sessions' }
|
|
],
|
|
orderBy: [{
|
|
metric: { metricName: 'screenPageViews' },
|
|
desc: true
|
|
}],
|
|
limit: 25
|
|
});
|
|
|
|
// Fetch device data
|
|
const [deviceResponse] = await analyticsClient.runReport({
|
|
property: `properties/${propertyId}`,
|
|
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
|
dimensions: [{ name: 'deviceCategory' }],
|
|
metrics: [
|
|
{ name: 'screenPageViews' },
|
|
{ name: 'sessions' }
|
|
]
|
|
});
|
|
|
|
// Fetch source data
|
|
const [sourceResponse] = await analyticsClient.runReport({
|
|
property: `properties/${propertyId}`,
|
|
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
|
dimensions: [{ name: 'sessionSource' }],
|
|
metrics: [
|
|
{ name: 'sessions' },
|
|
{ name: 'conversions' }
|
|
],
|
|
orderBy: [{
|
|
metric: { metricName: 'sessions' },
|
|
desc: true
|
|
}],
|
|
limit: 25,
|
|
returnPropertyQuota: true
|
|
});
|
|
|
|
const response = {
|
|
pageResponse,
|
|
deviceResponse,
|
|
sourceResponse
|
|
};
|
|
|
|
// Cache the response
|
|
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
|
EX: CACHE_DURATIONS.USER_BEHAVIOR
|
|
});
|
|
|
|
res.json({ success: true, data: response });
|
|
} catch (error) {
|
|
logger.error('Error fetching user behavior data:', error);
|
|
res.status(500).json({ success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|