111 lines
4.2 KiB
Markdown
111 lines
4.2 KiB
Markdown
|
|
---
|
||
|
|
phase: 15-multi-signer-backend
|
||
|
|
plan: 02
|
||
|
|
subsystem: api
|
||
|
|
tags: [multi-signer, signing, email, jwt, drizzle, nextjs]
|
||
|
|
|
||
|
|
# Dependency graph
|
||
|
|
requires:
|
||
|
|
- phase: 15-01
|
||
|
|
provides: createSigningToken with signerEmail param, DocumentSigner type, signingTokens.signerEmail column
|
||
|
|
- phase: 14-multi-signer-schema
|
||
|
|
provides: documents.signers JSONB column, DocumentSigner interface
|
||
|
|
provides:
|
||
|
|
- Multi-signer send route with one token per signer and parallel email dispatch
|
||
|
|
- Legacy single-signer fallback preserved when doc.signers is null/empty
|
||
|
|
- APP_BASE_URL used for all signing URLs (not NEXT_PUBLIC_BASE_URL)
|
||
|
|
- Audit event with metadata.signerEmail per signer in multi-signer path
|
||
|
|
affects: [15-03, 16-multi-signer-ui]
|
||
|
|
|
||
|
|
# Tech tracking
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- "Promise.all over signers array for parallel token creation and email dispatch"
|
||
|
|
- "Branch on doc.signers?.length > 0 for multi vs. legacy path"
|
||
|
|
- "APP_BASE_URL (server-side env) instead of NEXT_PUBLIC_BASE_URL for signing URLs"
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created: []
|
||
|
|
modified:
|
||
|
|
- teressa-copeland-homes/src/app/api/documents/[id]/send/route.ts
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "Kept Promise.all fail-fast behavior: if one signer email fails, entire send fails and agent retries (consistent with legacy behavior)"
|
||
|
|
- "clientName omitted for multi-signer emails (DocumentSigner has email+color only); email template handles gracefully"
|
||
|
|
- "Return NextResponse.json({ ok: true }) without expiresAt in multi-signer path (all tokens same 72h TTL anyway)"
|
||
|
|
|
||
|
|
patterns-established:
|
||
|
|
- "Multi-signer branch: doc.signers && (doc.signers as DocumentSigner[]).length > 0"
|
||
|
|
- "Legacy fallback: resolve clientId = doc.assignedClientId ?? doc.clientId, same as before"
|
||
|
|
|
||
|
|
requirements-completed: [MSIGN-05, MSIGN-06]
|
||
|
|
|
||
|
|
# Metrics
|
||
|
|
duration: 5min
|
||
|
|
completed: 2026-04-03
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 15 Plan 02: Multi-Signer Send Route Summary
|
||
|
|
|
||
|
|
**Send route rewritten to loop over doc.signers, create one JWT per signer with signerEmail, and dispatch all emails in parallel via Promise.all — with full legacy fallback when signers is null.**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** ~5 min
|
||
|
|
- **Started:** 2026-04-03T00:00:00Z
|
||
|
|
- **Completed:** 2026-04-03T00:05:00Z
|
||
|
|
- **Tasks:** 1
|
||
|
|
- **Files modified:** 1
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
|
||
|
|
- Multi-signer path: `doc.signers` loop calls `createSigningToken(doc.id, signer.email)` for each signer, dispatches all emails in parallel via `Promise.all`
|
||
|
|
- Legacy single-signer path preserved exactly: resolves `assignedClientId ?? clientId`, creates token without signerEmail, sends to client.name
|
||
|
|
- `NEXT_PUBLIC_BASE_URL` replaced with `APP_BASE_URL` (server-side env var) across both paths
|
||
|
|
- Per-signer audit events with `metadata.signerEmail` in multi-signer path
|
||
|
|
- `DocumentSigner` imported from schema for proper type casting
|
||
|
|
|
||
|
|
## Task Commits
|
||
|
|
|
||
|
|
1. **Task 1: Rewrite send route with multi-signer token loop and legacy fallback** - `7a04a4f` (feat)
|
||
|
|
|
||
|
|
**Plan metadata:** (pending)
|
||
|
|
|
||
|
|
## Files Created/Modified
|
||
|
|
|
||
|
|
- `teressa-copeland-homes/src/app/api/documents/[id]/send/route.ts` - Multi-signer token loop + legacy fallback, APP_BASE_URL, parallel email dispatch
|
||
|
|
|
||
|
|
## Decisions Made
|
||
|
|
|
||
|
|
- Kept `Promise.all` fail-fast: if one signer's email fails, entire send rolls back; agent retries. Consistent with existing single-signer pattern.
|
||
|
|
- Omitted `clientName` in multi-signer path — `DocumentSigner` only has `{ email, color }`, and the email template already handles undefined clientName gracefully.
|
||
|
|
- Returned `{ ok: true }` without `expiresAt` in multi-signer response (all tokens share the same 72h TTL).
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
None - plan executed exactly as written.
|
||
|
|
|
||
|
|
## Issues Encountered
|
||
|
|
|
||
|
|
None.
|
||
|
|
|
||
|
|
## User Setup Required
|
||
|
|
|
||
|
|
None — no new environment variables introduced. `APP_BASE_URL` was already referenced in Phase 15 context (Pitfall 5); update `.env` to add it if not already present.
|
||
|
|
|
||
|
|
## Next Phase Readiness
|
||
|
|
|
||
|
|
- Send route is ready; Plan 15-03 (GET/POST sign handler rewrites) can proceed
|
||
|
|
- `signingTokens.signerEmail` is now written at send time — GET handler can filter fields by it in 15-03
|
||
|
|
|
||
|
|
## Self-Check: PASSED
|
||
|
|
|
||
|
|
- `teressa-copeland-homes/src/app/api/documents/[id]/send/route.ts` — FOUND
|
||
|
|
- `.planning/phases/15-multi-signer-backend/15-02-SUMMARY.md` — FOUND
|
||
|
|
- Commit `7a04a4f` — FOUND
|
||
|
|
|
||
|
|
---
|
||
|
|
*Phase: 15-multi-signer-backend*
|
||
|
|
*Completed: 2026-04-03*
|