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