readme
This commit is contained in:
@@ -160,7 +160,7 @@ docker compose exec backend sh
|
||||
|
||||
| Feature | Backend | Frontend | Notes |
|
||||
|---------|---------|----------|-------|
|
||||
| JWT Authentication | ✅ | ✅ | Login, register, middleware; HS256, 30-day expiry |
|
||||
| JWT Authentication | ✅ | ✅ | Login, register, refresh, middleware; HS256, 30-day expiry, auto-refresh |
|
||||
| Booking CRUD | ✅ | ⚠️ | Backend complete; frontend submit is stub (logs only) |
|
||||
| Admin Endpoints | ✅ | ✅ | Services, users, today view, notifications |
|
||||
| Scheduling System | ✅ | ✅ | Default hours, exceptional hours, working/available hours |
|
||||
@@ -168,6 +168,7 @@ docker compose exec backend sh
|
||||
| Loyalty Backend | ✅ | ❌ | DB schema ready, no frontend component |
|
||||
| VAT System | ✅ | ❌ | `get_vat_return_data()`, `calculate_vat()` functions exist |
|
||||
| User Referrals | ✅ | ❌ | `user_referrals` table, backend logic exists |
|
||||
| Token Refresh | ✅ | ✅ | POST /api/refresh-token, auto-refresh in auth store |
|
||||
|
||||
### ⚠️ Partially Complete
|
||||
|
||||
@@ -185,7 +186,6 @@ docker compose exec backend sh
|
||||
| Social Auth | `handlers/auth/social.go` | OAuth integration placeholder |
|
||||
| Analytics | `handlers/admin/analytics.go` | Statistics/dashboard endpoint |
|
||||
| Portfolio Images | `handlers/portfolio/images.go` | Gallery management |
|
||||
| RefreshTokenHandler | `handlers/auth/local.go:322` | Token refresh endpoint |
|
||||
|
||||
### 🚧 Critical TODOs
|
||||
|
||||
@@ -203,7 +203,7 @@ docker compose exec backend sh
|
||||
|---------|-------------|
|
||||
| One-off Custom Services | Allow creating single-use services not in regular catalog |
|
||||
| One-off Exceptional Hours | Single-day overrides without creating a group |
|
||||
| Auto Lunch Protection | Prevent booking during lunch hours automatically |
|
||||
| S3/R2 Image Hosting | Portfolio image storage with admin upload |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
# 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.
|
||||
@@ -17,7 +17,7 @@
|
||||
- [x] Password hashing with bcrypt
|
||||
- [x] Middleware for auth/roles (`mw.RequireAuth`, `mw.RequireAdmin`)
|
||||
- [x] DB connection pooling
|
||||
- [ ] **Refresh token endpoint** - `RefreshTokenHandler` exists at `local.go:322`, NOT wired in router
|
||||
- [x] Refresh token endpoint - Wired to `POST /api/refresh-token`, auto-refresh in frontend
|
||||
- [x] Login rate limiting (1 attempt per 5 seconds)
|
||||
|
||||
#### Booking System
|
||||
@@ -333,7 +333,6 @@ admin_notification_reason: pending_booking | cancelled_booking | rescheduled_boo
|
||||
| **User notifications** | Notification system for regular users (booking confirmations, reminders) |
|
||||
| **Remove debug logs** | `console.log` in BookingFlow.svelte:600 and BookingCreateModal.svelte:224 |
|
||||
| **S3/R2 image hosting** | Portfolio image storage with admin upload |
|
||||
| **Refresh token endpoint** | Wire existing `RefreshTokenHandler` to router |
|
||||
| **Loyalty display component** | Show stamps in account/bookings |
|
||||
| **Email/SMS reminders** | Scheduled notification jobs |
|
||||
| **Prometheus metrics** | Monitoring integration |
|
||||
@@ -556,4 +555,4 @@ if err := tx.Commit(r.Context()); err != nil { /* handle error */ }
|
||||
### Build Tags
|
||||
- `db_dev.go` - Used with `-tags dev` for 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
|
||||
- `internal/dav/service_dev.go` / `service_prod.go` - Same pattern for DAV service
|
||||
|
||||
Reference in New Issue
Block a user