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/.
147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
import {
|
|
pgTable,
|
|
pgEnum,
|
|
uuid,
|
|
timestamp,
|
|
text,
|
|
integer,
|
|
boolean,
|
|
date,
|
|
real,
|
|
index,
|
|
uniqueIndex,
|
|
} from "drizzle-orm/pg-core";
|
|
import { children as childrenTable } from "./family";
|
|
|
|
const children = childrenTable;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Logs schema — aligned to PRODUCTION as of 2026-05-19 baseline.
|
|
//
|
|
// Drift corrected at baseline:
|
|
// - `diapers` table renamed to `diapersLogs` (DB table is `diapers_logs`).
|
|
// The app already writes to diapers_logs via raw SQL; the old `diapers`
|
|
// pgTable was dead/phantom.
|
|
// - sleeps.startedAt is NULLABLE in prod.
|
|
// - milestone_achievements added (legacy 0014_milestones).
|
|
//
|
|
// RLS NOTE: feeds / diapers_logs / sleeps / vaccinations / growth carry a
|
|
// `family_isolation` row-level-security policy in prod. RLS policies are not
|
|
// modelled in these pgTable definitions — they live in the DB and are managed
|
|
// separately. Do not assume Drizzle will recreate them.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Feed types
|
|
export const feedType = pgEnum("feed_type", ["breast_milk", "formula", "solid", "water", "other"]);
|
|
export const feedMethod = pgEnum("feed_method", ["bottle", "breast_left", "breast_right", "breast_both", "cup", "spoon", "finger", "self"]);
|
|
|
|
// Diaper types
|
|
export const diaperType = pgEnum("diaper_type", ["wet", "dirty", "both", "dry"]);
|
|
|
|
// Sleep types
|
|
export const sleepType = pgEnum("sleep_type", ["nap", "night"]);
|
|
|
|
// Logs: feeds
|
|
export const feeds = pgTable("feeds", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
type: feedType("type").notNull(),
|
|
method: feedMethod("method"),
|
|
amountMl: real("amount_ml"),
|
|
notes: text("notes"),
|
|
loggedAt: timestamp("logged_at").defaultNow().notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Logs: diapers (DB table: diapers_logs)
|
|
export const diapersLogs = pgTable("diapers_logs", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
type: diaperType("type").notNull(),
|
|
notes: text("notes"),
|
|
loggedAt: timestamp("logged_at").defaultNow().notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Logs: sleep
|
|
export const sleeps = pgTable("sleeps", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
type: sleepType("type").notNull(),
|
|
startedAt: timestamp("started_at"), // nullable in prod
|
|
endedAt: timestamp("ended_at"),
|
|
durationMinutes: integer("duration_minutes"),
|
|
notes: text("notes"),
|
|
loggedAt: timestamp("logged_at").defaultNow().notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Logs: vaccinations
|
|
export const vaccinations = pgTable("vaccinations", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
vaccineName: text("vaccine_name").notNull(),
|
|
scheduledDate: date("scheduled_date").notNull(),
|
|
givenDate: date("given_date"),
|
|
status: text("status").notNull().default("pending"), // pending, given, skipped, delayed
|
|
provider: text("provider"),
|
|
lotNumber: text("lot_number"),
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Logs: growth
|
|
export const growth = pgTable("growth", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
measuredAt: timestamp("measured_at").notNull(),
|
|
weightKg: real("weight_kg"),
|
|
heightCm: real("height_cm"),
|
|
headCircumferenceCm: real("head_circumference_cm"),
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Logs: medications (the prescription record; doses are in medical.ts)
|
|
export const medications = pgTable("medications", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").references(() => children.id).notNull(),
|
|
name: text("name").notNull(),
|
|
dosage: text("dosage"),
|
|
frequency: text("frequency"),
|
|
startDate: date("start_date").notNull(),
|
|
endDate: date("end_date"),
|
|
active: boolean("active").notNull().default(true),
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// Milestone achievements (legacy 0014_milestones)
|
|
export const milestoneAchievements = pgTable(
|
|
"milestone_achievements",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
childId: uuid("child_id").notNull(),
|
|
familyId: uuid("family_id").notNull(),
|
|
milestoneKey: text("milestone_key").notNull(),
|
|
achievedAt: date("achieved_at").notNull(),
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
},
|
|
(table) => [
|
|
index("milestone_child_idx").on(table.childId),
|
|
uniqueIndex("milestone_achievements_child_milestone_unique").on(
|
|
table.childId,
|
|
table.milestoneKey
|
|
),
|
|
]
|
|
);
|
|
|
|
// Type exports
|
|
export type Feed = typeof feeds.$inferSelect;
|
|
export type DiaperLog = typeof diapersLogs.$inferSelect;
|
|
export type Sleep = typeof sleeps.$inferSelect;
|
|
export type Vaccination = typeof vaccinations.$inferSelect;
|
|
export type Growth = typeof growth.$inferSelect;
|
|
export type Medication = typeof medications.$inferSelect;
|
|
export type MilestoneAchievement = typeof milestoneAchievements.$inferSelect;
|