Updates
This commit is contained in:
145
backend/dist/analytics-db.js
vendored
Normal file
145
backend/dist/analytics-db.js
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
import Database from 'better-sqlite3';
|
||||
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.db');
|
||||
const db = new Database(DB_FILE);
|
||||
db.pragma('journal_mode = WAL');
|
||||
// Tabelle erstellen falls nicht vorhanden
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT,
|
||||
user_id INTEGER,
|
||||
timestamp TEXT,
|
||||
metadata TEXT
|
||||
);
|
||||
`);
|
||||
function now() {
|
||||
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
||||
}
|
||||
export function trackEvent(type, userId, metadata = {}) {
|
||||
const timestamp = now();
|
||||
const metaStr = JSON.stringify(metadata);
|
||||
const stmt = db.prepare('INSERT INTO events (type, user_id, timestamp, metadata) VALUES (?, ?, ?, ?)');
|
||||
const info = stmt.run(type, userId || null, timestamp, metaStr);
|
||||
return {
|
||||
id: info.lastInsertRowid,
|
||||
type,
|
||||
user_id: userId || null,
|
||||
timestamp,
|
||||
...metadata,
|
||||
};
|
||||
}
|
||||
export function getEvents({ from, to, type, user_id, limit = 100 } = {}) {
|
||||
let query = 'SELECT * FROM events WHERE 1=1';
|
||||
const params = [];
|
||||
if (from) {
|
||||
query += ' AND timestamp >= ?';
|
||||
params.push(from);
|
||||
}
|
||||
if (to) {
|
||||
query += ' AND timestamp <= ?';
|
||||
params.push(to);
|
||||
}
|
||||
if (type) {
|
||||
query += ' AND type = ?';
|
||||
params.push(type);
|
||||
}
|
||||
if (user_id) {
|
||||
query += ' AND user_id = ?';
|
||||
params.push(Number(user_id));
|
||||
}
|
||||
query += ' ORDER BY timestamp DESC LIMIT ?';
|
||||
params.push(limit);
|
||||
const rows = db.prepare(query).all(...params);
|
||||
return rows.map(r => {
|
||||
let meta = {};
|
||||
try {
|
||||
meta = JSON.parse(r.metadata || '{}');
|
||||
}
|
||||
catch { }
|
||||
return {
|
||||
id: r.id,
|
||||
type: r.type,
|
||||
user_id: r.user_id,
|
||||
timestamp: r.timestamp,
|
||||
...meta,
|
||||
};
|
||||
});
|
||||
}
|
||||
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 allPageviews = db.prepare("SELECT timestamp FROM events WHERE type = 'page_view'").all();
|
||||
const pageviewsToday = allPageviews.filter(e => e.timestamp.startsWith(today)).length;
|
||||
const pageviewsYesterday = allPageviews.filter(e => e.timestamp.startsWith(yesterday)).length;
|
||||
const pageviewsWeek = allPageviews.filter(e => e.timestamp.slice(0, 10) >= weekAgo).length;
|
||||
const pageviewsTotal = allPageviews.length;
|
||||
const activeUsersTotal = db.prepare("SELECT COUNT(DISTINCT user_id) as count FROM events WHERE user_id IS NOT NULL").get().count;
|
||||
const activeUsersToday = db.prepare("SELECT COUNT(DISTINCT user_id) as count FROM events WHERE user_id IS NOT NULL AND timestamp LIKE ?").get(`${today}%`).count;
|
||||
const recipesCreated = db.prepare("SELECT COUNT(*) as count FROM events WHERE type = 'recipe_created'").get().count;
|
||||
const recipesUpdated = db.prepare("SELECT COUNT(*) as count FROM events WHERE type = 'recipe_updated'").get().count;
|
||||
const recipesDeleted = db.prepare("SELECT COUNT(*) as count FROM events WHERE type = 'recipe_deleted'").get().count;
|
||||
const logins = db.prepare("SELECT COUNT(*) as count FROM events WHERE type = 'user_login'").get().count;
|
||||
return {
|
||||
pageviews: { today: pageviewsToday, yesterday: pageviewsYesterday, week: pageviewsWeek, total: pageviewsTotal },
|
||||
activeUsers: { total: activeUsersTotal, today: activeUsersToday },
|
||||
recipes: { created: recipesCreated, updated: recipesUpdated, deleted: recipesDeleted },
|
||||
logins,
|
||||
};
|
||||
}
|
||||
export function getPageviewsByDay(from, to) {
|
||||
let query = "SELECT SUBSTR(timestamp, 1, 10) as day, COUNT(*) as count FROM events WHERE type = 'page_view'";
|
||||
const params = [];
|
||||
if (from) {
|
||||
query += " AND SUBSTR(timestamp, 1, 10) >= ?";
|
||||
params.push(from);
|
||||
}
|
||||
if (to) {
|
||||
query += " AND SUBSTR(timestamp, 1, 10) <= ?";
|
||||
params.push(to);
|
||||
}
|
||||
query += " GROUP BY day ORDER BY day ASC";
|
||||
const rows = db.prepare(query).all(...params);
|
||||
return rows.map(r => ({ date: r.day, count: r.count }));
|
||||
}
|
||||
export function getTopUsers(limit = 10) {
|
||||
const rows = db.prepare(`
|
||||
SELECT user_id,
|
||||
SUM(CASE WHEN type = 'user_login' THEN 1 ELSE 0 END) as logins,
|
||||
SUM(CASE WHEN type = 'recipe_created' THEN 1 ELSE 0 END) as recipes_created,
|
||||
SUM(CASE WHEN type = 'recipe_updated' THEN 1 ELSE 0 END) as recipes_updated,
|
||||
COUNT(*) as total_events
|
||||
FROM events
|
||||
WHERE user_id IS NOT NULL
|
||||
GROUP BY user_id
|
||||
ORDER BY total_events DESC
|
||||
LIMIT ?
|
||||
`).all(limit);
|
||||
return rows;
|
||||
}
|
||||
export function getTopRecipes(limit = 10) {
|
||||
const pageviews = db.prepare("SELECT metadata FROM events WHERE type = 'page_view'").all();
|
||||
const recipeViews = {};
|
||||
for (const row of pageviews) {
|
||||
try {
|
||||
const meta = JSON.parse(row.metadata || '{}');
|
||||
if (meta.path && meta.path.startsWith('/recipe/')) {
|
||||
const id = meta.path.split('/')[2];
|
||||
if (id) {
|
||||
recipeViews[id] = (recipeViews[id] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
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 getEvents({ limit });
|
||||
}
|
||||
Reference in New Issue
Block a user