Files

8.9 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
03-agent-portal-shell 01 execute 1
teressa-copeland-homes/src/lib/db/schema.ts
teressa-copeland-homes/middleware.ts
teressa-copeland-homes/src/lib/auth.config.ts
teressa-copeland-homes/src/app/agent/(protected)/dashboard/page.tsx
teressa-copeland-homes/drizzle/0001_clients_documents.sql
true
CLIENT-01
CLIENT-02
CLIENT-03
DASH-01
DASH-02
truths artifacts key_links
Running `npm run db:generate && npm run db:migrate` produces a migration that creates the `clients` and `documents` tables with no errors
Visiting /portal/dashboard while unauthenticated redirects to /agent/login — not a 404 or blank page
After login, agent is redirected to /portal/dashboard (not /agent/dashboard)
The document_status PostgreSQL enum exists with values Draft, Sent, Viewed, Signed
path provides contains
teressa-copeland-homes/src/lib/db/schema.ts clients and documents table definitions + documentStatusEnum pgTable("clients"
path provides contains
teressa-copeland-homes/middleware.ts Route protection for /portal/:path* /portal/:path*
path provides contains
teressa-copeland-homes/src/lib/auth.config.ts Post-login redirect to /portal/dashboard /portal/dashboard
from to via pattern
middleware.ts matcher /portal/:path* routes matcher array portal.*path
from to via pattern
auth.config.ts authorized callback session check for /portal routes nextUrl.pathname.startsWith('/portal') startsWith.*portal
Add the `clients` and `documents` stub tables to the Drizzle schema, generate and run the migration, and update the middleware + auth config so all `/portal/` routes are protected and post-login redirect lands on `/portal/dashboard`.

Purpose: Every subsequent Phase 3 plan depends on these two tables and on the /portal route prefix being protected. This plan lays the data and routing foundations. Output: Working Drizzle migration for clients + documents tables; middleware protecting /portal/*; post-login redirect updated.

<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/03-agent-portal-shell/03-CONTEXT.md @.planning/phases/03-agent-portal-shell/03-RESEARCH.md

Existing users table (for reference pattern):

import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
// users table already exists — do NOT redefine it

// ADD below the existing users table:
export const clients = pgTable("clients", {
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
  name: text("name").notNull(),
  email: text("email").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

export const documentStatusEnum = pgEnum("document_status", [
  "Draft", "Sent", "Viewed", "Signed"
]);

export const documents = pgTable("documents", {
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
  name: text("name").notNull(),
  clientId: text("client_id").notNull().references(() => clients.id, { onDelete: "cascade" }),
  status: documentStatusEnum("status").notNull().default("Draft"),
  sentAt: timestamp("sent_at"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

Existing middleware.ts pattern (matcher array to extend):

export const config = {
  matcher: ["/agent/:path*"],  // ADD "/portal/:path*" here
};

Existing auth.config.ts authorized callback (redirect to update):

// Change: redirect(new URL("/agent/dashboard", nextUrl))
// To:     redirect(new URL("/portal/dashboard", nextUrl))
// Also add: nextUrl.pathname.startsWith("/portal") to protected routes check
Task 1: Extend Drizzle schema with clients and documents tables teressa-copeland-homes/src/lib/db/schema.ts Read the current schema.ts first. Then add, below the existing users table definition:
  1. Import pgEnum from drizzle-orm/pg-core (add to existing import).
  2. Export documentStatusEnum using pgEnum("document_status", ["Draft", "Sent", "Viewed", "Signed"]).
  3. Export clients table: id (text PK with crypto.randomUUID), name (text not null), email (text not null), createdAt (timestamp defaultNow not null), updatedAt (timestamp defaultNow not null).
  4. Export documents table (stub — no PDF content yet): id (text PK with crypto.randomUUID), name (text not null), clientId (text not null, FK to clients.id with onDelete: "cascade"), status (documentStatusEnum column default "Draft" not null), sentAt (timestamp nullable), createdAt (timestamp defaultNow not null).

CRITICAL: Export the enum BEFORE the documents table (it is referenced by the documents table column). Export both tables so they are importable by other modules.

Then generate and run the migration:

cd teressa-copeland-homes && npm run db:generate && npm run db:migrate

The migration SQL file will be created automatically in the drizzle/ directory. Do NOT hand-write SQL.

PITFALL: pgEnum must be exported AND referenced by a table column or drizzle-kit generate may omit it. Confirm the generated SQL contains CREATE TYPE document_status AS ENUM. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate 2>&1 | tail -20 Migration runs without error. clients and documents tables exist in the database. document_status enum exists in PostgreSQL. schema.ts exports clients, documents, and documentStatusEnum.

Task 2: Update middleware and auth config to protect /portal routes teressa-copeland-homes/middleware.ts teressa-copeland-homes/src/lib/auth.config.ts teressa-copeland-homes/src/app/agent/(protected)/dashboard/page.tsx **middleware.ts**: Read the file. Change the matcher to include both `/agent/:path*` and `/portal/:path*`: ```typescript export const config = { matcher: ["/agent/:path*", "/portal/:path*"], }; ```

auth.config.ts: Read the file. Make two changes:

  1. In the authorized callback, extend the protected route check to also cover /portal routes:
    // Add alongside the existing /agent check:
    if (nextUrl.pathname.startsWith("/portal")) {
      if (!isLoggedIn) return Response.redirect(new URL("/agent/login", nextUrl));
    }
    
  2. Change the post-login redirect (where a logged-in user visiting the login page is sent) from /agent/dashboard to /portal/dashboard:
    // Change: new URL("/agent/dashboard", nextUrl)
    // To:     new URL("/portal/dashboard", nextUrl)
    

PITFALL (from RESEARCH.md): The authorized callback in Auth.js v5 beta may handle the redirect differently than a plain middleware — read the existing callback logic carefully and mirror the existing /agent protection pattern rather than rewriting it.

src/app/agent/(protected)/dashboard/page.tsx: Replace the dashboard stub with a redirect to /portal/dashboard. Import redirect from next/navigation and call redirect("/portal/dashboard") as the only content. This ensures any old link to /agent/dashboard silently forwards to the new location. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 TypeScript compiles with zero errors. middleware.ts matcher includes /portal/:path*. auth.config.ts post-login redirect is /portal/dashboard. /agent/dashboard/page.tsx redirects to /portal/dashboard.

1. `npm run db:generate && npm run db:migrate` completes without error 2. `npx tsc --noEmit` produces zero TypeScript errors 3. schema.ts contains exports for `clients`, `documents`, and `documentStatusEnum` 4. middleware.ts matcher array contains `"/portal/:path*"` 5. auth.config.ts contains `startsWith("/portal")` in protected route check 6. auth.config.ts post-login redirect is `/portal/dashboard`

<success_criteria>

  • Drizzle migration creates clients and documents tables and document_status enum in PostgreSQL
  • All /portal/ routes require authentication (middleware protects them)
  • After login, agent is redirected to /portal/dashboard
  • TypeScript compiles cleanly </success_criteria>
After completion, create `.planning/phases/03-agent-portal-shell/03-01-SUMMARY.md`