Phase 10 — Admin Role & Settings (ADMIN-01/02/03) #17
@@ -30,6 +30,7 @@ const varcharBin = (name: string) =>
|
||||
/**
|
||||
* Members of the household — identity keyed by oidc_iss + oidc_sub (never email, per D-10).
|
||||
* Color auto-assigned from palette on first login (D-06).
|
||||
* isAdmin: first-login-wins bootstrap (D-01); gated by app_config.setup_complete in Phase 12.
|
||||
*/
|
||||
export const users = mysqlTable(
|
||||
'users',
|
||||
@@ -40,6 +41,8 @@ export const users = mysqlTable(
|
||||
displayName: varchar('display_name', { length: 256 }),
|
||||
color: varchar('color', { length: 7 }).notNull(), // hex e.g. '#4A90D9'
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
// v1.1 (Phase 10): admin role flag — first-login-wins; Phase 12 tightens bootstrap
|
||||
isAdmin: boolean('is_admin').default(false).notNull(),
|
||||
},
|
||||
(t) => [
|
||||
// Composite unique key — identity is iss+sub, never email (D-10)
|
||||
@@ -48,9 +51,11 @@ export const users = mysqlTable(
|
||||
);
|
||||
|
||||
/**
|
||||
* Encrypted Fastmail app-password credentials per member (D-04).
|
||||
* Encrypted provider credentials per member (D-04 generic provider shape, Fastmail/CalDAV first).
|
||||
* Keyed by user_id (oidc_sub → user row). Backend-only, never exposed to frontend.
|
||||
* Stored as JSON: { iv, authTag, ciphertext } (AES-256-GCM).
|
||||
* providerType: generic discriminator (D-04); 'caldav' is the only implemented provider.
|
||||
* UNIQUE(user_id) enforces one-credential-per-member (D-05) and enables onDuplicateKeyUpdate upsert.
|
||||
*/
|
||||
export const memberCredentials = mysqlTable(
|
||||
'member_credentials',
|
||||
@@ -64,8 +69,14 @@ export const memberCredentials = mysqlTable(
|
||||
fastmailEmail: varchar('fastmail_email', { length: 256 }).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
|
||||
// v1.1 (Phase 10, D-04): generic provider discriminator; 'caldav' default for existing rows
|
||||
providerType: varchar('provider_type', { length: 64 }).notNull().default('caldav'),
|
||||
},
|
||||
(t) => [index('idx_member_credentials_user_id').on(t.userId)],
|
||||
(t) => [
|
||||
index('idx_member_credentials_user_id').on(t.userId),
|
||||
// v1.1 (Phase 10, D-05): one credential per member; enables Drizzle onDuplicateKeyUpdate upsert
|
||||
unique('uniq_member_credential_user').on(t.userId),
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -129,6 +140,8 @@ export const calendarEvents = mysqlTable(
|
||||
// Events with hasRrule=true have dtstartUtc potentially years before any window,
|
||||
// so the windowed query must include them regardless of dtstartUtc range (see RESEARCH.md §Pitfall 5).
|
||||
hasRrule: boolean('has_rrule').default(false).notNull(),
|
||||
// v1.1 (Phase 10): reminder lead time in minutes; nullable — consumed by Phase 11
|
||||
reminderLeadMinutes: int('reminder_lead_minutes'),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
|
||||
},
|
||||
(t) => [
|
||||
@@ -256,6 +269,22 @@ export const pushSubscriptions = mysqlTable(
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* Global app configuration — simple key/value store.
|
||||
*
|
||||
* v1.1 (Phase 10): ships the table; consumed by downstream phases:
|
||||
* - setup_complete (boolean string 'true'/'false'): Phase 12 setup wizard writes this key
|
||||
* after first-time setup; Phase 10's first-login-wins admin bootstrap reads it in Phase 12.
|
||||
* Key: 'setup_complete', Value: 'true' | 'false' | null (not yet set → treated as false).
|
||||
*
|
||||
* Do NOT add setup_complete gating logic here — Phase 12 owns that.
|
||||
*/
|
||||
export const appConfig = mysqlTable('app_config', {
|
||||
key: varchar('key', { length: 128 }).primaryKey(),
|
||||
value: text('value'), // nullable
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Items within a list.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user