events: add events table and prepared statements
This commit is contained in:
parent
44f7a8c5d7
commit
0cc3dc808e
1 changed files with 59 additions and 0 deletions
59
src/db.js
59
src/db.js
|
|
@ -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_email ON bifrost_joins(email);
|
||||||
CREATE INDEX IF NOT EXISTS idx_bifrost_joins_clicked_at ON bifrost_joins(clicked_at);
|
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 ──────────────────────────────────────────────
|
// ─── Migrations ──────────────────────────────────────────────
|
||||||
|
|
@ -173,6 +189,49 @@ export const q = {
|
||||||
countJoins: db.prepare(`SELECT COUNT(*) AS n FROM bifrost_joins`),
|
countJoins: db.prepare(`SELECT COUNT(*) AS n FROM bifrost_joins`),
|
||||||
countUniqueJoiners: db.prepare(`SELECT COUNT(DISTINCT email) 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
|
||||||
cleanup: {
|
cleanup: {
|
||||||
sessions: db.prepare('DELETE FROM sessions WHERE expires_at < ?'),
|
sessions: db.prepare('DELETE FROM sessions WHERE expires_at < ?'),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue