--- phase: 01-foundation plan: 01 type: execute wave: 1 depends_on: [] files_modified: - package.json - tsconfig.json - next.config.ts - tailwind.config.ts - drizzle.config.ts - src/lib/db/index.ts - src/lib/db/schema.ts - src/lib/auth.ts - middleware.ts - scripts/seed.ts - drizzle/ autonomous: true requirements: - AUTH-01 - AUTH-02 - AUTH-03 user_setup: - service: neon why: "PostgreSQL database for Drizzle ORM" env_vars: - name: DATABASE_URL source: "Neon console → your project → Connection string → Pooled connection (use the ?sslmode=require URL)" dashboard_config: - task: "Create a new Neon project named 'teressa-copeland-homes' in the us-east-1 region" location: "https://console.neon.tech/app/new" - service: vercel-blob why: "File/blob storage for signed PDFs and templates (provisioned now, used in Phase 5+)" env_vars: - name: BLOB_READ_WRITE_TOKEN source: "Vercel Dashboard → your project → Storage → Blob → Create Store → token appears after creation" dashboard_config: - task: "Create a Blob store named 'teressa-copeland-homes-blob' linked to the Vercel project" location: "Vercel Dashboard → Storage tab" - service: vercel why: "Deployment host and environment variable manager" env_vars: - name: AUTH_SECRET source: "Generate with: npx auth secret — copies to clipboard; paste into Vercel dashboard" - name: AGENT_EMAIL source: "Teressa's login email address (e.g. teressa@teressacopelandhomes.com)" - name: AGENT_PASSWORD source: "A strong password for Teressa's portal login" dashboard_config: - task: "Create a new Vercel project linked to the Git repo; set all 5 env vars (DATABASE_URL, BLOB_READ_WRITE_TOKEN, AUTH_SECRET, AGENT_EMAIL, AGENT_PASSWORD) in the project's Environment Variables settings" location: "https://vercel.com/new" must_haves: truths: - "Next.js project builds without TypeScript errors (npm run build succeeds)" - "Drizzle schema generates a valid SQL migration file for the users table" - "Auth.js configuration exports handlers, auth, signIn, signOut without import errors" - "Middleware.ts exists at project root (not inside src/) and imports from @/lib/auth" - "Seed script creates Teressa's hashed-password account in the database when run" - "Vercel Blob store is provisioned and BLOB_READ_WRITE_TOKEN is available" artifacts: - path: "src/lib/db/schema.ts" provides: "users table definition (id, email, password_hash, created_at)" exports: ["users"] - path: "src/lib/db/index.ts" provides: "Drizzle client singleton using Neon HTTP driver" exports: ["db"] - path: "src/lib/auth.ts" provides: "Auth.js v5 configuration — JWT strategy, Credentials provider, 7-day rolling session" exports: ["handlers", "auth", "signIn", "signOut"] - path: "middleware.ts" provides: "Edge middleware that redirects unauthenticated /agent/* requests to /agent/login" - path: "scripts/seed.ts" provides: "One-time seed script that creates Teressa's account from AGENT_EMAIL + AGENT_PASSWORD env vars" - path: "drizzle.config.ts" provides: "Drizzle Kit configuration pointing to schema.ts and /drizzle output directory" - path: "drizzle/" provides: "Generated SQL migration files committed to repo" key_links: - from: "src/lib/auth.ts" to: "src/lib/db" via: "Credentials authorize() callback calls db.select(users)" pattern: "db\\.select.*from.*users" - from: "middleware.ts" to: "src/lib/auth.ts" via: "import { auth } from @/lib/auth" pattern: "import.*auth.*from.*@/lib/auth" - from: "scripts/seed.ts" to: "src/lib/db/schema.ts" via: "import { users } from ../src/lib/db/schema" pattern: "import.*users.*from" --- Scaffold the Next.js 15 project, install all Phase 1 dependencies, define the database schema, configure Drizzle ORM, write the Auth.js v5 configuration with JWT strategy + Credentials provider, create the route-protection middleware, and write the database seed script. This plan produces all the non-UI foundation contracts that Plans 02 and 03 build on top of. Purpose: Every subsequent plan in this phase and every future phase depends on these files. Getting the contracts right here prevents cascading rework. Output: Scaffolded project with auth.ts, schema.ts, db/index.ts, middleware.ts, seed.ts, and drizzle migration committed. @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.md src/lib/db/schema.ts will export: ```typescript export const users = pgTable("users", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), email: text("email").notNull().unique(), passwordHash: text("password_hash").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), }); ``` src/lib/auth.ts will export: ```typescript export const { handlers, auth, signIn, signOut } = NextAuth({ ... }); ``` src/lib/db/index.ts will export: ```typescript export const db = drizzle({ client: sql, schema }); ``` Task 1: Scaffold Next.js 15 project and install all dependencies package.json, tsconfig.json, next.config.ts, tailwind.config.ts, .gitignore Run the following scaffold command from the parent directory of where the project should live. If the directory `/Users/ccopeland/temp/red/teressa-copeland-homes` already exists, skip scaffolding and install missing deps into the existing project. ```bash npx create-next-app@latest teressa-copeland-homes \ --typescript \ --tailwind \ --eslint \ --app \ --src-dir \ --import-alias "@/*" \ --no-git ``` After scaffolding (cd into the project directory), install Phase 1 dependencies: ```bash # Auth + DB + Blob npm install next-auth@beta npm install drizzle-orm @neondatabase/serverless npm install bcryptjs @vercel/blob zod npm install -D drizzle-kit tsx @types/bcryptjs dotenv ``` After install, note the exact installed `next-auth` version with `npm ls next-auth` and pin it in package.json (replace `"next-auth": "beta"` with the exact version string like `"next-auth": "5.0.0-beta.25"`). Add these npm scripts to package.json: ```json "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:seed": "tsx scripts/seed.ts", "db:studio": "drizzle-kit studio" ``` Ensure `.gitignore` includes: ``` .env .env.local .env*.local ``` Do NOT create a `.env` file or `.env.local` file — all secrets go through Vercel dashboard per user decision. Confirm `npm run build` succeeds (will show only placeholder page errors, not import errors). npm run build passes with no TypeScript compilation errors; npm ls next-auth shows a pinned specific version (not "beta") Project directory exists, all dependencies installed, exact next-auth version pinned in package.json, npm run build succeeds Task 2: Define database schema, configure Drizzle Kit, and write seed script src/lib/db/schema.ts, src/lib/db/index.ts, drizzle.config.ts, scripts/seed.ts, drizzle/ (generated) Create `drizzle.config.ts` at the project root: ```typescript import "dotenv/config"; import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/lib/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL!, }, }); ``` Create `src/lib/db/schema.ts`: ```typescript import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; export const users = pgTable("users", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), email: text("email").notNull().unique(), passwordHash: text("password_hash").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), }); ``` Create `src/lib/db/index.ts`: ```typescript import { drizzle } from "drizzle-orm/neon-http"; import { neon } from "@neondatabase/serverless"; import * as schema from "./schema"; const sql = neon(process.env.DATABASE_URL!); export const db = drizzle({ client: sql, schema }); ``` Create `scripts/seed.ts`: ```typescript import "dotenv/config"; import { drizzle } from "drizzle-orm/neon-http"; import { neon } from "@neondatabase/serverless"; import { users } from "../src/lib/db/schema"; import bcrypt from "bcryptjs"; const sql = neon(process.env.DATABASE_URL!); const db = drizzle({ client: sql }); async function seed() { const email = process.env.AGENT_EMAIL; const password = process.env.AGENT_PASSWORD; if (!email || !password) { throw new Error("AGENT_EMAIL and AGENT_PASSWORD env vars are required"); } const passwordHash = await bcrypt.hash(password, 12); await db.insert(users).values({ email, passwordHash }).onConflictDoNothing(); console.log(`Seeded agent account: ${email}`); process.exit(0); } seed().catch((err) => { console.error(err); process.exit(1); }); ``` Generate migration files (requires DATABASE_URL to be set — if not yet available, generate schema only and note that `db:migrate` and `db:seed` run after user sets up Neon): ```bash # If DATABASE_URL is available locally (via `vercel env pull` after Vercel project creation): npm run db:generate # Creates drizzle/0000_*.sql migration file npm run db:migrate # Applies migration to Neon database npm run db:seed # Creates Teressa's account # If DATABASE_URL is NOT yet available: # Generate migration file only (does not need DB connection): npm run db:generate # Note in SUMMARY.md that db:migrate and db:seed must run after user sets up Neon ``` The generated `/drizzle` directory with SQL migration files MUST be committed to the repo (per user decision — version-controlled, auditable migrations). drizzle/ directory contains at least one .sql migration file; src/lib/db/schema.ts and index.ts export users and db without TypeScript errors (npx tsc --noEmit) Schema file exports users table, db/index.ts exports db singleton, drizzle.config.ts present, seed script present, migration SQL file exists in /drizzle and committed to git Task 3: Configure Auth.js v5 with JWT strategy and create route-protection middleware src/lib/auth.ts, src/app/api/auth/[...nextauth]/route.ts, middleware.ts Create `src/lib/auth.ts` — the single source of truth for authentication configuration: ```typescript import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { db } from "@/lib/db"; import { users } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; import bcrypt from "bcryptjs"; import { z } from "zod"; const loginSchema = z.object({ email: z.string().email(), password: z.string().min(1), }); export const { handlers, auth, signIn, signOut } = NextAuth({ session: { strategy: "jwt", maxAge: 7 * 24 * 60 * 60, // 7 days — rolling session per user decision updateAge: 24 * 60 * 60, // Refresh cookie once per day (not every request) }, pages: { signIn: "/agent/login", // Custom login page — per user decision }, providers: [ Credentials({ async authorize(credentials) { const parsed = loginSchema.safeParse(credentials); if (!parsed.success) return null; const [user] = await db .select() .from(users) .where(eq(users.email, parsed.data.email)) .limit(1); if (!user) return null; const passwordMatch = await bcrypt.compare( parsed.data.password, user.passwordHash ); if (!passwordMatch) return null; return { id: user.id, email: user.email }; }, }), ], callbacks: { jwt({ token, user }) { if (user) token.id = user.id; return token; }, session({ session, token }) { session.user.id = token.id as string; return session; }, }, }); ``` Create `src/app/api/auth/[...nextauth]/route.ts`: ```typescript import { handlers } from "@/lib/auth"; export const { GET, POST } = handlers; ``` Create `middleware.ts` at the PROJECT ROOT (not inside src/ — critical, middleware placed in src/ is silently ignored by Next.js): ```typescript import { auth } from "@/lib/auth"; import { NextResponse } from "next/server"; export default auth((req) => { const isLoggedIn = !!req.auth; const isAgentRoute = req.nextUrl.pathname.startsWith("/agent"); const isLoginPage = req.nextUrl.pathname === "/agent/login"; // Protect all /agent/* routes except the login page itself if (isAgentRoute && !isLoginPage && !isLoggedIn) { return NextResponse.redirect(new URL("/agent/login", req.nextUrl.origin)); } // Redirect already-authenticated users away from the login page if (isLoginPage && isLoggedIn) { return NextResponse.redirect(new URL("/agent/dashboard", req.nextUrl.origin)); } }); export const config = { matcher: [ // Run on /agent/* routes, excluding Next.js internals and static files "/((?!_next/static|_next/image|favicon.ico).*)", ], }; ``` Verify `middleware.ts` is at the same level as `package.json` — NOT inside `src/`. This is the single most common pitfall: middleware inside src/ is silently ignored and routes are unprotected. AUTH_SECRET env var must be set for Auth.js to function. The user will add this to Vercel dashboard. For local development, note this requirement in SUMMARY.md — `vercel env pull` will populate `.env.local` once the Vercel project is created. npx tsc --noEmit passes with no errors in auth.ts, middleware.ts, and route.ts; middleware.ts file exists at project root (same level as package.json), NOT inside src/ src/lib/auth.ts exports handlers, auth, signIn, signOut; API route handler exists at src/app/api/auth/[...nextauth]/route.ts; middleware.ts exists at project root with /agent/* protection logic Run these commands from the project root directory after completing all tasks: ```bash # TypeScript: zero errors across all created files npx tsc --noEmit # Build: confirms Next.js can compile the project npm run build # Migration file exists (committed to git) ls drizzle/*.sql # Middleware location (MUST be at root, not src/) ls middleware.ts && echo "OK" || echo "MISSING — check location" ls src/middleware.ts 2>/dev/null && echo "WRONG LOCATION" || echo "Correctly absent from src/" # Auth exports are importable (no circular deps or missing modules) node -e "import('./src/lib/auth.ts').then(() => console.log('auth.ts OK')).catch(e => console.error(e.message))" ``` - npm run build succeeds with no TypeScript errors - drizzle/ directory contains at least one committed SQL migration file - middleware.ts exists at project root, NOT inside src/ - src/lib/auth.ts exports handlers, auth, signIn, signOut (JWT strategy, 7-day maxAge, Credentials provider) - src/lib/db/schema.ts exports users table with id, email, passwordHash, createdAt columns - src/lib/db/index.ts exports db (Drizzle + Neon HTTP driver singleton) - src/app/api/auth/[...nextauth]/route.ts exports GET and POST - scripts/seed.ts reads AGENT_EMAIL and AGENT_PASSWORD from env and creates hashed user row - next-auth version is pinned to a specific beta version (not "beta" or "^5...") After completion, create `.planning/phases/01-foundation/01-01-SUMMARY.md` using the summary template. Include in the summary: - Exact next-auth beta version pinned - Whether db:migrate and db:seed were run successfully (or if they are pending user Neon setup) - Location of middleware.ts confirmed (root vs src/) - Any deviations from the planned patterns and why