feat: add Drizzle config and auth/family schema

This commit is contained in:
Manohar Gupta 2026-05-10 04:08:39 +05:30
parent 7e2cb39d9b
commit 7098339200
5 changed files with 182 additions and 0 deletions

12
drizzle.config.ts Normal file
View file

@ -0,0 +1,12 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema/*",
dialect: "postgresql",
out: "./drizzle",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});

17
src/db/index.ts Normal file
View file

@ -0,0 +1,17 @@
import { encodeBase64 } from "crypto";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const connectionString = process.env.DATABASE_URL!;
const queryClient = postgres(connectionString, {
max: 10,
idle_timeout: 20,
max_lifetime: 60 * 30,
});
export const db = drizzle(queryClient, { schema });
export const sql = queryClient;
export type Database = typeof db;

49
src/db/schema/auth.ts Normal file
View file

@ -0,0 +1,49 @@
import { pgTable, text, timestamp, uuid, uniqueIndex } from "drizzle-orm/pg-core";
// Users table
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name"),
email: text("email").notNull().unique(),
emailVerified: timestamp("email_verified"),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Accounts table (for OAuth providers)
export const accounts = pgTable("accounts", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id").notNull(),
type: text("type").notNull(),
provider: text("provider").notNull(),
providerAccountId: text("provider_account_id").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: timestamp("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
}, (table) => [uniqueIndex("accounts_provider_idx").on(table.provider, table.providerAccountId)]);
// Sessions table
export const sessions = pgTable("sessions", {
id: uuid("id").primaryKey().defaultRandom(),
sessionToken: text("session_token").notNull().unique(),
userId: uuid("user_id").notNull(),
expires: timestamp("expires").notNull(),
});
// Verification tokens (for magic links)
export const verificationTokens = pgTable("verification_tokens", {
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires").notNull(),
}, (table) => [uniqueIndex("verification_tokens_idx").on(table.identifier, table.token)]);
// Type exports
export type User = typeof users.$inferSelect;
export type Account = typeof accounts.$inferSelect;
export type Session = typeof sessions.$inferSelect;
export type VerificationToken = typeof verificationTokens.$inferSelect;

102
src/db/schema/family.ts Normal file
View file

@ -0,0 +1,102 @@
import {
pgTable,
text,
timestamp,
uuid,
pgEnum,
uniqueIndex,
index,
jsonb,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
// Enums
export const memberRoleEnum = pgEnum("member_role", ["admin", "caregiver", "viewer"]);
export const childSexEnum = pgEnum("child_sex", ["male", "female", "other"]);
export const childStageEnum = pgEnum("child_stage", [
"newborn",
"infant",
"solids_start",
"toddler_early",
"toddler_late",
"preschool",
]);
// Families table
export const families = pgTable("families", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull().default("The Gupta Family"),
pediatricianPhone: text("pediatrician_phone"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Family members table
export const familyMembers = pgTable(
"family_members",
{
id: uuid("id").primaryKey().defaultRandom(),
familyId: uuid("family_id").notNull(),
userId: uuid("user_id").notNull(),
role: memberRoleEnum("role").notNull().default("caregiver"),
displayName: text("display_name").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
},
(table) => [uniqueIndex("family_user_unique").on(table.familyId, table.userId)]
);
// Children table
export const children = pgTable(
"children",
{
id: uuid("id").primaryKey().defaultRandom(),
familyId: uuid("family_id").notNull(),
name: text("name").notNull(),
birthDate: timestamp("birth_date").notNull(),
sex: childSexEnum("sex").notNull(),
currentStage: childStageEnum("current_stage"),
stageOverrides: jsonb("stage_overrides").$type<Record<string, unknown>>().default({}),
profilePhotoUrl: text("profile_photo_url"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(table) => [index("children_family_idx").on(table.familyId)]
);
// Family invites table
export const familyInvites = pgTable(
"family_invites",
{
id: uuid("id").primaryKey().defaultRandom(),
familyId: uuid("family_id").notNull(),
email: text("email").notNull(),
role: memberRoleEnum("role").notNull(),
displayName: text("display_name").notNull(),
token: text("token").notNull().unique(),
expiresAt: timestamp("expires_at").notNull(),
acceptedAt: timestamp("accepted_at"),
createdAt: timestamp("created_at").defaultNow().notNull(),
},
(table) => [uniqueIndex("invite_token_idx").on(table.token)]
);
// Type exports
export type Family = typeof families.$inferSelect;
export type FamilyMember = typeof familyMembers.$inferSelect;
export type Child = typeof children.$inferSelect;
export type FamilyInvite = typeof familyInvites.$inferSelect;
// Helper function for deriving stage
export function deriveStage(birthDate: Date): (typeof childStageEnum)["enumValues"][number] {
const now = new Date();
const months = Math.floor(
(now.getTime() - birthDate.getTime()) / (1000 * 60 * 60 * 24 * 30)
);
if (months < 3) return "newborn";
if (months < 6) return "infant";
if (months < 12) return "solids_start";
if (months < 24) return "toddler_early";
if (months < 36) return "toddler_late";
return "preschool";
}

2
src/db/schema/index.ts Normal file
View file

@ -0,0 +1,2 @@
export * from "./auth";
export * from "./family";