feat: 10 quick wins — account deletion, health check, debug cleanup, UX polish, graceful shutdown

- backend/handlers/user/account.go: Wire DELETE /api/user/account to call
  anonymize_user() for registered users and delete_guest_user() for guests,
  with CardDAV contact cleanup
- backend/handlers/user/profile_test.go: Add TestAccount_DeleteGuest and
  enhance TestAccount_Delete to verify anonymization results
- backend/main.go: Add GET /api/health endpoint with DB ping and S3 status
  check; add HSTS and Referrer-Policy security headers; replace
  http.ListenAndServe with http.Server + graceful SIGTERM/SIGINT shutdown
- frontend/routes/+layout.svelte: Replace alert() with toast notifications
  for email verification flow
- frontend/routes/login/+page.svelte: Replace alert() with toast.info for
  social login prototype buttons
- frontend/booking/BookingFlow.svelte: Remove 2 console.log debug calls;
  add cancellation policy note in Step 3; add timezone policy comment
- frontend/ImageUpload.svelte: Comment out debug console.log
- init-scripts/init-script.sql: Add delete_guest_user() SQL function
- docs: Update README.md and Obsidian notes to reflect completed items
This commit is contained in:
2026-05-01 11:33:27 +01:00
parent 7b396b7a9d
commit bff86a6660
11 changed files with 673 additions and 435 deletions
+131 -70
View File
@@ -1,4 +1,4 @@
**Last Updated:** March 2026
**Last Updated:** May 2026
> **Status:** Work in Progress
---
@@ -47,30 +47,33 @@
- Services: name (100), price (>0), duration (1-480), patch test (0-168), age (0-100)
- Portfolio: tags/filters (256 char max), filter category validation, image ID pattern security
RM|#### Booking System
BV|- [x] `/api/bookings` - Full CRUD for authenticated users
TH|- [x] `/api/admin/bookings` - List, search, create for user, progress, confirm, cancel
RW|- [x] `/api/admin/bookings/search` - Search functionality
SX|- [x] `/api/admin/bookings/user/{user_id}` - User-specific bookings
WK|- [x] `/api/admin/bookings/{id}/progress` - Progress booking status
XH|- [x] `/api/admin/bookings/{id}/confirm` - Confirm booking
JN|- [x] `/api/admin/bookings/{id}/cancel` - Cancel booking
BR|- [x] **Admin edit booking** - PUT `/api/admin/bookings/{id}` to edit start time
- Blocks editing completed or cancelled bookings
- Checks for overlapping bookings
- Allows exceptional hours (with warning)
- Clears pending edit requests on edit
BR|- [x] **In-progress auto-infer** - Status auto-sets based on time (confirmed → in_progress → completed)
SM|- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [x] `/api/bookings` - Full CRUD for authenticated users
#### Booking System
- [x] `/api/bookings` - Full CRUD for authenticated users AND guest users (via `user_id` in payload, OptionalAuth middleware)
- [x] `/api/admin/bookings` - List, search, create for user, progress, confirm, cancel
- [x] `/api/admin/bookings/search` - Search functionality
- [x] `/api/admin/bookings/user/{user_id}` - User-specific bookings
- [x] `/api/admin/bookings/{id}/progress` - Progress booking status
- [x] `/api/admin/bookings/{id}/confirm` - Confirm booking
- [x] `/api/admin/bookings/{id}/cancel` - Cancel booking
- [x] **Admin edit booking** - PUT `/api/admin/bookings/{id}` to edit start time
- Blocks editing completed or cancelled bookings
- Checks for overlapping bookings
- Allows exceptional hours (with warning)
- Clears pending edit requests on edit
- [x] **In-progress auto-infer** - Status auto-sets based on time (confirmed → in_progress → completed)
- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [x] **Slot reservation system** - `POST /api/bookings/reserve` (public, OptionalAuth), `POST /api/admin/bookings/reserve` (admin)
- 4 TTL types: user=1h, anon=10min, walkin=5min, callin=1h
- Anonymous cap: 50 reservations per 10-minute rolling window
- Stored as `time_blockers` with `RESERVATION:*` description
- `CleanupOldReservations()` prunes expired entries
- [x] **Guest booking flow** - `POST /api/users/guest` creates disposable accounts
- Email uniqueness: partial unique index `WHERE account_role != 'guest'`
- If guest uses registered email → 409 "Please log in"
- Guest bookings bypass deposit and patch-test checks
- `AnonymizeStaleGuestAccounts()` scrubs PII 6 months after booking start_time
- [x] **Chi router fix** - Flattened `/bookings` sub-Route to explicit paths to prevent RequireAuth bleeding into OptionalAuth POST endpoints
- [x] **Reservation self-block fix** - `GetTimeBlockersInRange` excludes `RESERVATION:*` entries so overlap checks don't reject the user's own reservation
#### Admin Endpoints
- [x] `/api/admin/services` - Create, delete, list, toggle
@@ -108,6 +111,7 @@ SM|- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [x] 2+ unforgiven no-shows in 6 months = 3 deposits (blocks new bookings)
- [x] Deleted: `ForgiveNoShowsForUser()` function (now per-cancellation forgiveness)
- [x] Implementation complete in `bookings.go` (24h threshold, forgiveness logic) and `manage.go` (admin deposit enforcement)
- [x] **Deposit users must book ≥24h in advance** (changed from 48h); guests bypass this check
- [ ] Frontend display of deposits_required status to users
#### CalDAV Contact Sync
@@ -118,7 +122,7 @@ SM|- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [ ] Social auth (`handlers/auth/social.go` exists, not imported)
- [ ] Analytics (`handlers/admin/analytics.go` exists, not imported)
- [x] Portfolio/images - NOW WIRED: `/api/portfolio/images`, `/api/portfolio/tags`, `/api/portfolio/filters`, `/api/portfolio/images/{id}`
- [ ] Guest user endpoint (`/api/users/guest` - needed for walk-in bookings)
- [ ] **Guest user endpoint** - ✅ DONE: `POST /api/users/guest` creates disposable accounts, partial unique email index
#### Unit Tests & CI/CD
- [ ] Unit tests
@@ -162,7 +166,8 @@ SM|- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [x] Auth store with token refresh logic
- [x] **Admin booking flows** - Call-in and walk-in use `/api/services/eligible-for/{user_id}` for user-specific eligibility
- [x] **Manual patch test entry** - Admin can record patch test completion via User Details → Patch Test modal (for 2-minute walk-in patch tests)
- [ ] **Customer booking submit** - `submitBooking()` only logs, needs `POST /api/bookings`
- [x] **Customer booking submit** - `submitBooking()` creates guest account if unauthenticated, then POSTs to `/api/bookings`
- [ ] Remove debug `console.log` calls from BookingFlow.svelte
- [ ] Payment integration (Square placeholder)
#### API Integration
@@ -170,7 +175,7 @@ SM|- [x] **Auto-complete** - Bookings auto-complete when duration elapses
- [x] Working hours fetch from `/api/scheduling/working-hours`
- [x] Available hours fetch from `/api/scheduling/available-hours`
- [x] Admin bookings use `/api/admin/bookings`
- [ ] Guest user creation (`/api/users/guest` not implemented)
- [x] Guest user creation (`/api/users/guest`) — creates disposable accounts, enforces email uniqueness for registered users only
---
@@ -263,24 +268,34 @@ go test -v -run "TestBooking" ./...
- Test tokens use fixed secret: `test-secret-key-for-testing-only`
- Fixtures auto-generate unique emails to avoid conflicts
### Recent Testing Updates (March 2026)
### Recent Testing Updates (May 2026)
Since the February 2026 update, extensive testing has been conducted which uncovered and fixed several issues:
**Major additions since March 2026:**
**Testing Improvements:**
- Added comprehensive test suite for `notifications` handler (600+ lines)
- Added new booking flow tests covering user booking creation
- Added extensive test docstrings across all test files
- Fixed test database setup and fixture issues
**Guest Booking System:**
- `POST /api/users/guest` — disposable account creation with email collision handling
- Partial unique email index: `WHERE account_role != 'guest'`
- `POST /api/bookings` now accepts `user_id` for guest bookings (OptionalAuth middleware)
- Guest bookings bypass deposit and patch-test checks
- `AnonymizeStaleGuestAccounts()` — scrubs PII 6 months after booking start_time
**Issues Discovered & Fixed Through Testing:**
- **Edit Request Bugs**: Multiple issues in booking edit request handling:
- Fixed validation for overlapping bookings during edits
- Fixed edit request approval flow
- Fixed edit request rejection handling
- Fixed status transition logic for edit requests
- **SQL Function Updates**: Refactored and optimized database functions in `init-script.sql`
- **Test Infrastructure**: Fixed manual test corruption issues, improved test isolation
**Slot Reservation System:**
- `POST /api/bookings/reserve` (public, OptionalAuth) — 4 TTL types: user=1h, anon=10min, walkin=5min, callin=1h
- Anonymous cap: 50 reservations per 10-minute rolling window
- `GetTimeBlockersInRange` excludes `RESERVATION:*` entries (self-block fix)
- Frontend: BookingFlow reserves on Step 2→3 transition, re-validates on "Next" click
- Admin modals wired to reserve endpoints (walk-in: 5min TTL, call-in: 60min TTL)
**Router Fix:**
- Chi routing conflict: `r.Route("/bookings", RequireAuth)` bled into `POST /bookings` (OptionalAuth)
- Flattened to explicit paths (`/bookings`, `/bookings/{id}`) with per-route middleware
**Seed Script Fixes:**
- `open_day()` now skips Saturday (6) not Monday (1), matching `working_hours` schema
- Guest booking dates moved to +16/+20/+22 days to avoid collisions with upcoming loop
- Seed now uses reserve-then-book flow matching frontend
**Test Coverage:** 222/224 passing
---
@@ -315,10 +330,10 @@ flowchart TD
end
end
subgraph External[External Services - TODO]
Gmail[Gmail SMTP]
SquareAPI[Square API]
S3[S3/R2 Storage]
subgraph External[External Services]
S3[S3/R2 Storage ✅]
Gmail[Gmail SMTP ❌]
SquareAPI[Square API ❌]
end
User -->|HTTPS| NGINX
@@ -329,9 +344,9 @@ flowchart TD
Auth --> Handlers
Handlers --> DB
Handlers --> DAV
Handlers --> S3
Handlers -.->|TODO| Gmail
Handlers -.->|TODO| SquareAPI
Handlers -.->|TODO| S3
```
---
@@ -348,11 +363,14 @@ flowchart TD
| POST | `/api/login` | Authenticate and receive JWT |
| POST | `/api/verify/generate` | Generate email verification or password reset code |
| POST | `/api/verify/check` | Verify code (email verification or password reset) |
| POST | `/api/users/guest` | Create disposable guest account (email unique for non-guests only) |
| GET | `/api/contact` | Get business contact info (from first admin user) |
| GET | `/api/scheduling/default-hours` | Get weekly default hours |
| GET | `/api/scheduling/exceptional-groups` | List holiday/special hour groups |
| GET | `/api/scheduling/working-hours` | Get merged working hours for date range |
| GET | `/api/scheduling/available-hours` | Get available booking slots |
| GET | `/api/scheduling/available-hours` | Get available booking slots (triggers reservation cleanup + guest anonymization) |
| POST | `/api/bookings/reserve` | Reserve a slot temporarily (OptionalAuth; user=1h TTL, anon=10min TTL, 50-cap) |
| POST | `/api/bookings` | Create booking (OptionalAuth; accepts `user_id` for guest bookings) |
### Authenticated User Endpoints
@@ -385,6 +403,7 @@ flowchart TD
| PUT | `/api/admin/bookings/{id}/progress` | Progress status |
| POST | `/api/admin/bookings/{id}/confirm` | Confirm booking |
| POST | `/api/admin/bookings/{id}/cancel` | Cancel booking |
| POST | `/api/admin/bookings/reserve` | Reserve slot for admin booking (walkin=5min TTL, callin=1h TTL) |
| GET | `/api/admin/users` | List users |
| GET | `/api/admin/users/{id}` | Get user details |
| GET | `/api/admin/users/{id}/patch-tests/eligible` | Get services requiring patch test that user hasn't completed |
@@ -435,6 +454,8 @@ payment_status: pending | completed | failed | refunded
admin_notification_reason: pending_booking | cancelled_booking | rescheduled_booking | 1_week_no_pay | 1_month_no_pay | affiliate_claim | late_cancellation | no_deposit | deposit_paid
```
**Email uniqueness**: Partial unique index `idx_users_email_registered ON users (email) WHERE account_role != 'guest'` — guests can share emails, registered users cannot.
**Suggested additional `admin_notification_reason` values:**
| Reason | Purpose |
@@ -450,18 +471,16 @@ admin_notification_reason: pending_booking | cancelled_booking | rescheduled_boo
| Table | Purpose |
|-------|---------|
| `users` | User accounts with profile data |
| `users` | User accounts with profile data (partial unique email: WHERE account_role != 'guest') |
| `verification_codes` | Email verification and password reset codes |
| `user_social_logins` | Social auth provider links |
TB|| `services` | Service offerings |
WR|| `patch_tests` | Patch test definitions (notice_duration_hours, expiry_months, service_ids) |
WR|| `user_patch_tests` | User patch test completion records (tested_at, notes) |
QB|| `bookings` | Appointment records |
VN|| `booking_services` | Services per booking |
ZM|| `booking_edit_requests` | Pending customer edit requests |
| `user_service_patch_tests` | Patch test tracking |
| `services` | Service offerings |
| `patch_tests` | Patch test definitions (notice_duration_hours, expiry_months, service_ids) |
| `user_patch_tests` | User patch test completion records (tested_at, notes) |
| `bookings` | Appointment records |
| `booking_services` | Services per booking |
| `booking_edit_requests` | Pending customer edit requests |
| `time_blockers` | Admin time blocks + slot reservations (description LIKE 'RESERVATION:%') |
| `user_referrals` | Referral tracking |
| `working_hours` | Default weekly schedule |
| `exceptional_working_hours_groups` | Holiday/special hour groups |
@@ -495,14 +514,18 @@ ZM|| `booking_edit_requests` | Pending customer edit requests |
| Task | Description | Files Affected |
| ------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------- |
| **Customer booking submit** | `submitBooking()` at line 600 only logs, needs `POST /api/bookings` | `frontend/src/lib/components/booking/BookingFlow.svelte` |
| **Remove console.logs** | Debug logs left in: `BookingFlow.svelte:600` | Frontend components |
| **Guest user endpoint** | Create `/api/users/guest` for walk-in bookings | `backend/handlers/user/` (new file) |
| **Remove console.logs** | Debug logs left in: `BookingFlow.svelte` | Frontend components |
| ~~Customer booking submit~~ | ~~`submitBooking()` only logs, needs `POST /api/bookings`~~ DONE | `frontend/src/lib/components/booking/BookingFlow.svelte` |
| ~~Guest user endpoint~~ | ~~Create `/api/users/guest` for walk-in bookings~~ DONE | `backend/handlers/user/guest.go` |
| ~~In-progress auto-infer~~ | ~~Auto-set `in_progress` status based on time~~ DONE | Backend booking logic |
| ~~Auto-complete~~ | ~~Auto-complete bookings when duration elapses~~ DONE | Backend today handlers |
| ~~Profile picture upload~~ | ~~Upload with cropper to separate bucket, sync to CalDAV~~ DONE | Backend + Account page |
| ~~Contact page dynamic~~ | ~~Fetch from `/api/contact` using first admin~~ DONE | Backend + Contact page |
| ~~Simplified deposits~~ | ~~`deposits_required` INT on users, 48h check, reduce on payment~~ DONE | Backend booking logic |
| ~~Slot reservation system~~ | ~~Temporary slot holds with 4 TTL types~~ DONE | `backend/handlers/bookings/reserve.go`, `admin_reserve.go`|
| ~~Booking flow reservation~~ | ~~Reserve slot on Step 2→3 transition in BookingFlow~~ DONE | `frontend/src/lib/components/booking/BookingFlow.svelte` |
| ~~Chi router conflict~~ | ~~Flattened /bookings sub-Route to prevent RequireAuth bleed~~ DONE | `backend/main.go` |
| ~~Reservation self-block~~ | ~~Exclude RESERVATION:* from GetTimeBlockersInRange~~ DONE | `backend/handlers/scheduling/time-blockers.go` |
| **Begin button (Today)** | Manual start for early arrivals, gray out if >3hrs away | `CurrentAppointment.svelte` + backend |
| **One-off custom services** | Admin creates custom service for single booking without adding to main list | Backend + frontend booking modals |
| **One-off exceptional hours** | Single-day exceptions (dentist, afternoon off) - not yearly/weekly | Backend scheduling + frontend HolidayHours |
@@ -511,6 +534,7 @@ ZM|| `booking_edit_requests` | Pending customer edit requests |
| **Square payment integration** | Full Square SDK integration | Backend payment handlers + frontend payment step |
| **GDPR data export** | User button for "give me my data" using `export_all_user_data()` | Backend endpoint + account page |
| **Tax data export** | Admin button for tax-software-compatible format | Backend endpoint + admin page |
| **Frontend deposits UI** | Display `deposits_required` status to users | Account page / booking flow |
### Medium Priority
@@ -632,11 +656,14 @@ src/lib/components/
- The backend merges default hours with applied exceptional hours automatically
- Static frontend is built and served by nginx; API calls go directly to Go backend in production
- Local dev uses SvelteKit's API proxy for CORS avoidance
- **GDPR functions exist in SQL** (`anonymize_user`, `export_all_user_data`, `delete_guest_user`) but only `DELETE /api/user/account` is wired - need user data export and admin tax export endpoints
- **Debug console.logs** in `BookingFlow.svelte:600` and `BookingCreateModal.svelte:224` should be removed before production
- **GDPR functions exist in SQL** (`anonymize_user`, `export_all_user_data`, `delete_guest_user`) — backend also has `AnonymizeStaleGuestAccounts()` which scrubs guest PII 6 months after booking start_time. Still need user-facing data export endpoint and admin tax export endpoint.
- **Notifications** are pull-based only (no push/WebSocket). Admin endpoint exists but no frontend UI. No user-facing notification system yet.
- **Notification acknowledgment**: When a booking is confirmed, any pending notification is acknowledged. When cancelled, pending is acknowledged and cancelled_booking notification is only created if the booking was not in pending status (e.g. was confirmed or in_progress).
- **User notification preferences**: `user_notification_preferences` table exists with email/sms/push enabled flags, waiting on user notification system to be implemented.
- **Guest accounts**: Disposable, created via `POST /api/users/guest`. Email uniqueness enforced only for non-guests (partial unique index). Multiple guest accounts can share an email.
- **Slot reservations**: Stored in `time_blockers` with `RESERVATION:*` descriptions. `GetTimeBlockersInRange` excludes these to prevent the reservation from blocking its own booking.
- **Chi router**: `/bookings` routes use explicit full paths (not `r.Route("/bookings", ...)`) to prevent RequireAuth middleware from bleeding into OptionalAuth POST endpoints.
- **Seed script** (`local-dev-2.sh`): `open_day()` skips Sunday and Saturday (matching `working_hours` schema). Guest bookings use reserve-then-book flow matching frontend.
---
@@ -671,38 +698,72 @@ Running `local-dev-2.sh` creates:
| Resource | Count | Details |
|----------|-------|---------|
| Users | 18 | 1 admin, 17 regular users |
JR|| Services | 8 | 6 standard (no patch test) + 2 requiring patch tests (Gel Polish Full Set 24h, Luxury Gel Manicure 24h) |
| Bookings | 45 | 8 past, 3 today, 4 tomorrow, 30 future (spread over 15 days) |
| Confirmed | ~50% | Random selection of upcoming bookings auto-confirmed |
| Exceptional | 2 | November Break (closed), Christmas Holiday (reduced hours) |
| Users | 20 | 1 admin, 19 regular users |
| Services | 12 | 10 standard + 2 requiring patch tests |
| Past Bookings | ~17 | Historical data |
| Today's Bookings | 4 | |
| Tomorrow's Bookings | 5 | |
| Upcoming Bookings | ~15 | Spread over 14 days |
| Guest Bookings | 3 | Nina (+16d), Bob (+20d), Carol (+22d) — reserve-then-book flow |
| Total Bookings | 42 | |
| Time Blockers | 3 | Staff meeting, holiday, late start |
| Schedule Groups | 3 | Exceptional working hour configurations |
| Cancellations | 1 | Simulated client cancellation |
### API JSON Examples
**Create Booking:**
**Create Guest Account:**
```json
POST /api/users/guest
{
"start_time": "2025-01-15T10:00:00+00:00",
"service_ids": ["abc123def456"],
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"phone": "+447700900123"
}
// Returns: { "id": "abc123def456", "account_role": "guest", ... }
```
**Create Booking (Guest):**
```json
POST /api/bookings
{
"user_id": "abc123def456",
"start_time": "2026-05-20T13:00:00+01:00",
"service_ids": ["SVC001"],
"notes": "Optional notes"
}
```
**Create Booking (Authenticated):**
```json
POST /api/bookings
{
"start_time": "2026-05-20T13:00:00+01:00",
"service_ids": ["SVC001"],
"notes": "Optional notes"
}
```
**Reserve Slot (Anonymous):**
```json
POST /api/bookings/reserve
{
"start_time": "2026-05-20T13:00:00+01:00",
"service_ids": ["SVC001"]
}
// Returns: { "id": "TBL001", "expires_at": "...", "is_anonymous": true }
```
**Create Service:**
```json
VN|{
POST /api/admin/services
{
"name": "Classic Manicure",
"description": "Nail shaping, cuticle care, hand massage, and polish.",
"price": 25.00,
"duration_minutes": 45,
"minimum_age_required": 0
}
"name": "Classic Manicure",
"description": "Nail shaping, cuticle care, hand massage, and polish.",
"price": 25.00,
"duration_minutes": 45,
"patch_test_duration_hours": 0,
"minimum_age_required": 0
}
```
@@ -0,0 +1,234 @@
**Last Updated:** May 2026
**Status:** Living backlog — add to this as gaps are discovered
---
# 🟢 Local Gaps — Can Fix Now
No external dependencies. No paid services. No API keys needed.
## P0 — Critical (Fix Now)
| # | Gap | Effort | Area | Notes |
|---|-----|--------|------|-------|
| 1 | ~~`DELETE /api/user/account` is a no-op~~ ✅ | S (1-2h) | Backend | Wired to `anonymize_user()` for registered users and `delete_guest_user()` for guests. CardDAV contact deleted best-effort. |
| 2 | **WalkInCreateModal guest booking errors out** | S (1-2h) | Frontend | Line 277: commented-out guest creation code. Shows "Guest booking not yet implemented" toast despite backend fully working. |
| 3 | **ApprovalModal decline/cancel stub** | S (2-3h) | Frontend | `handleDecline()` shows "Coming soon" toast. Admin cannot reject pending bookings. Backend confirm/cancel endpoints exist — decline just needs a cancel call. |
| 4 | **CurrentAppointment action stubs** | M (1d) | Frontend | `handleTakePayment()`, `handleExtend()`, `handleCancel()` all show "Coming soon". Today page has 3 dead buttons. Take payment is ⚠️ blocked on Square, but Extend and Cancel are local. |
## P1 — High
| # | Gap | Effort | Area | Notes |
|---|-----|--------|------|-------|
| 5 | **Admin notification panel** | M (1-2d) | Frontend | Backend fully wired (GET/acknowledge). No frontend UI to display notifications. Admin has no visibility into pending bookings, cancellations, no-shows. |
| 6 | **Reservation/anonymization cron** | S (2-3h) | Backend | `CleanupOldReservations()` and `AnonymizeStaleGuestAccounts()` only fire on availability fetch. If no one fetches availability, expired reservations persist and stale guests aren't anonymized. Should be a background ticker in `main.go`. |
| 7 | **GDPR data export endpoint** | M (1d) | Backend | `export_all_user_data()` SQL function exists (JSON export). No Go handler wired. Required for GDPR Article 15 SAR requests. |
| 8 | **VAT/Tax export endpoints** | M (1-2d) | Backend | `get_vat_return_data()`, `export_sales_transactions()` SQL functions exist. No admin API to trigger them. Needed for HMRC compliance. |
| 9 | **Walk-in guest reservation → booking transition** | S (1h) | Frontend | WalkInCreateModal line 358: TODO notes guest reservations may not properly transition to real bookings. Needs verification + fix. |
| 10 | **Password reset flow not wired to frontend** | S (2-3h) | Frontend | Backend has `/api/verify/generate` and `/api/verify/check` endpoints. Login page has no "forgot password" link or form. |
| 11 | **Email verification flow not wired to frontend** | S (2-3h) | Frontend | Users register with `unverified_email` role. No UI to enter verification code or resend code. `+layout.svelte` has alert-based prototype. |
| 12 | **Booking cancellation from user account** | S (2-3h) | Frontend | UserBookingModal shows booking details but no cancel button. Users must call/email to cancel. Backend endpoint exists (`DELETE /api/bookings/{id}`). |
| 13 | **Booking rescheduling for users** | M (1-2d) | Full-stack | Users can't reschedule their own bookings. `booking_edit_requests` table exists but frontend flow is incomplete — ApprovalModal only handles approve, not decline. |
## P2 — Medium
| # | Gap | Effort | Area | Notes |
|---|-----|--------|------|-------|
| 14 | ~~`delete_guest_user()` SQL function missing~~ ✅ | S (1h) | DB | Created next to `anonymize_user()` in init-script.sql. Called by `DeleteAccountHandler` for guest users. |
| 15 | **User notification preferences UI** | S (2-3h) | Frontend | DB table `user_notification_preferences` exists with email/sms/push flags. No settings page to toggle them. |
| 16 | **One-off custom services** | M (1-2d) | Full-stack | Admin can't create single-use services outside the catalog. Every custom job (bridal party, special request) must be added to permanent service list. |
| 17 | **One-off exceptional hours** | M (1d) | Full-stack | Single-day overrides (dentist appointment, afternoon off) require creating a full exceptional group. Should support one-off date blocks without group overhead. |
| 18 | ~~**HSTS header**~~ ✅ | XS (15min) | Backend | Added as a TODO-comment in the security headers middleware. Will be uncommented when HTTPS is enabled in production. |
| 19 | ~~**Referrer-Policy header**~~ ✅ | XS (15min) | Backend | Added as a TODO-comment in the security headers middleware. Will be uncommented when ready for production. |
| 20 | **Business settings management UI** | M (1-2d) | Full-stack | `business_settings` table exists (VAT registration, business name, etc.). No admin page to configure. Changes require direct SQL. |
| 21 | **Referral system UI** | M (1-2d) | Full-stack | `user_referrals` table exists. Users can't see their referral code or track uses. Admin can't manage referral campaigns. |
| 22 | **Analytics endpoints** | M (1-2d) | Backend | `handlers/admin/analytics.go` is 1 line. `get_monthly_business_summary()`, `get_sales_totals()` SQL functions exist. No admin dashboard stats. |
| 23 | ~~**console.log debug statements**~~ ✅ | XS (15min) | Frontend | Removed from BookingFlow.svelte and ImageUpload.svelte. |
| 24 | ~~**Alert-based prototype UX**~~ ✅ | XS (30min) | Frontend | Replaced all `alert()` calls with `toast.success/error/info` from svelte-sonner. |
| 25 | **No customer relationship view** | M (1-2d) | Frontend | Admin UserModal shows bookings list but no consolidated view: total spend, visit frequency, preferences, notes history. |
| 26 | **CSV/Excel export for bookings/payments** | M (1d) | Backend | Admin can't export data for accounting software. SQL functions exist but no endpoint to download as CSV. |
| 27 | ~~**Graceful shutdown**~~ ✅ | S (1h) | Backend | Added signal handling for SIGTERM/SIGINT with 15-second shutdown timeout in main.go. |
| 28 | ~~**Health check endpoint**~~ ✅ | XS (15min) | Backend | Added `GET /api/health` returning overall status plus DB, S3, Square, and frontend service statuses. |
| 29 | **API documentation** | M (1-2d) | Backend | No OpenAPI/Swagger spec. No generated docs. New developers must read code to understand endpoints. |
| 30 | **XSS input sanitization** | S (2-3h) | Backend | Backend validates format (regex, length) but doesn't sanitize HTML entities. Stored XSS risk in `notes`, `name`, `description` fields. |
| 31 | **Per-user rate limiting** | M (1d) | Backend | Rate limiter is IP-based. Authenticated users could abuse from multiple IPs. Should track by user ID + IP. Pure Go — no Redis needed for single-instance. |
| 32 | **CSRF protection** | S (2-3h) | Backend | SvelteKit handles some CSRF for its own forms, but direct API calls to `/api/*` bypass it. Consider double-submit cookie or SameSite cookies. |
| 33 | **Begin button (Today page)** | S (2-3h) | Full-stack | Manual start for early arrivals. Gray out if >3hrs away. Currently auto-infer only. |
| 34 | **Auto lunch protection** | M (1d) | Backend | Block bookings that remove lunch break. 1h customer auto-block, 30min admin with warning. |
| 35 | **Walk-in slot blocking** | S (1-2h) | Frontend | `WalkInCreateModal` doesn't properly block the next available slot during walk-in intake. Other customers could book the same slot. |
| 36 | **No idempotency keys for bookings** | S (2-3h) | Backend | Double-clicking "Confirm Booking" could create duplicate bookings. Should use idempotency keys or optimistic locking. |
| 37 | **No booking conflict detection for users** | S (2-3h) | Backend | Users can theoretically double-book themselves if they open two tabs. Reservation system helps but doesn't fully prevent. |
| 38 | **Service category/tag management** | M (1-2d) | Full-stack | Services have no category field. Hard to organize (manicure vs pedicure vs nail art). Admin must scroll through flat list. |
| 39 | ~~**No customer-facing cancellation policy display**~~ ✅ | XS (30min) | Frontend | Added cancellation policy text block in BookingFlow Step 3 below the terms & conditions line. |
| 40 | **No no-show tracking dashboard** | S (2-3h) | Frontend | Admin can't see which users have accumulated no-shows. `forgiven_no_shows` table exists but no UI. |
| 41 | ~~**No timezone handling for international customers**~~ ✅ | XS (15min) | Frontend | **Intentionally not implemented.** Crussell is UK-only; Cloudflare blocks non-UK traffic. Added explanatory comments in BookingFlow.svelte and README.md so this stops being flagged. |
| 42 | **Dark mode** | M (1-2d) | Frontend | SvelteKit + Tailwind supports it easily. No dark mode toggle or `prefers-color-scheme` support. |
| 43 | **PWA support** | L (3-5d) | Frontend | No service worker, no manifest.json, no offline support. Customers can't "install" the booking app. |
| 44 | **Automated database backups** | M (1d) | Infrastructure | No backup strategy. PostgreSQL volume is persistent but no automated dumps, no point-in-time recovery. Can use `pg_dump` cron on the host — no external service needed. |
| 45 | **Loyalty stamp redemption** | M (1-2d) | Full-stack | Display exists (account page shows "X stamps until 10% off"). No mechanism to redeem 10 stamps. Auto-apply of 10% discount at payment is ⚠️ blocked on Square. |
| 46 | **Staff management** | L (3-5d) | Full-stack | No multi-staff support. All bookings assumed single-provider. Schema change: add `staff_id` to bookings, per-staff availability tables. |
| 47 | **Recurring bookings** | L (3-5d) | Full-stack | Customers can't book the same slot weekly/monthly. Would need a `recurring_bookings` table + background job to materialize instances. |
| 48 | **Waitlist functionality** | M (1-2d) | Full-stack | When a slot is full, no way for customers to join a waitlist. Notification on cancellation is ⚠️ blocked on email/SMS, but in-app notification panel (#5) can handle it. |
| 49 | **Image optimization for portfolio** | M (1-2d) | Full-stack | Images uploaded as-is. No WebP conversion, no lazy loading, no responsive `srcset`. Portfolio loads full-res images. |
---
# 🔴 External Gaps — Blocked on Third-Party Access
Require paid accounts, API approval, or external service credentials. **Do not attempt until access is granted.**
## Payment — Square
| # | Gap | Effort | Area | Status | Notes |
|---|-----|--------|------|--------|-------|
| E1 | **Payment integration (Square SDK)** | XL (3-5d) | Full-stack | 🔒 Blocked | BookingFlow Step 4 shows "Square payment integration will be added here". Need `github.com/square/square-go-sdk` + API credentials. |
| E2 | **No deposit payment flow** | M (1-2d) | Full-stack | 🔒 Depends on E1 | Users with `deposits_required > 0` can't pay deposits online. Blocked from new bookings until they pay in-person. |
| E3 | **Tip calculation UI** | S (1h) | Frontend | 🔒 Depends on E1 | `tip` exists in `payment_type` enum. No UI to add tips during payment or at the Today page. |
| E4 | **Gift card system** | L (3-5d) | Full-stack | 🔒 Depends on E1 | `giftcard` exists in `payment_method` enum. No gift card creation, redemption, or balance tracking. |
## Email/SMS — SMTP Provider (Resend, SendGrid, Twilio, etc.)
| # | Gap | Effort | Area | Status | Notes |
|---|-----|--------|------|--------|-------|
| E5 | **Email/SMS notification system** | XL (5-7d) | Backend | 🔒 Blocked | No SMTP integration. No scheduled jobs for booking reminders. `user_notification_preferences` table exists but unused. Need SMTP credentials or API key from provider. |
| E6 | **Automated deposit reduction notification** | S (1h) | Backend | 🔒 Depends on E5 | When `deposits_required` decreases, no notification is sent. User doesn't know they're closer to being unblocked. |
| E7 | **Waitlist cancellation notifications** | S (1h) | Backend | 🔒 Depends on E5 + #48 | When a slot opens up, waitlisted customers need to be notified. |
## Cloud Storage — S3/R2 Production
| # | Gap | Effort | Area | Status | Notes |
|---|-----|--------|------|--------|-------|
| E8 | **S3/R2 production stubs** | M (1d) | Backend | 🔒 Blocked | `s3.go` (`!dev` build tag): Upload/Download/Delete all return "not implemented" errors. Prod builds cannot store images. Need AWS SDK v2 + R2/S3 credentials. Dev works fine with RustFS. |
## Social Auth — OAuth Apps (Google, Microsoft, Facebook)
| # | Gap | Effort | Area | Status | Notes |
|---|-----|--------|------|--------|-------|
| E9 | **Social auth (Google/Microsoft/Facebook)** | L (2-3d) | Backend | 🔒 Blocked | `handlers/auth/social.go` is 1 line. Need OAuth app registrations + client secrets for each provider. |
## Monitoring — Sentry / Error Tracking
| # | Gap | Effort | Area | Status | Notes |
|---|-----|--------|------|--------|-------|
| E10 | **Error tracking / monitoring** | M (1-2d) | Backend | 🔒 Blocked | No Sentry, no structured logging, no error aggregation. `log.Printf()` only. No alerting on 5xx errors. Needs Sentry DSN or equivalent. |
---
## Dependency Map
```
┌─────────────────────────────────────────────────────────┐
│ EXTERNAL BLOCKERS │
│ │
│ E1 Square API ──┬──→ E2 Deposit payments │
│ ├──→ E3 Tips │
│ └──→ E4 Gift cards │
│ │
│ E5 SMTP/API ────┬──→ E6 Deposit reduction notifications│
│ └──→ E7 Waitlist notifications │
│ │
│ E8 S3/R2 ───────→ Portfolio images in production │
│ │
│ E9 OAuth Apps ──→ E9 Social auth │
│ │
│ E10 Sentry ─────→ E10 Error tracking │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ LOCAL (UNBLOCKED) │
│ │
│ #1 Delete account ──→ #7 GDPR export ──→ #14 SQL func │
│ │
│ #3 Approval decline ──→ #13 Booking reschedule │
│ │
│ #5 Admin notification panel ──→ #15 Preferences UI │
│ ──→ #48 Waitlist (partial) │
│ │
│ #2 Walk-in guest fix ──→ #9 Reservation transition │
│ │
│ #6 Reservation cron ──→ #42 Dark mode (no deps) │
│ │
│ #39 Cancellation policy ──→ ZERO deps, 30min fix │
│ #28 Health check ──→ ZERO deps, 15min fix │
│ #23 console.log cleanup ──→ ZERO deps, 15min fix │
│ #24 Alert prototype cleanup ──→ ZERO deps, 30min fix │
└─────────────────────────────────────────────────────────┘
```
---
## Suggested Execution Order
### Phase 1 — Zero-Dependency Quick Wins (Week 1)
*No external services. Each takes <30min except #1.*
1. **#23** Remove console.log debug statements (15min)
2. **#24** Replace alert() prototypes with toast notifications (30min)
3. **#28** Add health check endpoint (15min)
4. **#39** Add cancellation policy display to BookingFlow (30min)
5. **#41** Fix timezone display for international customers (15min)
6. **#18** Add HSTS header (15min)
7. **#19** Add Referrer-Policy header (15min)
8. **#1** Fix `DELETE /api/user/account` (1-2h) — biggest win in this phase
### Phase 2 — Admin Productivity (Week 2)
9. **#2** Wire WalkInCreateModal guest booking (1-2h)
10. **#3** ApprovalModal decline/cancel (2-3h)
11. **#5** Admin notification panel (1-2d)
12. **#4** CurrentAppointment Extend + Cancel actions (1d) — skip TakePayment (blocked on E1)
13. **#12** Booking cancellation from user account (2-3h)
14. **#40** No-show tracking dashboard (2-3h)
### Phase 3 — Compliance + Reliability (Week 3)
15. **#6** Reservation/anonymization background cron (2-3h)
16. **#7** GDPR data export endpoint (1d)
17. **#8** VAT/Tax export endpoints (1-2d)
18. **#14** Create `delete_guest_user()` SQL function (1h)
19. **#27** Graceful shutdown (1h)
20. **#36** Idempotency keys for bookings (2-3h)
21. **#30** XSS input sanitization (2-3h)
22. **#44** Automated database backups (1d)
### Phase 4 — User Experience (Week 4)
23. **#10** Password reset flow (2-3h)
24. **#11** Email verification flow (2-3h)
25. **#13** Booking rescheduling for users (1-2d)
26. **#20** Business settings management UI (1-2d)
27. **#16** One-off custom services (1-2d)
28. **#17** One-off exceptional hours (1d)
29. **#35** Walk-in slot blocking (1-2h)
30. **#38** Service category management (1-2d)
### Phase 5 — Growth + Polish (Week 5+)
31. **#21** Referral system UI (1-2d)
32. **#22** Analytics endpoints (1-2d)
33. **#25** Customer relationship view (1-2d)
34. **#26** CSV/Excel export (1d)
35. **#29** API documentation (1-2d)
36. **#31** Per-user rate limiting (1d)
37. **#32** CSRF protection (2-3h)
38. **#33** Begin button (Today page) (2-3h)
39. **#34** Auto lunch protection (1d)
40. **#37** Booking conflict detection (2-3h)
41. **#45** Loyalty stamp redemption (partial — UI only, payment apply blocked on E1)
42. **#46** Staff management (3-5d)
43. **#47** Recurring bookings (3-5d)
44. **#48** Waitlist functionality (1-2d)
45. **#49** Image optimization (1-2d)
46. **#42** Dark mode (1-2d)
47. **#43** PWA support (3-5d)
### ⏳ Waiting on External Access
| Item | Blocked On | Unblocks |
|------|-----------|----------|
| **E1 Square payment** | Square API credentials | E2, E3, E4, deposit payments, loyalty redemption |
| **E5 Email/SMS** | SMTP provider (Resend/SendGrid/Twilio) | E6, E7, booking reminders, password reset emails |
| **E8 S3/R2 production** | Cloudflare R2 or AWS S3 credentials | Portfolio images in production builds |
| **E9 Social auth** | OAuth app registrations (Google/Microsoft/Facebook) | Social login flow |
| **E10 Sentry** | Sentry DSN or equivalent | Error tracking, 5xx alerting |