feat: appointment confirmation and cancellation (GH #98) #104
Reference in New Issue
Block a user
Delete Branch "feat/appointment-confirmation-gh98"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #98
Summary
Implements full customer appointment confirmation and cancellation flow — the #1 request for reducing no-shows.
What Changed
Database
0013_appointment_confirmation.sql: addsconfirmation_status(pending/confirmed/cancelled),confirmed_at,cancelled_at,confirmation_tokento appointments table with partial index on tokenschema.ts,factories.ts,types/index.tsupdated with new fieldsAPI — Public tokenized endpoints (no auth)
GET /api/book/confirm/:token— confirm appointment via email link; redirects to/booking/confirmedor/booking/error; idempotentGET /api/book/cancel/:token— cancel via email link; single-use (token nullified after use); redirects to/booking/cancelledor/booking/errorAPI — Portal/staff endpoints (behind auth)
POST /api/appointments/:id/confirm— confirm from portal; idempotent; 409 if already customer-cancelledPOST /api/appointments/:id/cancel— cancel from portal; nullifies token; 409 if already cancelledEmail
buildReminderEmailnow accepts an optionalconfirmationTokenand renders Confirm Appointment and Cancel Appointment CTA buttons in HTML, plus plain-text fallback URLsReminder service
confirmationTokenon the appointment (if none exists) immediately before sending a reminder email, so every reminder includes action linksFrontend
✓ confirmed/✗ cust. cancelledstatus badges/booking/confirmed,/booking/cancelled,/booking/errorredirect pages added as public routes (no portal chrome)Tests
23 new unit tests in
confirmation.test.tscovering:Full suite: 142/142 tests passing, typecheck clean.
Test Plan
/booking/confirmed, DB showsconfirmation_status=confirmed/booking/cancelled, token nullified, second click →/booking/error/booking/errorPOST /api/appointments/:id/confirmand/cancelwork correctlypnpm typecheckpassespnpm test142/142cc @cpfarhood
CTO Technical Review — Architecture Aligned, Lint Fix Needed
I've reviewed the full diff against the GRO-144 architecture plan. Implementation is correct across all 11 files.
Architecture Alignment: 10/10
All GRO-144 requirements are met:
crypto.randomBytes(32).toString("hex")(64 hex chars, cryptographically secure)GET /api/book/confirm/:tokenandGET /api/book/cancel/:tokenwith redirect flowPOST /api/appointments/:id/confirmandPOST /api/appointments/:id/cancelwith 409 conflict handling✓ confirmed/✗ cust. cancelledbadges/booking/confirmed,/booking/cancelled,/booking/errorSecurity: Solid
CI Blocker: Lint Error
apps/api/src/services/email.ts:102—baseUrlis assigned but never used. Remove the line:The
apiUrlvariable (line below) is the one actually used for building confirm/cancel links.Tests: 23 new, 142 total — all passing
Good coverage of edge cases: idempotent confirm, single-use cancel, expired tokens, 404/409 responses, token format validation, email conditional rendering.
Waiting for QA (Scrubs) review before CTO approval per workflow policy.
CTO Approval ✓
Architecture, security, and code quality look good.
Reviewed:
Minor note (non-blocking):
reminders.tsline ~115 duplicates token generation (randomBytes(32).toString("hex")) instead of importinggenerateConfirmationTokenfromappointments.ts. Consider consolidating in a follow-up.QA approved, CI green. Ready for CEO merge.
Product Scope Review — Approved
This PR matches the spec in #98 exactly. No scope creep. Every acceptance criterion is met:
This completes the P1 build order:
All three P1 features specced, built, reviewed, and ready for merge. The Groom Book MVP feature set is complete.
Ready for CEO merge.