# Crussell - Beauty Salon Booking System

Stack: Go (Chi) backend | SvelteKit 5 static frontend | PostgreSQL | SabreDAV | Docker

## Dev Startup
```bash
./local-dev-2.sh  # Creates tmux session 'crussell-dev' with 3 panes:
# Pane 0: psql interactive
# Pane 1: backend (go run -tags dev ./main.go)
# Pane 2: frontend (npm run dev -- --host)
```

## Architecture

```
nginx:80/443 → static frontend build + /api/* → go:8080 → postgres:5432 + sabredav
```

Frontend is built static (no SSR). API calls go directly to Go backend in production. Local dev uses SvelteKit proxy for CORS.

## Project Structure

```
Crussell/
├── backend/
│   ├── main.go                 # Router, all routes defined here
│   ├── auth/jwt.go             # JWT init, signing
│   ├── auth/password.go        # bcrypt hashing
│   ├── mw/auth.go              # RequireAuth, RequireAdmin middleware
│   ├── db/db.go                # Connection pooling
│   ├── db/db_dev.go            # Dev-specific DB config
│   ├── handlers/
│   │   ├── auth/local.go       # Login, register, refresh-token
│   │   ├── auth/social.go      # NOT WIRED
│   │   ├── bookings/           # User + admin booking CRUD
│   │   ├── admin/              # Users, analytics (analytics NOT WIRED)
│   │   ├── scheduling/         # Default + exceptional hours
│   │   ├── services/           # Service management
│   │   ├── user/               # Profile, loyalty, account
│   │   ├── notifications/      # NOT WIRED
│   │   ├── portfolio/          # NOT WIRED
│   │   └── today/              # Current/next appointments
│   └── internal/dav/           # CardDAV/CalDAV client
├── frontend/
│   └── src/
│       ├── routes/
│       │   ├── admin/+page.svelte      # Admin dashboard
│       │   ├── today/+page.svelte      # Today view
│       │   ├── book/+page.svelte       # Booking wizard
│       │   ├── login/+page.svelte      # Auth
│       │   └── api/[...path]/+server.ts # Dev proxy only
│       └── lib/
│           ├── components/
│           │   ├── admin/              # 13 components
│           │   ├── booking/            # 8 components
│           │   └── today/              # 3 components
│           ├── stores/auth.svelte.ts   # Auth state
│           └── types/booking.ts        # TS interfaces
├── init-scripts/init-script.sql        # Full schema + functions
├── compose.yml                         # Docker stack
├── nginx/conf.d/                       # nginx config
├── sabredav/                           # DAV server
└── local-dev-2.sh                      # Dev environment + seeding
```

## Helpful Greps

```bash
# Find all API routes
grep -n "r\.\(Get\|Post\|Put\|Delete\|Route\)" backend/main.go

# Find TODOs/FIXMEs in code
grep -rn "TODO\|FIXME" backend/ frontend/src/ --include="*.go" --include="*.svelte"

# Find unwired handlers (imported in main.go?)
grep -n "import.*handlers" backend/main.go

# Shows all three booking creation handlers: user self-booking, admin walk-in, admin call/message-in
grep -rn "func.*Create.*Booking\|func.*WalkIn\|func.*Walk.*In\|POST.*booking" backend/handlers/ --include="*.go"

# Shows all three frontend booking flows: customer BookingFlow, admin walk-in modal, admin booking modal
grep -rln "BookingFlow\|WalkIn\|walk-in\|call.*in\|message.*in" frontend/src/lib/components/ --include="*.svelte"
F
# Find where each booking flow starts - API routes and page loads
grep -rn "booking.*create\|/api/bookings\|booking/POST\|booking/new" backend/ frontend/ --include="*.go" --include="*.ts"

# Find all booking status handling
grep -rn "booking_status\|in_progress\|confirmed\|pending" backend/handlers/

# Find frontend API calls
grep -rn "fetch.*\/api\/" frontend/src/ --include="*.svelte" --include="*.ts"

# Find auth-protected routes
grep -n "RequireAuth\|RequireAdmin" backend/main.go

# Find Svelte 5 reactive state
grep -n "\$state\|\$derived\|\$effect" frontend/src/ -r --include="*.svelte"

# Find SQL function definitions
grep -n "CREATE.*FUNCTION" init-scripts/init-script.sql

# Find specific handler implementation
grep -l "func.*Handler" backend/handlers/**/*.go

# Find transaction patterns
grep -rn "tx, err := db.DB.Begin" backend/handlers/

# Find refresh token handler (exists but not wired)
grep -n "RefreshTokenHandler" backend/handlers/auth/local.go

# Find notification handler (exists but not wired)
grep -n "func.*Notification" backend/handlers/notifications/notifications.go

# Find guest booking TODO in frontend
grep -n "TODO.*guest\|guest.*TODO" frontend/src/lib/components/admin/WalkInCreateModal.svelte

# Find console.logs to remove
grep -rn "console\.log" frontend/src/ --include="*.svelte" | grep -v node_modules
```

## Auth Flow

JWT in localStorage → decoded for role/user_id → profile fetch from `/api/user/profile`. Refresh logic exists but endpoint not wired. Roles: `unverified_email | verified_email | admin | guest | affiliate`

**Admin promotion**: Direct SQL only, no API endpoint: `UPDATE users SET account_role = 'admin' WHERE email = '...'`

**Validation rules**:
- Names: 1-50 chars, unicode letters/spaces/hyphen/apostrophe/dot only
- Phone: UK format, converted to E.164 (+44...)
- Email: standard format validation
- Age: Must be 16+ years old
- Login rate limit: 1 attempt per 5 seconds

## Booking Status Flow

```
pending → confirmed → in_progress → completed
                  ↘ client_cancelled | we_cancelled | no_show | re-schedule
```

TODO: `in_progress` should auto-infer by time OR manual "Begin" button (gray if >3hrs away).

## Scheduling

- `/api/scheduling/default-hours` - Weekly template
- `/api/scheduling/exceptional-groups` - Recurring exceptions (holidays)
- `/api/scheduling/working-hours` - Merged result (default + applied exceptions)
- `/api/scheduling/available-hours` - Slots minus bookings

All 3 booking flows (customer, call-in, walk-in) correctly use merged hours.

## Critical TODOs

**HIGH:**
- `BookingFlow.svelte:600` - `submitBooking()` logs only, needs `POST /api/bookings`
- `BookingCreateModal.svelte:224` - Remove `console.log(users)` debug
- `/api/users/guest` - Guest endpoint for walk-ins
- One-off custom services (single booking, no list add)
- One-off exceptional hours (single day, not recurring)
- Auto lunch protection (block if removes 1h lunch, 30min admin with warning)
- Walk-in slot blocking during intake
- Square payment integration
- GDPR export endpoint (`export_all_user_data()` SQL exists)
- Tax data export (admin, software-compatible format)

**MEDIUM:**
- Notifications UI (frontend panel for admin notifications)
- Notifications push (WebSocket/polling mechanism)
- User notifications (booking confirmations, reminders for customers)
- Refresh token endpoint (handler exists, not wired)
- Loyalty display component
- S3/R2 for portfolio images
- Prometheus metrics
- CI/CD (Gitea)

**NOT WIRED:**
- `handlers/auth/social.go`
- `handlers/admin/analytics.go`
- `handlers/portfolio/images.go`

## Dev Build Tag
Backend uses `go run -tags dev ./main.go` - check for dev-specific behavior.

## API JSON Examples

**Booking:** `{"start_time":"2025-01-15T10:00:00+00:00","service_ids":["abc123def456"],"notes":"optional"}`

**Service:** `{"name":"Classic Manicure","description":"...","price":25.00,"duration_minutes":45,"patch_test_duration_hours":0,"minimum_age_required":0}`

**Confirm booking:** `POST /api/admin/bookings/{id}/confirm` with body `{"serviceOverrides":[]}`

**Exceptional group:** `{"name":"Holiday","description":"...","hours":[{"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},...],"weekStarts":["2025-12-22"]}`

## Database Enums

```sql
account_role, account_type, booking_status, payment_type, payment_method, payment_status, admin_notification_reason
```

**Current `admin_notification_reason`**: `pending_booking | cancelled_booking | rescheduled_booking | 1_week_no_pay | 1_month_no_pay | affiliate_claim`

**Suggested additions**: `no_show`, `payment_failed`, `patch_test_due`, `loyalty_milestone`, `first_time_customer`, `vip_booking`, `inactive_customer`, `birthday_this_week`, `special_request`, `schedule_conflict`

## Key SQL Functions

`anonymize_user()`, `export_all_user_data()`, `delete_guest_user()`, `get_vat_return_data()`, `calculate_vat()`, `get_receipt_data()`

## Env Required

`JWT_SECRET_KEY`, `DATABASE_URL`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`

## Conventions

- IDs: 12-char generated strings (not UUIDs)
- Timezone: UK local throughout (`TZ=Europe/London`)
- Frontend: Svelte 5 runes (`$state`, `$derived`, `$effect`)
- Auth header: `Authorization: Bearer ${token}`
- All times in ISO format with timezone: `YYYY-MM-DDTHH:MM:SS±HH:MM`

## Seed Data (local-dev-2.sh)
- 18 users (1 admin, 17 regular)
- 6 services (manicure, gel, pedicure, express, removal, nail-art)
- 45 bookings (8 past, 3 today, 4 tomorrow, 30 future spread over 15 days)
- ~50% of upcoming bookings auto-confirmed
- 2 exceptional groups (November Break, Christmas Holiday)

## Code Patterns

**Transaction pattern** (used throughout):
```go
tx, err := db.DB.Begin(r.Context())
if err != nil { ... }
defer tx.Rollback(r.Context())
// ... queries using tx instead of db.DB ...
if err := tx.Commit(r.Context()); err != nil { ... }
```

**CardDAV sync**:
- On registration: creates vCard in SabreDAV
- On profile update: updates existing vCard via `updateCardDAV()` helper
- Uses internal HTTP calls to DAV server

**Role change detection**: `RefreshTokenHandler` checks if role changed since token issued - forces re-login if so.
