docs(09-01): complete schema and types foundation plan
- SUMMARY.md for plan 09-01: theme column, personal channels, event-key and subscription tables
This commit is contained in:
parent
b4f8ccac8b
commit
485053c639
1 changed files with 126 additions and 0 deletions
|
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
phase: 09-user-profile-preferences-new
|
||||
plan: "01"
|
||||
subsystem: schema-and-types
|
||||
tags: [migration, auth, types, schema, phase-9]
|
||||
dependency_graph:
|
||||
requires: []
|
||||
provides:
|
||||
- "user.theme column with DEFAULT 'system' and backfill"
|
||||
- "session.user.theme via Better Auth additionalFields"
|
||||
- "notification_channels.owner_user_id column + cascade delete"
|
||||
- "partial unique index notification_channels_owner_user_id_channel_type_uniq"
|
||||
- "notify_event_keys table (admin-CRUD, humanization layer)"
|
||||
- "user_event_subscriptions table (composite PK, opt-out model)"
|
||||
- "NotifyEventKey TypeScript interface"
|
||||
- "UserEventSubscription TypeScript interface"
|
||||
- "NotificationChannel.owner_user_id: string | null TypeScript field"
|
||||
affects:
|
||||
- "lib/types/pipeline.ts — downstream code using NotificationChannel"
|
||||
- "lib/auth.ts — session shape gains theme field"
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Mirror 083_add_user_timezone.sql pattern for theme column (ALTER + backfill + COMMENT)"
|
||||
- "partial unique index WHERE owner_user_id IS NOT NULL for defense-in-depth UPSERT safety"
|
||||
- "Better Auth additionalFields for server-canonical user preferences"
|
||||
key_files:
|
||||
created:
|
||||
- migrations/084_add_user_theme.sql
|
||||
- migrations/085_personal_notification_channels.sql
|
||||
- migrations/086_notify_event_keys_and_subscriptions.sql
|
||||
modified:
|
||||
- lib/auth.ts
|
||||
- lib/types/pipeline.ts
|
||||
decisions:
|
||||
- "Mirrored 083_add_user_timezone.sql structure exactly (ALTER + UPDATE backfill + COMMENT ON COLUMN)"
|
||||
- "theme defaults to 'system' — zero behavior change for existing users; next-themes detects OS at render time"
|
||||
- "partial unique index on notification_channels is defense-in-depth for Plan 02 API-layer UPSERT race window"
|
||||
- "user_event_subscriptions uses row-absence = enabled (opt-out) model per D-15"
|
||||
- "notify_event_keys is humanization layer only, not a gate — unknown keys are still routable"
|
||||
metrics:
|
||||
duration_minutes: 1
|
||||
completed_date: "2026-05-10"
|
||||
tasks_completed: 2
|
||||
files_created: 3
|
||||
files_modified: 2
|
||||
---
|
||||
|
||||
# Phase 9 Plan 01: Schema and Types Foundation Summary
|
||||
|
||||
One-liner: Three sequential migrations land theme column, personal-channel ownership, and event-subscription tables; TypeScript types extended to match; session.user.theme exposed via Better Auth additionalFields following Phase 7.1 precedent.
|
||||
|
||||
## Tasks Completed
|
||||
|
||||
| Task | Name | Commit | Files |
|
||||
|------|------|--------|-------|
|
||||
| 1 | Add theme column to user + Better Auth additionalField | 4fc4a3d | migrations/084_add_user_theme.sql, lib/auth.ts |
|
||||
| 2 | Add owner_user_id + partial unique index to notification_channels + create event-keys / subscriptions tables | b4f8cca | migrations/085_personal_notification_channels.sql, migrations/086_notify_event_keys_and_subscriptions.sql, lib/types/pipeline.ts |
|
||||
|
||||
## What Was Built
|
||||
|
||||
### migrations/084_add_user_theme.sql
|
||||
Adds `theme TEXT NOT NULL DEFAULT 'system'` to the Better Auth `"user"` table.
|
||||
|
||||
- `ALTER TABLE "user" ADD COLUMN IF NOT EXISTS theme TEXT NOT NULL DEFAULT 'system'`
|
||||
- Defensive backfill: `UPDATE "user" SET theme = 'system' WHERE theme IS NULL`
|
||||
- `COMMENT ON COLUMN "user".theme` documents allowed values (`light | dark | system`)
|
||||
- Mirrors 083_add_user_timezone.sql structure exactly
|
||||
|
||||
### migrations/085_personal_notification_channels.sql
|
||||
Extends `notification_channels` to support personal (per-user) channels alongside existing global rows.
|
||||
|
||||
- `ALTER TABLE notification_channels ADD COLUMN IF NOT EXISTS owner_user_id TEXT REFERENCES "user"(id) ON DELETE CASCADE`
|
||||
- Cascade delete: removing a Better Auth user removes their personal channels automatically
|
||||
- Non-unique index `idx_notification_channels_owner` on `(owner_user_id, channel_type) WHERE owner_user_id IS NOT NULL` for query performance
|
||||
- Partial unique index `notification_channels_owner_user_id_channel_type_uniq` on `(owner_user_id, channel_type) WHERE owner_user_id IS NOT NULL` — enforces one-personal-channel-per-type-per-user at DB layer; global rows (NULL owner) are exempt
|
||||
- `COMMENT ON COLUMN notification_channels.owner_user_id` documents the global vs personal semantics
|
||||
|
||||
### migrations/086_notify_event_keys_and_subscriptions.sql
|
||||
Creates two new tables for the notification event taxonomy and per-user subscription matrix.
|
||||
|
||||
- `notify_event_keys` table: `key TEXT PRIMARY KEY`, `display_label`, `description`, `sort_order`, `is_active`, audit columns. Index on `(is_active, sort_order, key)`. Seeded with `ticket_assigned_to_me` row via `INSERT ... ON CONFLICT (key) DO NOTHING`.
|
||||
- `user_event_subscriptions` table: composite `PRIMARY KEY (user_id, event_key, channel_type)`, `enabled BOOLEAN NOT NULL DEFAULT true`, `updated_at`. `user_id` cascades on user delete. Index on `(user_id)`.
|
||||
- Table comments document the opt-out model (row absence = enabled) and the humanization-layer-only role of event keys.
|
||||
|
||||
### lib/auth.ts — theme additionalField
|
||||
Added `theme` as the fourth `additionalFields` entry, immediately after `timezone`:
|
||||
|
||||
```typescript
|
||||
theme: {
|
||||
type: "string",
|
||||
defaultValue: "system",
|
||||
},
|
||||
```
|
||||
|
||||
`session.user.theme` is now exposed via Better Auth in the same way `session.user.timezone` is (Phase 7.1 precedent). The server-side value becomes available after the next session refresh.
|
||||
|
||||
### lib/types/pipeline.ts — type extensions
|
||||
Three changes, no other types touched:
|
||||
|
||||
1. `NotificationChannel` gains `owner_user_id: string | null` (inserted after `is_active`)
|
||||
2. New exported interface `NotifyEventKey` with fields `key`, `display_label`, `description: string | null`, `sort_order`, `is_active`, `created_at`, `updated_at`
|
||||
3. New exported interface `UserEventSubscription` with fields `user_id`, `event_key`, `channel_type: ChannelType`, `enabled`, `updated_at`
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
No new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries beyond what was declared in the plan's threat model. All six threats documented in plan T-09-01-01 through T-09-01-06 are addressed by the schema choices made here (cascade delete, composite PK, partial unique index, NOT NULL DEFAULT).
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
Files exist:
|
||||
- migrations/084_add_user_theme.sql: FOUND
|
||||
- migrations/085_personal_notification_channels.sql: FOUND
|
||||
- migrations/086_notify_event_keys_and_subscriptions.sql: FOUND
|
||||
- lib/auth.ts: FOUND (modified)
|
||||
- lib/types/pipeline.ts: FOUND (modified)
|
||||
|
||||
Commits exist:
|
||||
- 4fc4a3d: FOUND (Task 1)
|
||||
- b4f8cca: FOUND (Task 2)
|
||||
|
||||
TypeScript: `npx tsc --noEmit --pretty` exit 0 — no errors.
|
||||
Loading…
Add table
Add a link
Reference in a new issue