feat: add Drizzle config and auth/family schema
This commit is contained in:
parent
7e2cb39d9b
commit
7098339200
5 changed files with 182 additions and 0 deletions
12
drizzle.config.ts
Normal file
12
drizzle.config.ts
Normal 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
17
src/db/index.ts
Normal 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
49
src/db/schema/auth.ts
Normal 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
102
src/db/schema/family.ts
Normal 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
2
src/db/schema/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./auth";
|
||||
export * from "./family";
|
||||
Loading…
Add table
Reference in a new issue