Files
Tiktok-Rezepte/backend/analytics-db.js
2026-08-26 16:17:54 +02:00

159 lines
4.9 KiB
JavaScript

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)) {
try {
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
} catch (e) {
console.error('Analytics Datei korrupt, Backup erstellt:', e.message);
try { writeFileSync(DB_FILE + '.bak', readFileSync(DB_FILE, 'utf-8')); } catch {}
}
}
return { events: [], _counter: 0 };
}
function saveDB(data) {
try {
writeFileSync(DB_FILE, JSON.stringify(data, null, 2));
} catch (e) {
console.error('Analytics DB Schreibfehler:', e.message);
}
}
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);
}