34 KiB
#MW|> Last Updated: March 2026
Status: Work in Progress
Project Checklist
Backend (Go / Chi / Postgres)
Authentication & Authorization
- JWT authentication (login, refresh, role verification)
- User registration with input validation
- Names: 1-50 chars, unicode letters/spaces/hyphen/apostrophe/dot
- Phone: UK format → E.164 (+44...)
- Email: standard format
- Age: Must be 16+ years
- Password hashing with bcrypt
- Middleware for auth/roles (
mw.RequireAuth,mw.RequireAdmin) - DB connection pooling
- Refresh token endpoint - Wired to
POST /api/refresh-token, auto-refresh in frontend - Login rate limiting (1 attempt per 5 seconds)
- Global rate limiting middleware (per-endpoint: 120/min public, 10/min register, 60/min filters, none admin)
- Security headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection)
- Image metadata stripping - All EXIF/GPS stripped on upload via
imaginglibrary (security) - Service eligibility system - Age and patch test filtering for bookings
- Uses dedicated
patch_teststable (notice_period_hours, expiry_months) /api/services- Returns services with eligibility for authenticated users/api/services/eligible-for/{user_id}- Returns services with eligibility for specific user (admin booking flows)- Age < minimum_age_required → Service EXCLUDED
- Patch test required + no record → Service GRAYED OUT
- Patch test within notice period (24h) → Service GRAYED OUT
- Patch test expired → Service GRAYED OUT
- Patch test valid / not required → Normal
- Uses dedicated
- Patch test validation on booking - Both user and admin booking creation/editing validates patch test requirements
/api/services- Returns services with eligibility for authenticated users/api/services/eligible-for/{user_id}- Returns services with eligibility for specific user (admin booking flows)- Age < minimum_age_required → Service EXCLUDED
- Patch test required + no record → Service GRAYED OUT
- Patch test expired → Service GRAYED OUT
- Patch test valid / not required → Normal
- Strict-Transport-Security (HSTS) - Tell browsers to only access via HTTPS, prevents downgrade attacks. Add after HTTPS is working in prod.
- Referrer-Policy - Track referrer sources for analytics (social media tracking). Use
strict-origin-when-cross-originto send origin but not full URLs. - Rate limiter + Cloudflare - Currently doesn't read CF-Connecting-IP header, so behind Cloudflare all users share one rate limit bucket.
- Account creation spam - Registration endpoint (10/min) could benefit from additional bot protection beyond rate limiting.
- Input validation on all endpoints:
- Registration: name (1-50), email (255), phone (20), password (72), age 16+
- 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
/api/bookings- Full CRUD for authenticated users/api/admin/bookings- List, search, create for user, progress, confirm, cancel/api/admin/bookings/search- Search functionality/api/admin/bookings/user/{user_id}- User-specific bookings/api/admin/bookings/{id}/progress- Progress booking status/api/admin/bookings/{id}/confirm- Confirm booking/api/admin/bookings/{id}/cancel- Cancel booking- In-progress auto-infer - Status auto-sets based on time (confirmed → in_progress → completed)
- Auto-complete - Bookings auto-complete when duration elapses
Admin Endpoints
/api/admin/services- Create, delete, list, toggle/api/admin/users- List, view with booking history/api/admin/today- Current/next appointment, today's appointments, pending approvals/api/admin/notifications- GET/acknowledge endpoint wired, but:- Pending notification acknowledgment - Acknowledged on confirm/cancel (not deleted)
- Cancelled booking notification - Created when cancelling non-pending bookings
- Frontend UI to display notifications
- Push mechanism (currently only pull-based)
- User notifications (only admin notifications exist)
- User notification preferences (DB table ready)
Scheduling System
/api/scheduling/default-hours- GET public, PUT admin/api/scheduling/exceptional-groups- CRUD for holiday/special hours/api/scheduling/working-hours- Merged default + exceptional hours/api/scheduling/available-hours- Available slots accounting for bookings
User Endpoints
/api/user/profile- GET, PUT/api/user/profile-picture- POST upload profile picture (separate bucket)/api/user/account- DELETE (GDPR compliant)/api/user/loyalty- GET loyalty stamps/api/contact- Public endpoint returning first admin's contact info (name, phone, email, profilePicUrl)- GDPR data export -
export_all_user_data()exists but not wired to endpoint - Tax data export - Admin endpoint for tax-software-compatible format
Deposits System (Simplified)
users.deposits_requiredINT (0-3) tracks outstanding deposit obligations- 24h late cancellation rule: < 24h without forgiveness =
no_show+ 3 deposits (resets to 3, not +=), ≥ 24h = normal cancellation - Optional
forgive_no_showboolean at cancellation - admin can forgive penalty case-by-case, counts asclient_cancelled - Optional
enforce_depositsboolean in admin booking creation - admin can bypass deposit checks when needed - Reduces by 1 when booking completes with payment
- 2+ unforgiven no-shows in 6 months = 3 deposits (blocks new bookings)
- Deleted:
ForgiveNoShowsForUser()function (now per-cancellation forgiveness) - Implementation complete in
bookings.go(24h threshold, forgiveness logic) andmanage.go(admin deposit enforcement) - Frontend display of deposits_required status to users
CalDAV Contact Sync
- Profile photos synced to CardDAV contacts (PHOTO field in vCard)
- Auto-updates when profile is changed
Not Yet Wired
- Social auth (
handlers/auth/social.goexists, not imported) - Analytics (
handlers/admin/analytics.goexists, not imported) - 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)
Unit Tests & CI/CD
- Unit tests
- CI/CD pipeline
Frontend (SvelteKit / Tailwind / shadcn)
Core Pages
- Home (
/) - Prices (
/prices) - Contact (
/contact) - Dynamic, fetches from/api/contact - Book (
/book) - Full wizard with service selection, date/time, customer details - Portfolio (
/portfolio) - S3/R2 storage with tag filtering, category filters, pagination, ?img= featured image, admin upload - Today (
/today) - Admin only, real-time schedule view with auto-status transitions - Schedule (
/schedule) - User's upcoming bookings with .ics export and calendar download button - Account (
/account) - Profile management, profile picture upload with cropper, loyalty stamps display - Login (
/login) - Manage (
/manage)
Admin Dashboard (/admin)
- Auth guard with role check
- ImageUpload component
- UsersCard + UserModal + PatchTestModal
- BookingsCard + BookingModal
- HolidayHours (exceptional hours management)
- WeeklySchedule (default hours management)
- ServicesManagement
- BookingCreateModal (call-in/admin booking creation)
- WalkInBooking + WalkInCreateModal
- CallInBooking
- ApprovalModal
Booking Flow
- Service selection with pricing/duration
- Service eligibility display - Gray out services requiring patch test or below minimum age
- Calendar with availability detection
- Time slot generation with gap logic
- Customer details form (guest or authenticated)
- Auth store with token refresh logic
- Admin booking flows - Call-in and walk-in use
/api/services/eligible-for/{user_id}for user-specific eligibility - 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, needsPOST /api/bookings - Payment integration (Square placeholder)
API Integration
- Services fetch from
/api/services - Working hours fetch from
/api/scheduling/working-hours - Available hours fetch from
/api/scheduling/available-hours - Admin bookings use
/api/admin/bookings - Guest user creation (
/api/users/guestnot implemented)
Infrastructure
.envconfig- Docker Compose (postgres, backend, sabredav, nginx)
- Static frontend build served via nginx
- nginx reverse proxy - config under review
- Monitoring/logging - no stack configured
- CI/CD pipeline (Gitea) - not yet defined
- Prometheus metrics integration
Integrations
- CardDAV sync for contacts (SabreDAV)
- CalDAV ready
- Profile pics bucket - separate bucket
crussell-profile-picsfor user profile pictures - Email/SMS reminders - not yet implemented
- Square payment - placeholder only
- S3/R2 image hosting - Rustfs for dev, Cloudflare R2 for prod via build tags
Testing Infrastructure
Crussell has a comprehensive Go testing infrastructure located in backend/testutils/:
Test Utilities
| Component | File | Description |
|---|---|---|
| Database | testutils/testdb/testdb.go |
PostgreSQL test pool, migrations, table truncation |
| HTTP Helpers | testutils/helpers.go |
Request builders, auth helpers, assertions |
| JWT | testutils/jwt/jwt.go |
Test token generation for users/admins |
| HTTP Client | testutils/httptest/client.go |
REST client wrapper with auth support |
| Fixtures | testutils/fixtures/fixtures.go |
Factory functions for test data (users, services, bookings) |
| Validators | internal/validators/validators.go |
ID validation utilities |
Test Files
16 test files covering all major handlers:
backend/
├── bookings.test # Main booking integration tests
├── portfolio.test # Portfolio system tests
├── handlers/
│ ├── admin/
│ │ ├── bookings_test.go # Admin booking management
│ │ ├── today_test.go # Today's view
│ │ ├── users_test.go # User management
│ │ └── services_test.go # Service CRUD
│ ├── auth/
│ │ └── auth_test.go # Authentication
│ ├── bookings/
│ │ └── bookings_test.go # User booking flow
│ ├── portfolio/
│ │ └── images_test.go # Image upload/management
│ ├── scheduling/
│ │ └── scheduling_test.go # Availability logic
│ ├── services/
│ │ └── services_test.go # Service eligibility
│ ├── user/
│ │ └── profile_test.go # User profile
│ └── handlers_test.go # Common handler tests
Running Tests
cd backend
go test ./...
go test -v ./handlers/bookings
go test -v -run "TestBooking" ./...
Test Database
- DSN:
postgres://myuser:mypassword@localhost:5432/crussell_test?sslmode=disable - Override:
export TEST_DB_DSN="..." - Uses
testdb.Migrate()to run init-script.sql - Tables truncated between tests via
testdb.TruncateTables()
Conventions
- All test files use
//go:build testbuild tag - Test tokens use fixed secret:
test-secret-key-for-testing-only - Fixtures auto-generate unique emails to avoid conflicts
Recent Testing Updates (March 2026)
Since the February 2026 update, extensive testing has been conducted which uncovered and fixed several issues:
Testing Improvements:
- Added comprehensive test suite for
notificationshandler (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
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
Overview Diagram
flowchart TD
User([Customer])
Admin([Admin])
subgraph Docker[Docker Compose Stack]
subgraph NGINX[Nginx :80/:443]
Static[Static Frontend Build]
Proxy[API Proxy → Backend:8080]
DAVProxy[DAV Proxy → SabreDAV]
end
subgraph Backend[Go + Chi :8080]
Router[chi Router]
Auth[JWT Middleware]
Handlers[API Handlers]
end
subgraph Database[PostgreSQL :5432]
DB[(Users / Bookings<br/>Payments / Services<br/>Scheduling)]
end
subgraph DAV[SabreDAV :9000]
CardDAV[(vCard Contacts)]
CalDAV[(Calendar Events)]
end
end
subgraph External[External Services - TODO]
Gmail[Gmail SMTP]
SquareAPI[Square API]
S3[S3/R2 Storage]
end
User -->|HTTPS| NGINX
Admin -->|HTTPS| NGINX
Static --> User
Proxy --> Router
Router --> Auth
Auth --> Handlers
Handlers --> DB
Handlers --> DAV
Handlers -.->|TODO| Gmail
Handlers -.->|TODO| SquareAPI
Handlers -.->|TODO| S3
API Reference
Public Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/services |
List active services (with eligibility for authenticated users) |
| GET | /api/services/eligible-for/{user_id} |
List services filtered by user's age and patch test status (admin only) |
| POST | /api/register |
Create new user account |
| 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) |
| 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 |
Authenticated User Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/user/profile |
Get current user profile |
| PUT | /api/user/profile |
Update profile |
| POST | /api/user/profile-picture |
Upload profile picture with cropper |
| DELETE | /api/user/account |
Delete account (GDPR) |
| GET | /api/user/loyalty |
Get loyalty stamp count |
| GET | /api/bookings |
List user's bookings |
| POST | /api/bookings |
Create booking |
| GET | /api/bookings/{id} |
Get specific booking |
| GET | /api/bookings/{id}/calendar |
Download ICS calendar file |
| PUT | /api/bookings/{id} |
Update booking |
| DELETE | /api/bookings/{id} |
Cancel booking |
Admin Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/admin/services |
List all services |
| POST | /api/admin/services |
Create service |
| DELETE | /api/admin/services/{id} |
Delete service |
| PUT | /api/admin/services/{id}/toggle |
Toggle active status |
| GET | /api/admin/bookings |
List all bookings |
| POST | /api/admin/bookings |
Create booking for user |
| GET | /api/admin/bookings/search |
Search bookings |
| GET | /api/admin/bookings/user/{user_id} |
User's bookings |
| PUT | /api/admin/bookings/{id}/progress |
Progress status |
| POST | /api/admin/bookings/{id}/confirm |
Confirm booking |
| POST | /api/admin/bookings/{id}/cancel |
Cancel booking |
| 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 |
| POST | /api/admin/users/{id}/patch-tests |
Record patch test completion for user |
| GET | /api/admin/today/current-next |
Current and next appointment |
| GET | /api/admin/today/appointments |
Today's appointments |
| GET | /api/admin/today/pending-approvals |
Pending approval queue |
| GET | /api/admin/notifications |
List notifications |
| POST | /api/admin/notifications/{id}/acknowledge |
Acknowledge |
| PUT | /api/scheduling/default-hours |
Update weekly hours |
| POST | /api/scheduling/exceptional-groups |
Create exception group |
| DELETE | /api/scheduling/exceptional-groups |
Delete exception group |
| PUT | /api/scheduling/exceptional-applications |
Apply exceptions to dates |
Portfolio Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/portfolio/images |
List images with pagination, filter by tag/tags, filter by category:value |
| GET | /api/portfolio/tags |
List all tags for autocomplete (public) |
| GET | /api/portfolio/filters |
Get filter categories with counts. Supports tag filtering. Unselected categories show counts reduced by other filters. |
| GET | /api/portfolio/images/{id} |
Get single image by UUID or timestamp (fallback to URL pattern match) |
| POST | /api/portfolio/images |
Upload new image with thumbnail and tags (admin only) |
| DELETE | /api/portfolio/images/{id} |
Delete image (admin only) |
Portfolio Frontend Features:
/portfoliopage with tag-based filtering and category filters- Category filters:
?filter[color]=red&filter[season]=summer - Tag search:
?tag=tobyor?tags=toby,summer - Featured image:
?img=<id|timestamp>- loads image directly, bypasses filters - Filter UI: scrollable dropdowns, keyboard navigation, mobile-optimized
- Admin upload: ImageUpload component with live tag suggestions, keyboard nav, confirmation modal
Database Schema
Enums
account_role: unverified_email | verified_email | admin | guest | affiliate
account_type: email | google | microsoft | facebook | guest
booking_status: pending | confirmed | in_progress | completed | client_cancelled | we_cancelled | re-schedule | no_show | no_deposit
verification_purpose: email_verify | password_reset
payment_type: deposit | full | tip | balance | partial
payment_method: online_square | in_person_card | cash | giftcard | discount
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
Suggested additional admin_notification_reason values:
| Reason | Purpose |
|---|---|
payment_failed |
Payment processing failed |
patch_test_due |
Customer needs patch test before appointment |
first_time_customer |
New customer's first booking |
inactive_customer |
Regular hasn't booked in X months - 5% discount (non stacking) |
birthday_this_week |
Customer birthday - 5% discount (stacking) |
schedule_conflict |
Potential double-booking detected - admin alert, 'second customer' alert |
Core Tables
| Table | Purpose |
|---|---|
users |
User accounts with profile data |
verification_codes |
Email verification and password reset codes |
user_social_logins |
Social auth provider links |
| TB | |
| WR | |
| WR | |
| QB | |
| VN | |
| ZM | |
user_service_patch_tests |
Patch test tracking |
bookings |
Appointment records |
booking_services |
Services per booking |
user_referrals |
Referral tracking |
working_hours |
Default weekly schedule |
exceptional_working_hours_groups |
Holiday/special hour groups |
exceptional_working_hours |
Hours for exception groups |
exceptional_group_applications |
Apply exceptions to date ranges |
payments |
Payment transactions |
business_settings |
Business configuration |
admin_notifications |
Admin notification queue |
images |
Portfolio gallery images with tags |
tags |
Image tag autocomplete |
user_notification_preferences |
User notification preferences (email/sms/push) |
Key Functions
| Function | Purpose |
|---|---|
generate_short_id() |
Generate 12-char IDs |
anonymize_user() |
GDPR data removal |
delete_guest_user() |
Clean up guest accounts |
export_all_user_data() |
GDPR subject access |
get_monthly_business_summary() |
Analytics |
get_vat_return_data() |
VAT reporting |
calculate_vat() |
VAT calculation |
get_receipt_data() |
Receipt generation |
Remaining Work
High Priority
| 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) |
in_progress status based on time |
Backend booking logic | |
| Backend today handlers | ||
| Backend + Account page | ||
/api/contact using first admin |
Backend + Contact page | |
deposits_required INT on users, 48h check, reduce on payment |
Backend booking logic | |
| 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 |
| Auto lunch protection | Block bookings that remove lunch break (1h customer, 30min admin with warning) | Backend available-hours logic |
| Walk-in slot blocking | Properly block next available slot during walk-in intake | WalkInCreateModal.svelte |
| 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 |
Medium Priority
| Task | Description |
|---|---|
| Notifications UI | Frontend panel to display admin notifications |
| Notifications push | Real-time notification mechanism (WebSocket/polling) |
| User notification preferences | [x] DB table ready, waiting on user notification system |
| User notifications | Notification system for regular users (booking confirmations, reminders) |
| Remove debug logs | console.log in BookingFlow.svelte:600 and BookingCreateModal.svelte:224 |
| Loyalty display component | Show stamps in account/bookings |
| Email/SMS reminders | Scheduled notification jobs |
| Prometheus metrics | Monitoring integration |
Low Priority
| Task | Description |
|---|---|
| Social auth | Wire handlers/auth/social.go |
| Analytics | Wire handlers/admin/analytics.go |
| nginx config review | Finalize production config |
| CI/CD pipeline | Gitea Actions workflow |
| And many more |
Environment Variables
| Variable | Purpose | Required |
|---|---|---|
JWT_SECRET_KEY |
Secret for JWT signing | Yes |
DATABASE_URL |
PostgreSQL connection string | Yes |
POSTGRES_USER |
Database username | Docker |
POSTGRES_PASSWORD |
Database password | Docker |
POSTGRES_DB |
Database name | Docker |
S3_BUCKET |
Main image bucket (portfolio) | No (default: crussell) |
S3_PROFILE_PICS_BUCKET |
Profile pictures bucket | No (default: crussell-profile-pics) |
S3_ENDPOINT |
S3/Rustfs endpoint | Dev |
S3_PUBLIC_URL |
Public URL for S3 bucket | Dev |
S3_ACCESS_KEY |
S3 access key | Dev |
S3_SECRET_KEY |
S3 secret key | Dev |
R2_ENDPOINT |
Cloudflare R2 endpoint | Prod |
R2_BUCKET |
R2 bucket name | Prod |
R2_PUBLIC_URL |
R2 public URL | Prod |
R2_ACCESS_KEY |
R2 access key | Prod |
R2_SECRET_KEY |
R2 secret key | Prod |
Docker Compose
services:
postgres:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-scripts/init-script.sql:/docker-entrypoint-initdb.d/init-script.sql:ro
backend:
build: ./backend
depends_on: [postgres]
sabredav:
image: php:8.2-fpm
volumes:
- ./sabredav:/var/www/dav
depends_on: [postgres]
nginx:
image: nginx:stable
ports: ["80:80", "443:443"]
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./frontend/build:/usr/share/nginx/html
depends_on: [backend, sabredav]
Frontend Component Structure
src/lib/components/
├── admin/
│ ├── ApprovalModal.svelte
│ ├── BookingCreateModal.svelte
│ ├── BookingModal.svelte
│ ├── BookingsCard.svelte
│ ├── CallInBooking.svelte
│ ├── HolidayHours.svelte
│ ├── ImageUpload.svelte
│ ├── PatchTestModal.svelte
│ ├── ServicesManagement.svelte
│ ├── UserModal.svelte
│ ├── UsersCard.svelte
│ ├── WalkInBooking.svelte
│ └── WalkInCreateModal.svelte
├── booking/
│ ├── BookingActions.svelte
│ ├── BookingFlow.svelte
│ ├── BookingSummary.svelte
│ ├── DatePicker.svelte
│ ├── ServiceCard.svelte
│ ├── ServiceSelector.svelte
│ ├── StepIndicator.svelte
│ └── TimeSlotPicker.svelte
└── today/
├── CurrentAppointment.svelte
├── PendingApprovals.svelte
└── TodayCalendar.svelte
Notes
- Holiday hours ARE integrated into all 3 booking flows (customer, call-in, walk-in) via
/api/scheduling/working-hoursand/api/scheduling/available-hours - 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 onlyDELETE /api/user/accountis wired - need user data export and admin tax export endpoints - Debug console.logs in
BookingFlow.svelte:600andBookingCreateModal.svelte:224should be removed before production - 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_preferencestable exists with email/sms/push enabled flags, waiting on user notification system to be implemented.
Development Workflow
Quick Start
./local-dev-2.sh # Creates tmux session 'crussell-dev'
Creates 3 panes:
- Pane 0: psql interactive shell
- Pane 1: Backend (
go run -tags dev ./main.go) - Pane 2: Frontend (
npm run dev -- --host)
Dev Build Tag
Backend uses -tags dev - check for dev-specific behavior in code with build constraints.
Admin User Setup
No API endpoint exists for role promotion. Admin users are created via direct SQL:
UPDATE users SET account_role = 'admin' WHERE email = 'admin@example.com';
Seed Data
Running local-dev-2.sh creates:
| Resource | Count | Details |
|---|---|---|
| Users | 18 | 1 admin, 17 regular users |
| JR | Services | |
| 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) |
API JSON Examples
Create Booking:
{
"start_time": "2025-01-15T10:00:00+00:00",
"service_ids": ["abc123def456"],
"notes": "Optional notes"
}
Create Service:
VN|{
"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
}
Confirm Booking:
POST /api/admin/bookings/{id}/confirm
Body: {"serviceOverrides": []}
Create Exceptional Group:
{
"name": "Christmas Holiday Period",
"description": "Reduced hours for Christmas and New Year",
"hours": [
{"weekday": 0, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
{"weekday": 1, "startTime": "10:00:00", "endTime": "15:00:00", "isOpen": true}
],
"weekStarts": ["2025-12-22", "2025-12-29"]
}
Time Format
All timestamps use ISO 8601 with timezone: YYYY-MM-DDTHH:MM:SS±HH:MM (e.g., 2025-01-15T10:00:00+00:00)
Timezone is always Europe/London (handles BST automatically).
Code Patterns
Transaction Pattern
Used throughout for atomic operations:
tx, err := db.DB.Begin(r.Context())
if err != nil { /* handle error */ }
defer tx.Rollback(r.Context())
// Use tx instead of db.DB for queries
err = tx.QueryRow(r.Context(), `INSERT INTO...`)
if err := tx.Commit(r.Context()); err != nil { /* handle error */ }
CardDAV Synchronization
- On registration: Creates vCard in SabreDAV via
dav.Service.CreateContact() - On profile update: Updates existing vCard via
updateCardDAV()helper - Uses internal HTTP calls to DAV server
Role Change Detection
RefreshTokenHandler verifies user's current role hasn't changed since token was issued. If role changed, forces re-login with 401 Unauthorized.
Build Tags
db_dev.go- Used with-tags devfor local development (localhost connection)db.go- Production build (uses env var for host)internal/dav/service_dev.go/service_prod.go- Same pattern for DAV service