Add Drizzle + Postgres setup (task 03)

drizzle-orm + postgres driver + drizzle-kit wired up. Core schema declares
users, households, and household_members. docker-compose.dev.yaml runs
Postgres 16 locally. Initial migration generated and verified applied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-06 01:21:40 -05:00
co-authored by Claude Sonnet 4.6
parent 09d6adbf18
commit 37977568ed
8 changed files with 1036 additions and 2 deletions
+2 -1
View File
@@ -6,10 +6,11 @@ Living progress tracker. Update at the end of each task. The canonical brief is
- **01 — Repo init & tooling** (commit `b89690a`). pnpm 10 + TS strict + ESLint flat + Prettier. All acceptance criteria green. - **01 — Repo init & tooling** (commit `b89690a`). pnpm 10 + TS strict + ESLint flat + Prettier. All acceptance criteria green.
- **02 — Next.js app skeleton**. Next.js 15 + React 19 + Tailwind v4 + shadcn/ui (button, card, input, dialog). `pnpm dev` serves placeholder, `pnpm build` produces `.next/standalone/`, `pnpm lint` clean. Added `.npmrc` with `node-linker=hoisted` for Windows symlink compatibility. - **02 — Next.js app skeleton**. Next.js 15 + React 19 + Tailwind v4 + shadcn/ui (button, card, input, dialog). `pnpm dev` serves placeholder, `pnpm build` produces `.next/standalone/`, `pnpm lint` clean. Added `.npmrc` with `node-linker=hoisted` for Windows symlink compatibility.
- **03 — Drizzle + Postgres setup**. drizzle-orm + postgres driver + drizzle-kit wired up. `src/modules/_core/schema.ts` declares `users`, `households`, `household_members`. `docker-compose.dev.yaml` starts Postgres 16. `drizzle/0000_silent_magma.sql` generated and applied. `tsc --noEmit` passes.
## Next up ## Next up
- **03Drizzle + Postgres setup** ([brief](docs/tasks/03-drizzle-postgres.md)). Phase 1 is sequential through 08. - **04Module loader** ([brief](docs/tasks/04-module-loader.md)). Phase 1 is sequential through 08.
## Phase 1 remaining ## Phase 1 remaining
+15
View File
@@ -0,0 +1,15 @@
services:
famapp-db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: famapp
POSTGRES_USER: famapp
POSTGRES_PASSWORD: famapp
ports:
- "5432:5432"
volumes:
- famapp-db-data:/var/lib/postgresql/data
volumes:
famapp-db-data:
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./src/modules/**/schema.ts",
out: "./drizzle",
dbCredentials: {
url: process.env["DATABASE_URL"]!,
},
});
+24
View File
@@ -0,0 +1,24 @@
CREATE TYPE "public"."household_member_role" AS ENUM('owner', 'member');--> statement-breakpoint
CREATE TABLE "household_members" (
"household_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"role" "household_member_role" DEFAULT 'member' NOT NULL,
CONSTRAINT "household_members_household_id_user_id_pk" PRIMARY KEY("household_id","user_id")
);
--> statement-breakpoint
CREATE TABLE "households" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"email" varchar(255) NOT NULL,
"display_name" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "household_members" ADD CONSTRAINT "household_members_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "household_members" ADD CONSTRAINT "household_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
+7 -1
View File
@@ -15,7 +15,10 @@
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.5", "@eslint/eslintrc": "^3.3.5",
@@ -24,6 +27,7 @@
"@types/node": "^22.9.0", "@types/node": "^22.9.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"drizzle-kit": "^0.31.10",
"eslint": "^9.15.0", "eslint": "^9.15.0",
"eslint-config-next": "^16.2.4", "eslint-config-next": "^16.2.4",
"globals": "^15.12.0", "globals": "^15.12.0",
@@ -36,8 +40,10 @@
"@base-ui/react": "^1.4.1", "@base-ui/react": "^1.4.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"drizzle-orm": "^0.45.2",
"lucide-react": "^1.14.0", "lucide-react": "^1.14.0",
"next": "^15.5.15", "next": "^15.5.15",
"postgres": "^3.4.9",
"react": "^19.2.5", "react": "^19.2.5",
"react-dom": "^19.2.5", "react-dom": "^19.2.5",
"shadcn": "^4.7.0", "shadcn": "^4.7.0",
+942
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const client = postgres(process.env["DATABASE_URL"]!);
export const db = drizzle(client);
+30
View File
@@ -0,0 +1,30 @@
import { pgEnum, pgTable, primaryKey, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
export const roleEnum = pgEnum("household_member_role", ["owner", "member"]);
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: varchar("email", { length: 255 }).notNull().unique(),
displayName: varchar("display_name", { length: 255 }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
});
export const households = pgTable("households", {
id: uuid("id").primaryKey().defaultRandom(),
name: varchar("name", { length: 255 }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
});
export const householdMembers = pgTable(
"household_members",
{
householdId: uuid("household_id")
.notNull()
.references(() => households.id, { onDelete: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
role: roleEnum("role").notNull().default("member"),
},
(t) => [primaryKey({ columns: [t.householdId, t.userId] })],
);