Files

5.9 KiB
Raw Permalink Blame History

Phase 18: Template Schema and CRUD API - Context

Gathered: 2026-04-06 Status: Ready for planning

## Phase Boundary

Schema migration + CRUD API only. No UI, no template editor, no apply-to-document logic. Those are Phases 19 and 20.

Deliverables:

  1. Drizzle migration adding document_templates table
  2. GET /api/templates — list active templates (archivedAt IS NULL)
  3. POST /api/templates — create template (pick a form from the library)
  4. PATCH /api/templates/[id] — rename template
  5. DELETE /api/templates/[id] — soft-delete (set archivedAt)
## Implementation Decisions

Schema Design

  • D-01: New table document_templates — separate from form_templates. The form_templates table is a read-only seeded catalog. document_templates is agent-authored.

  • D-02: Table columns:

    document_templates:
      id               TEXT PRIMARY KEY (crypto.randomUUID())
      name             TEXT NOT NULL
      formTemplateId   TEXT NOT NULL REFERENCES form_templates(id)
      signatureFields  JSONB NULL  — SignatureFieldData[] with roles in signerEmail slot, hints in hint slot
      archivedAt       TIMESTAMP NULL  — soft-delete; NULL = active
      createdAt        TIMESTAMP NOT NULL DEFAULT NOW()
      updatedAt        TIMESTAMP NOT NULL DEFAULT NOW()
    
  • D-03: NO separate signerRoles or textHints columns. Both are already embedded per-field in signatureFields:

    • Role labels stored in field.signerEmail (e.g. "Buyer", "Seller")
    • Text hints stored in field.hint (already on SignatureFieldData)
    • No duplication, no sync issues.
  • D-04: signatureFields is nullable on creation (template starts empty, editor fills it in Phase 19). Default NULL.

  • D-05: updatedAt must be set explicitly in every UPDATE query (no DB trigger). Follow existing pattern from clients and formTemplates tables.

  • D-06: NO ON DELETE CASCADE on the formTemplateId FK. If a form is removed from the library, templates referencing it should remain (archivedAt pattern handles cleanup).

Soft-Delete

  • D-07: Deletion sets archivedAt = NOW(). Hard delete is never performed.
  • D-08: All list queries filter WHERE archivedAt IS NULL. Archived templates are invisible to the agent.
  • D-09: Archived templates are NOT restored. If agent needs the template back, they create a new one.

API Structure

  • D-10: Routes at /api/templates (top-level, matching /api/documents pattern):

    • GET /api/templates — returns active templates with: id, name, formTemplateId, formName (joined), fieldCount (derived from signatureFields length), createdAt, updatedAt
    • POST /api/templates — body: { name: string; formTemplateId: string } — creates with empty signatureFields
    • PATCH /api/templates/[id] — body: { name?: string; signatureFields?: SignatureFieldData[] } — partial update
    • DELETE /api/templates/[id] — sets archivedAt = NOW()
  • D-11: All routes auth-gated (agent session required). Same auth() guard as all other portal API routes.

  • D-12: fieldCount on GET list is derived server-side: (signatureFields ?? []).length. Not stored in DB.

Migration

  • D-13: Use existing Drizzle pattern: edit schema.ts, run drizzle-kit generatedrizzle/0012_*.sql, commit both. Next migration after 0011_common_mystique.sql.

Claude's Discretion

  • Exact migration filename (Drizzle generates automatically)
  • Whether to include formTemplate name JOIN in the list query (recommended — avoids extra fetches in UI)

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Schema (existing patterns to follow)

  • teressa-copeland-homes/src/lib/db/schema.ts — existing tables; document_templates goes here; follow clients table pattern for updatedAt
  • teressa-copeland-homes/drizzle/ — existing migrations 00000011; new migration is 0012
  • teressa-copeland-homes/drizzle.config.ts — migration config

Existing API patterns

  • teressa-copeland-homes/src/app/api/documents/route.ts — POST documents pattern to follow
  • teressa-copeland-homes/src/app/api/documents/[id]/route.ts — PATCH pattern to follow

Research

  • .planning/research/ARCHITECTURE.md — schema decision reasoning, build order
  • .planning/research/PITFALLS.md — soft-delete rationale, FK constraint notes

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • auth() from @/lib/auth — all existing API routes use this for session guard; follow same pattern
  • db from @/lib/db — Drizzle client; import and use directly
  • SignatureFieldData interface — already in schema.ts; signatureFields column types to SignatureFieldData[]
  • formTemplates table — JOIN target for getting form name in list query

Established Patterns

  • crypto.randomUUID() via $defaultFn for IDs — all existing tables use this pattern
  • timestamp("updated_at").defaultNow().notNull() — existing pattern; updated explicitly in every PATCH
  • jsonb("column").$type<T>() — existing JSONB pattern (signatureFields, signers, contacts)
  • text("foreign_key_id").notNull().references(() => otherTable.id) — FK pattern

Integration Points

  • formTemplates table (existing) — document_templates.formTemplateId FKs to this
  • Phase 19 will add the template editor UI that PATCHes signatureFields
  • Phase 20 will add the apply-template endpoint (POST to create document from template)

</code_context>

## Specific Ideas
  • fieldCount in GET /api/templates response: (t.signatureFields?.length ?? 0) — computed at query time, no extra DB column
  • PATCH route handles both rename (name) AND field save (Phase 19 calls it with signatureFields) — one route, two use cases
## Deferred Ideas

None — discussion stayed within phase scope.


Phase: 18-template-schema-and-crud-api Context gathered: 2026-04-06