16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-foundation | 01 | execute | 1 |
|
true |
|
|
|
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.
<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.mdsrc/lib/db/schema.ts will export:
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:
export const { handlers, auth, signIn, signOut } = NextAuth({ ... });
src/lib/db/index.ts will export:
export const db = drizzle({ client: sql, schema });
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:
# 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:
"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
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:
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:
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):
# 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
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):
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
# 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))"
<success_criteria>
- 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...") </success_criteria>
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