// src/services/googleAnalyticsService.js class GoogleAnalyticsService { constructor() { this.baseUrl = "/api/analytics"; // This matches your NGINX config } async getBasicMetrics({ startDate = "7daysAgo" } = {}) { try { const response = await fetch( `${this.baseUrl}/metrics?startDate=${startDate}`, { credentials: "include", } ); if (!response.ok) { throw new Error("Failed to fetch metrics"); } const result = await response.json(); if (!result?.data) { throw new Error("No data received"); } return this.processMetricsData(result.data); } catch (error) { console.error("Failed to fetch basic metrics:", error); throw error; } } async getRealTimeBasicData() { try { const response = await fetch(`${this.baseUrl}/realtime/basic`, { credentials: "include", }); if (!response.ok) { throw new Error("Failed to fetch basic realtime data"); } const result = await response.json(); if (!result?.data) { throw new Error("No data received"); } const processed = this.processRealTimeBasicData(result.data); return { ...processed, lastUpdated: result.lastUpdated || new Date().toISOString(), }; } catch (error) { console.error("Failed to fetch basic realtime data:", error); throw error; } } async getRealTimeDetailedData() { try { const response = await fetch(`${this.baseUrl}/realtime/detailed`, { credentials: "include", }); if (!response.ok) { throw new Error("Failed to fetch detailed realtime data"); } const result = await response.json(); if (!result?.data) { throw new Error("No data received"); } const processed = this.processRealTimeDetailedData(result.data); return { ...processed, lastUpdated: result.lastUpdated || new Date().toISOString(), }; } catch (error) { console.error("Failed to fetch detailed realtime data:", error); throw error; } } async getUserBehavior(timeRange = "30") { try { const response = await fetch( `${this.baseUrl}/user-behavior?timeRange=${timeRange}`, { credentials: "include", } ); if (!response.ok) { throw new Error("Failed to fetch user behavior"); } const result = await response.json(); console.log("Raw user behavior response:", result); if (!result?.success) { throw new Error("Invalid response structure"); } // Handle both data structures const rawData = result.data?.data || result.data; // Try to access the data differently based on the structure const pageResponse = rawData?.pageResponse || rawData?.reports?.[0]; const deviceResponse = rawData?.deviceResponse || rawData?.reports?.[1]; const sourceResponse = rawData?.sourceResponse || rawData?.reports?.[2]; console.log("Extracted responses:", { pageResponse, deviceResponse, sourceResponse, }); const processed = { success: true, data: { pageData: { pageData: this.processPageData(pageResponse), deviceData: this.processDeviceData(deviceResponse), }, sourceData: this.processSourceData(sourceResponse), }, }; console.log("Final processed data:", processed); return processed; } catch (error) { console.error("Failed to fetch user behavior:", error); throw error; } } processMetricsData(data) { if (!data?.rows) { console.log("No rows found in data"); return []; } return data.rows.map((row) => ({ date: row.dimensionValues[0].value, activeUsers: parseInt(row.metricValues[0].value), newUsers: parseInt(row.metricValues[1].value), avgSessionDuration: parseFloat(row.metricValues[2].value), pageViews: parseInt(row.metricValues[3].value), bounceRate: parseFloat(row.metricValues[4].value) * 100, conversions: parseInt(row.metricValues[5].value), })); } processRealTimeBasicData(data) { const last30MinUsers = parseInt( data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 ); const last5MinUsers = parseInt( data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 ); const byMinute = Array.from({ length: 30 }, (_, i) => { const matchingRow = data.timeSeriesResponse?.rows?.find( (row) => parseInt(row.dimensionValues[0].value) === i ); const users = matchingRow ? parseInt(matchingRow.metricValues[0].value) : 0; const timestamp = new Date(Date.now() - i * 60000); return { minute: -i, users, timestamp: timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }), }; }).reverse(); const tokenQuota = data.quotaInfo ? { projectHourly: data.quotaInfo.projectHourly || {}, daily: data.quotaInfo.daily || {}, serverErrors: data.quotaInfo.serverErrors || {}, thresholdedRequests: data.quotaInfo.thresholdedRequests || {}, } : null; return { last30MinUsers, last5MinUsers, byMinute, tokenQuota, }; } processRealTimeDetailedData(data) { return { currentPages: data.pageResponse?.rows?.map((row) => ({ page: row.dimensionValues[0].value, views: parseInt(row.metricValues[0].value), })) || [], sources: data.sourceResponse?.rows?.map((row) => ({ device: row.dimensionValues[0].value, users: parseInt(row.metricValues[0].value), })) || [], recentEvents: data.eventResponse?.rows ?.filter( (row) => !["session_start", "(other)"].includes( row.dimensionValues[0].value ) ) .map((row) => ({ event: row.dimensionValues[0].value, count: parseInt(row.metricValues[0].value), })) || [], }; } processPageData(data) { console.log("Processing page data input:", data); if (!data?.rows) { console.log("No rows in page data"); return []; } const processed = data.rows.map((row) => ({ path: row.dimensionValues[0].value || "Unknown", pageViews: parseInt(row.metricValues[0].value || 0), avgSessionDuration: parseFloat(row.metricValues[1].value || 0), bounceRate: parseFloat(row.metricValues[2].value || 0) * 100, engagedSessions: parseInt(row.metricValues[3].value || 0), })); console.log("Processed page data:", processed); return processed; } processDeviceData(data) { console.log("Processing device data input:", data); if (!data?.rows) { console.log("No rows in device data"); return []; } const processed = data.rows .filter((row) => { const device = (row.dimensionValues[0].value || "").toLowerCase(); return ["desktop", "mobile", "tablet"].includes(device); }) .map((row) => { const device = row.dimensionValues[0].value || "Unknown"; return { device: device.charAt(0).toUpperCase() + device.slice(1).toLowerCase(), pageViews: parseInt(row.metricValues[0].value || 0), sessions: parseInt(row.metricValues[1].value || 0), }; }) .sort((a, b) => b.pageViews - a.pageViews); console.log("Processed device data:", processed); return processed; } processSourceData(data) { console.log("Processing source data input:", data); if (!data?.rows) { console.log("No rows in source data"); return []; } const processed = data.rows.map((row) => ({ source: row.dimensionValues[0].value || "Unknown", sessions: parseInt(row.metricValues[0].value || 0), conversions: parseInt(row.metricValues[1].value || 0), })); console.log("Processed source data:", processed); return processed; } } // Create a single instance const service = new GoogleAnalyticsService(); // Export both the instance and the class export { service as googleAnalyticsService, GoogleAnalyticsService };