Audit History
Audit date: 2026-02-18 Status: All 32 findings resolved across 4 sprints
Summary
Section titled “Summary”| Severity | Findings | Resolved | Area |
|---|---|---|---|
| Critical | 8 | 8 | N+1 queries, SRP violations, missing transactions |
| Medium | 14 | 14 | Copy-paste, validation, schema drift |
| Minor | 10 | 10 | Config, naming, dead code |
Critical Findings
Section titled “Critical Findings”1. N+1 in listStudents()
Section titled “1. N+1 in listStudents()”Resolved: Replaced Promise.all per-student loop with Drizzle with relations prefetch. From N+1 queries to 1 query total.
2. SessionService — 828 lines, 13 responsibilities
Section titled “2. SessionService — 828 lines, 13 responsibilities”Mitigated: Credit logic centralized in SessionCreditHandler. Split into 3 services deferred as minor tech debt.
3. Credit logic dispersed in 6+ files
Section titled “3. Credit logic dispersed in 6+ files”Resolved: Created SessionCreditHandler with onReserve(), onCompleted(), onCanceled(), onNoShow(). All call sites migrated.
4. Missing transactions in multi-step operations
Section titled “4. Missing transactions in multi-step operations”Resolved: Added db.transaction() wrapping in confirmPurchase, bookLesson, scheduleManualLesson, updateStatus, autoComplete, addAttendees, completeAttendees, markNoShow.
5. Zero validation with as casts in profile routes
Section titled “5. Zero validation with as casts in profile routes”Resolved: Created 8 Zod schemas in @pinteach/shared. All 10 routes use .parse().
6. DashboardService.getSummary() — 8 sequential queries
Section titled “6. DashboardService.getSummary() — 8 sequential queries”Resolved: Consolidated 7 queries into 3 using CASE WHEN conditional aggregates and PostgreSQL FILTER.
7. throw new Error() instead of Errors helper
Section titled “7. throw new Error() instead of Errors helper”Resolved: Replaced 5x raw throws with Errors.notFound() etc.
8. Business logic in webhook route
Section titled “8. Business logic in webhook route”Resolved: Created StripeWebhookService. Route is now a thin switch.
Medium Findings (14)
Section titled “Medium Findings (14)”All resolved:
- products.lazy.tsx split into 3 components (1,364 → ~120 lines)
- ToggleSwitch extracted as reusable component (7 instances)
- Session cookie helper centralized (5 duplicates)
- PRODUCT_TYPES updated, FK constraints added
- Indexes added for teacherId columns
- React state-update-in-render anti-pattern fixed
- Google Calendar silent error logging added
- DevTools moved to devDependencies
- Validation duplications extracted to helpers
Minor Findings (10)
Section titled “Minor Findings (10)”All resolved:
- React version aligned (^19.2.4)
- Unused imports removed
- WhatsAppIcon extracted to components/icons
- Cascade deletes added to acceptance tables
- Dedicated bulkDeleteTagsSchema created
- Rate limiting added to verify-magic-link
- APP_URL defaults corrected
- process.env centralized to env helper
Remaining Tech Debt
Section titled “Remaining Tech Debt”- SessionService split into 3 services (deferred — complexity reduced by SessionCreditHandler)