|
| 1 | +# Dashboard |
| 2 | + |
| 3 | +A read-only dashboard for viewing notification system metrics. |
| 4 | + |
| 5 | +## Read-Only Enforcement |
| 6 | + |
| 7 | +**This dashboard is strictly read-only and never writes to the database.** |
| 8 | + |
| 9 | +### Safety Measures |
| 10 | + |
| 11 | +1. **Query Validation**: All database queries are validated to ensure only `SELECT` and `WITH` statements are executed |
| 12 | +2. **API Routes**: All metrics API routes are `GET` requests only |
| 13 | +3. **No Write Operations**: The codebase contains no `INSERT`, `UPDATE`, `DELETE`, `CREATE`, `ALTER`, `DROP`, or other write operations |
| 14 | + |
| 15 | +### Database Access |
| 16 | + |
| 17 | +- The dashboard connects to the database using a read-only connection |
| 18 | +- All queries go through the `query()` function in `src/lib/db.ts` which validates read-only operations |
| 19 | +- Any attempt to execute a write operation will throw an error |
| 20 | + |
| 21 | +### Authentication |
| 22 | + |
| 23 | +- The dashboard uses cookie-based authentication (no database writes) |
| 24 | +- Login credentials are validated against `DASHBOARD_SECRET` environment variable |
| 25 | +- Authentication cookies are set in memory only |
| 26 | + |
| 27 | +## Database Queries Guide |
| 28 | + |
| 29 | +This section documents every database query used by the dashboard and the intent |
| 30 | +behind each one. If the schema changes, use this to map the same business |
| 31 | +meaning to the new model. |
| 32 | + |
| 33 | +All queries live in `src/lib/metrics.ts` and are read-only. |
| 34 | + |
| 35 | +### Summary Metrics |
| 36 | + |
| 37 | +- Users total: `SELECT COUNT(*) FROM users` |
| 38 | + - Goal: total number of user records in the system. |
| 39 | +- Active subscriptions total: `SELECT COUNT(*) FROM user_preferences WHERE is_active = true` |
| 40 | + - Goal: total active DAO subscriptions across all users. |
| 41 | +- Active addresses total: `SELECT COUNT(*) FROM user_addresses WHERE is_active = true` |
| 42 | + - Goal: total active blockchain addresses linked to users. |
| 43 | +- Notifications total: `SELECT COUNT(*) FROM notifications` |
| 44 | + - Goal: total notifications created (all time). |
| 45 | + |
| 46 | +### Channel Distribution |
| 47 | + |
| 48 | +- Query: `SELECT channel, COUNT(*) FROM users GROUP BY channel ORDER BY count DESC` |
| 49 | + - Goal: how many users exist per channel (e.g., slack, telegram, etc.). |
| 50 | + |
| 51 | +### Engagement Distribution (addresses per user) |
| 52 | + |
| 53 | +- Query built by `buildEngagementDistributionQuery()` |
| 54 | + - Goal: histogram of users grouped by number of active addresses. |
| 55 | + - Uses `user_addresses` grouped by `user_id` with `is_active = true`. |
| 56 | + |
| 57 | +### User Growth (daily new users) |
| 58 | + |
| 59 | +- Query: `SELECT date_trunc('day', created_at), COUNT(*) FROM users GROUP BY day ORDER BY day` |
| 60 | + - Goal: daily new user signups; turned into a cumulative series for charts. |
| 61 | + |
| 62 | +### Top DAOs by active subscribers |
| 63 | + |
| 64 | +- Query in `getTopDaos()` |
| 65 | + - Source: `user_preferences` |
| 66 | + - Filters: `is_active = true`, `dao_id` not null/empty, `TRIM(dao_id)` |
| 67 | + - Goal: highest subscriber counts per DAO. |
| 68 | + |
| 69 | +### Notification Activity by Day |
| 70 | + |
| 71 | +- Query built by `buildNotificationActivityByDayQuery(daoId?)` |
| 72 | + - Source: `notifications` |
| 73 | + - Optional filter: `dao_id = $1` |
| 74 | + - Goal: daily notification counts globally or for a specific DAO. |
| 75 | + |
| 76 | +### Notification Activity by DAO |
| 77 | + |
| 78 | +- Query in `buildNotificationActivityByDaoQuery(limit)` |
| 79 | + - Source: `notifications` |
| 80 | + - Aggregation: `COUNT(DISTINCT event_id)` grouped by trimmed `dao_id` |
| 81 | + - Goal: most active DAOs by number of distinct notification events. |
| 82 | + |
| 83 | +### Users List + Filters (metrics table) |
| 84 | + |
| 85 | +- Queries built by `buildUsersQueries()` |
| 86 | + - Base sources: `users`, `user_addresses`, `user_preferences`, `channel_workspaces` |
| 87 | + - Joins: |
| 88 | + - Addresses per user (active addresses + array of addresses) |
| 89 | + - DAO preferences per user (active dao count + array of dao_ids) |
| 90 | + - Slack workspace name via `channel_workspaces` |
| 91 | + - Filters: |
| 92 | + - DAO filter via `user_preferences` (active only) |
| 93 | + - Channel filter via `users.channel` |
| 94 | + - Address filter via `user_addresses.address` (active only) |
| 95 | + - Min/Max addresses via computed address_count |
| 96 | + - Goal: paginated list of users with derived metrics and filters for the UI. |
| 97 | + |
| 98 | +## Development |
| 99 | + |
| 100 | +```bash |
| 101 | +npm run dev |
| 102 | +``` |
| 103 | + |
| 104 | +## Build |
| 105 | + |
| 106 | +```bash |
| 107 | +npm run build |
| 108 | +``` |
| 109 | + |
| 110 | +## Environment Variables |
| 111 | + |
| 112 | +- `DATABASE_URL` - PostgreSQL connection string (read-only access recommended) |
| 113 | +- `DASHBOARD_SECRET` - Password for dashboard access |
| 114 | + |
0 commit comments