Path A baseline reconciliation. drizzle-kit pull against tia_prod showed
prod had drifted well past schema.ts because legacy hand-rolled migrations
0003-0015 wrote to the DB but were never reflected back into TypeScript.
Shared-table drift fixed:
- users: + password_hash, + password_updated_at
- families: + tier, + max_children, + max_members
- children: col is 'stage' (kept JS key currentStage -> stage);
'image_url' not 'profile_photo_url'; birth_date is DATE;
sex nullable; dropped phantom stage_overrides
- family_members: dropped phantom display_name
- family_invites: dropped phantom display_name, accepted_at
- audit_log: + resource_id, + resource_type; metadata -> jsonb; +5 indexes
- memories: + vision_tags (text[]), + vision_embedding (vector 1536)
- logs.ts: 'diapers' phantom table renamed to diapersLogs ('diapers_logs')
19 missing tables added across new files:
- admin.ts: admins, admin_sessions, password_resets
- support.ts: support_tickets, support_responses
- ai.ts: chat_sessions, chat_messages, ai_usage
- medical.ts: medicines, medication_doses, allergies, illness_logs, doctor_visits
- affiliate.ts: member_profiles, recommended_products, product_clicks
- logs.ts: + milestone_achievements
- audit.ts: + log_corrections
BUG FIX: schema/index.ts never re-exported ./logs — Drizzle was blind to
feeds/sleeps/vaccinations/growth/medications. Now exported.
Verified: tsc --noEmit has zero non-test errors. Dropped phantom columns
confirmed to have zero references in src/.
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import {
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
uuid,
|
|
varchar,
|
|
check,
|
|
} from "drizzle-orm/pg-core";
|
|
import { sql } from "drizzle-orm";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Admin / auth-extra schema — aligned to PRODUCTION as of 2026-05-19 baseline.
|
|
// Tables: admins, admin_sessions, password_resets
|
|
// (legacy migrations 0006_admin_auth, 0009_admin_sessions, 0004's password_resets).
|
|
//
|
|
// SECURITY NOTE: `admins` is the privileged back-office account table, fully
|
|
// separate from the `users` table. Password hashes here gate admin access —
|
|
// treat any change to this table as security-critical.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const admins = pgTable(
|
|
"admins",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
username: varchar("username", { length: 50 }).notNull().unique(),
|
|
passwordHash: varchar("password_hash", { length: 255 }).notNull(),
|
|
role: varchar("role", { length: 20 }).default("admin"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
|
lastLogin: timestamp("last_login", { withTimezone: true }),
|
|
},
|
|
(table) => [
|
|
check(
|
|
"admins_role_check",
|
|
sql`(role)::text = ANY (ARRAY['super_admin','admin','support'])`
|
|
),
|
|
]
|
|
);
|
|
|
|
export const adminSessions = pgTable("admin_sessions", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
adminId: uuid("admin_id").notNull(),
|
|
sessionToken: text("session_token").notNull().unique(),
|
|
expires: timestamp("expires", { withTimezone: true }).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
|
});
|
|
|
|
export const passwordResets = pgTable("password_resets", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
userId: uuid("user_id").notNull(),
|
|
token: text("token").notNull().unique(),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
usedAt: timestamp("used_at", { withTimezone: true }),
|
|
});
|
|
|
|
export type Admin = typeof admins.$inferSelect;
|
|
export type AdminSession = typeof adminSessions.$inferSelect;
|
|
export type PasswordReset = typeof passwordResets.$inferSelect;
|