import { readFileSync, writeFileSync, existsSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DB_FILE = process.env.ANALYTICS_PATH || join(__dirname, 'analytics.json'); function loadDB() { if (existsSync(DB_FILE)) { return JSON.parse(readFileSync(DB_FILE, 'utf-8')); } return { events: [], _counter: 0 }; } function saveDB(data) { writeFileSync(DB_FILE, JSON.stringify(data, null, 2)); } let db = loadDB(); function nextId() { db._counter = (db._counter || 0) + 1; return db._counter; } function now() { return new Date().toISOString().replace('T', ' ').slice(0, 19); } export function trackEvent(type, userId, metadata = {}) { const event = { id: nextId(), type, user_id: userId || null, timestamp: now(), ...metadata, }; db.events.push(event); saveDB(db); return event; } export function getEvents({ from, to, type, user_id, limit = 100 } = {}) { let results = [...db.events]; if (from) { results = results.filter(e => e.timestamp >= from); } if (to) { results = results.filter(e => e.timestamp <= to); } if (type) { results = results.filter(e => e.type === type); } if (user_id) { results = results.filter(e => e.user_id === Number(user_id)); } results.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); return results.slice(0, limit); } export function getOverview() { const today = new Date().toISOString().slice(0, 10); const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const pageviews = db.events.filter(e => e.type === 'page_view'); const pageviewsToday = pageviews.filter(e => e.timestamp.startsWith(today)).length; const pageviewsYesterday = pageviews.filter(e => e.timestamp.startsWith(yesterday)).length; const pageviewsWeek = pageviews.filter(e => e.timestamp.slice(0, 10) >= weekAgo).length; const pageviewsTotal = pageviews.length; const activeUsers = new Set(db.events.filter(e => e.user_id).map(e => e.user_id)); const activeUsersToday = new Set( db.events.filter(e => e.user_id && e.timestamp.startsWith(today)).map(e => e.user_id) ).size; const recipesCreated = db.events.filter(e => e.type === 'recipe_created').length; const recipesUpdated = db.events.filter(e => e.type === 'recipe_updated').length; const recipesDeleted = db.events.filter(e => e.type === 'recipe_deleted').length; const logins = db.events.filter(e => e.type === 'user_login').length; return { pageviews: { today: pageviewsToday, yesterday: pageviewsYesterday, week: pageviewsWeek, total: pageviewsTotal }, activeUsers: { total: activeUsers.size, today: activeUsersToday }, recipes: { created: recipesCreated, updated: recipesUpdated, deleted: recipesDeleted }, logins, }; } export function getPageviewsByDay(from, to) { const pageviews = db.events.filter(e => e.type === 'page_view'); const days = {}; for (const e of pageviews) { const day = e.timestamp.slice(0, 10); if (from && day < from) continue; if (to && day > to) continue; days[day] = (days[day] || 0) + 1; } return Object.entries(days) .map(([date, count]) => ({ date, count })) .sort((a, b) => a.date.localeCompare(b.date)); } export function getTopUsers(limit = 10) { const userActivity = {}; for (const e of db.events) { if (!e.user_id) continue; if (!userActivity[e.user_id]) { userActivity[e.user_id] = { user_id: e.user_id, logins: 0, recipes_created: 0, recipes_updated: 0, total_events: 0 }; } userActivity[e.user_id].total_events++; if (e.type === 'user_login') userActivity[e.user_id].logins++; if (e.type === 'recipe_created') userActivity[e.user_id].recipes_created++; if (e.type === 'recipe_updated') userActivity[e.user_id].recipes_updated++; } return Object.values(userActivity) .sort((a, b) => b.total_events - a.total_events) .slice(0, limit); } export function getTopRecipes(limit = 10) { const recipeViews = {}; for (const e of db.events) { if (e.type === 'page_view' && e.path?.startsWith('/recipe/')) { const id = e.path.split('/')[2]; if (id) { recipeViews[id] = (recipeViews[id] || 0) + 1; } } } return Object.entries(recipeViews) .map(([recipe_id, views]) => ({ recipe_id: Number(recipe_id), views })) .sort((a, b) => b.views - a.views) .slice(0, limit); } export function getRecentActivity(limit = 50) { return db.events .sort((a, b) => b.timestamp.localeCompare(a.timestamp)) .slice(0, limit); }