mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-10-22 13:54:10 -04:00
first pass analytics redo
This commit is contained in:
1 parent
b656fd04c0
commit
45ffa39f2a
69 files changed
+6400
-1138
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { integer, sqliteTable, text, index, unique } from 'drizzle-orm/sqlite-core';
|
||||
import { integer, sqliteTable, text, index, unique, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
export const user = sqliteTable('user', {
|
||||
id: text('id').primaryKey(),
|
||||
@@ -8,7 +9,10 @@ export const user = sqliteTable('user', {
|
||||
passwordHash: text('password_hash'),
|
||||
appleId: text('apple_id').unique(),
|
||||
googleId: text('google_id').unique(),
|
||||
isPrivate: integer('is_private', { mode: 'boolean' }).default(false)
|
||||
isPrivate: integer('is_private', { mode: 'boolean' }).default(false),
|
||||
// Additive: existing rows stay NULL ("unknown signup date"). New rows get a
|
||||
// timestamp automatically via the client-side default function.
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date())
|
||||
});
|
||||
|
||||
export const session = sqliteTable('session', {
|
||||
@@ -65,3 +69,55 @@ export const verseSubmissions = sqliteTable('verse_submissions', {
|
||||
]);
|
||||
|
||||
export type VerseSubmission = typeof verseSubmissions.$inferSelect;
|
||||
|
||||
// ── Statistics dashboard (see statistics-plan.md) ────────────────────────────
|
||||
// One immutable row per "as of" day. Payload/config/playerState are JSON text.
|
||||
export const statsSnapshot = sqliteTable('stats_snapshot', {
|
||||
// as-of date, YYYY-MM-DD
|
||||
date: text('date').primaryKey(),
|
||||
// build time (server timestamp)
|
||||
generatedAt: integer('generated_at', { mode: 'timestamp' }).notNull(),
|
||||
// scheduled | lazy | reconstructed
|
||||
buildKind: text('build_kind').notNull(),
|
||||
// stable hash of the settings used at build time
|
||||
configVersion: text('config_version').notNull(),
|
||||
// all aggregate series / lab data
|
||||
payload: text('payload').notNull(),
|
||||
// compact per-player state for render-time segmentation (JSON)
|
||||
playerState: text('player_state'),
|
||||
// copy of the resolved settings used at build time (JSON)
|
||||
config: text('config').notNull()
|
||||
});
|
||||
|
||||
export type StatsSnapshot = typeof statsSnapshot.$inferSelect;
|
||||
|
||||
// Generic, extensible event log. Unlocks instrumentation B/D/E/F now; C later.
|
||||
export const analyticsEvents = sqliteTable('analytics_events', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
anonymousId: text('anonymous_id'),
|
||||
// Nullable so ON DELETE SET NULL keeps historical events when a user is deleted.
|
||||
userId: text('user_id').references(() => user.id, { onDelete: 'set null' }),
|
||||
date: text('date'),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
metadata: text('metadata')
|
||||
}, (table) => [
|
||||
index('analytics_events_name_date_idx').on(table.name, table.date),
|
||||
index('analytics_events_anonymous_id_date_idx').on(table.anonymousId, table.date),
|
||||
// Dedupe verse_served per player per day (reloads), without collapsing
|
||||
// legitimate repeat events like multiple shares in a day.
|
||||
uniqueIndex('analytics_events_serve_unique')
|
||||
.on(table.name, table.anonymousId, table.date)
|
||||
.where(sql`${table.name} = 'verse_served'`)
|
||||
]);
|
||||
|
||||
export type AnalyticsEvent = typeof analyticsEvents.$inferSelect;
|
||||
|
||||
// Simple key/value store for dashboard thresholds (env vars provide defaults).
|
||||
export const dashboardSettings = sqliteTable('dashboard_settings', {
|
||||
key: text('key').primaryKey(),
|
||||
value: text('value').notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull()
|
||||
});
|
||||
|
||||
export type DashboardSetting = typeof dashboardSettings.$inferSelect;
|
||||
Reference in new issue
Block a user