events: add events table and prepared statements

This commit is contained in:
Arlind Ukshini 2026-04-27 10:29:46 +02:00
parent 44f7a8c5d7
commit 0cc3dc808e

View file

@ -56,6 +56,22 @@ db.exec(`
CREATE INDEX IF NOT EXISTS idx_bifrost_joins_email ON bifrost_joins(email);
CREATE INDEX IF NOT EXISTS idx_bifrost_joins_clicked_at ON bifrost_joins(clicked_at);
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type TEXT NOT NULL,
email TEXT NOT NULL,
occurred_at INTEGER NOT NULL,
session_id TEXT,
device_type TEXT,
os TEXT,
browser TEXT,
user_agent TEXT,
meta TEXT
);
CREATE INDEX IF NOT EXISTS idx_events_email ON events(email);
CREATE INDEX IF NOT EXISTS idx_events_type_time ON events(event_type, occurred_at);
`);
// ─── Migrations ──────────────────────────────────────────────
@ -173,6 +189,49 @@ export const q = {
countJoins: db.prepare(`SELECT COUNT(*) AS n FROM bifrost_joins`),
countUniqueJoiners: db.prepare(`SELECT COUNT(DISTINCT email) AS n FROM bifrost_joins`),
// events — engagement tracking. One row per landmark event
// (login, timeline_view) with device fields parsed from the UA.
// Read-only from the app side; written once per event, never updated.
recordEvent: db.prepare(
`INSERT INTO events
(event_type, email, occurred_at, session_id, device_type, os, browser, user_agent, meta)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
),
listEvents: db.prepare(
`SELECT id, event_type, email, occurred_at, session_id, device_type, os, browser, user_agent, meta
FROM events ORDER BY occurred_at DESC LIMIT ?`
),
listEventsByType: db.prepare(
`SELECT id, event_type, email, occurred_at, session_id, device_type, os, browser, user_agent, meta
FROM events WHERE event_type = ? ORDER BY occurred_at DESC LIMIT ?`
),
listEventsForEmail: db.prepare(
`SELECT id, event_type, occurred_at, session_id, device_type, os, browser, meta
FROM events WHERE email = ? ORDER BY occurred_at DESC`
),
// Per-user summary: pivot login + timeline_view counts onto one row.
summariseEvents: db.prepare(
`SELECT email,
SUM(CASE WHEN event_type = 'login' THEN 1 ELSE 0 END) AS logins,
SUM(CASE WHEN event_type = 'timeline_view' THEN 1 ELSE 0 END) AS timeline_views,
MAX(occurred_at) AS last_seen
FROM events
GROUP BY email
ORDER BY last_seen DESC`
),
countEventsByType: db.prepare(
`SELECT event_type,
COUNT(*) AS total,
COUNT(DISTINCT email) AS unique_users
FROM events GROUP BY event_type ORDER BY event_type`
),
deviceBreakdown: db.prepare(
`SELECT device_type, COUNT(*) AS n
FROM events
WHERE device_type IS NOT NULL
GROUP BY device_type ORDER BY n DESC`
),
// cleanup
cleanup: {
sessions: db.prepare('DELETE FROM sessions WHERE expires_at < ?'),