tia/src/db/schema/admin.ts
Mannu a3d3a140ed refactor(db/schema): align TypeScript schema with production
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/.
2026-05-23 12:17:20 +05:30

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;