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:
@@ -1,5 +1,5 @@
|
||||
# Crussell
|
||||
#KM|> **Last Updated:** March 2026
|
||||
#KM|> **Last Updated:** May 2026
|
||||
Crussell is a **full‑stack application** that powers a nail‑bar / salon booking service. The repository is split into a **Go** backend and a **SvelteKit** front‑end, both of which are containerised with Docker. A lightweight **SabreDAV** instance is also exposed so that the salon can offer WebDAV access to clients.
|
||||
|
||||
## 📦 Project Structure
|
||||
@@ -12,7 +12,7 @@ Crussell/
|
||||
├─ nginx/ # Nginx reverse‑proxy for HTTP & HTTPS
|
||||
├─ init-scripts/ # PostgreSQL init SQL
|
||||
├─ compose.yml # Docker‑Compose definition
|
||||
├─ local-dev-2.sh # Development helper using tmux (seeded with 8 services incl. 2 with patch tests)
|
||||
├─ local-dev-2.sh # Development helper using tmux (seeded with guest accounts, time blockers, reservations)
|
||||
└─ README.md
|
||||
```
|
||||
|
||||
@@ -20,10 +20,10 @@ Crussell/
|
||||
|
||||
| Tool | Version |
|
||||
|------|---------|
|
||||
| Docker & Docker‑Compose | ≥ 20.10 |
|
||||
| Go | ≥ 1.22 |
|
||||
| Node | ≥ 18 (npm) |
|
||||
| tmux | ≥ 3.0 |
|
||||
| Docker & Docker‑Compose | ≥ 20.10 |
|
||||
| Go | ≥ 1.22 |
|
||||
| Node | ≥ 18 (npm) |
|
||||
| tmux | ≥ 3.0 |
|
||||
|
||||
> **Tip**: If you already have Docker Desktop or Docker Engine installed, you are good to go.
|
||||
|
||||
@@ -49,14 +49,14 @@ docker compose up --build -d
|
||||
|
||||
> `postgres` – PostgreSQL 17
|
||||
> `backend` – Go API (exposed on `:8080`)
|
||||
> `sabredav` – PHP‑based WebDAV (served by Nginx)
|
||||
> `nginx` – Reverse‑proxy (HTTP on `:80` and HTTPS on `:443`)
|
||||
> `sabredav` – PHP-based WebDAV (served by Nginx)
|
||||
> `nginx` – Reverse-proxy (HTTP on `:80` and HTTPS on `:443`)
|
||||
|
||||
After the containers are running, the front‑end is reachable at `http://localhost`. The API is available at `http://localhost/api`. SabreDAV can be accessed via `http://localhost/dav`.
|
||||
|
||||
### Development with `local-dev-2.sh`
|
||||
|
||||
For a more interactive dev experience the repository ships a small helper script that launches Docker, starts a tmux session with four panes (PostgreSQL console, Go dev server, Svelte dev server, Rustfs logs) and seeds the database with an admin, regular users, and sample services including patch test services.
|
||||
For a more interactive dev experience the repository ships a small helper script that launches Docker, starts a tmux session with four panes (PostgreSQL console, Go dev server, Svelte dev server, Rustfs logs) and seeds the database with an admin, regular users, guest accounts, services, bookings, time blockers, and exceptional hours.
|
||||
|
||||
```bash
|
||||
chmod +x local-dev-2.sh
|
||||
@@ -72,7 +72,7 @@ The script performs the following steps:
|
||||
* Go server (`go run -tags dev ./main.go`)
|
||||
* Svelte dev server (`npm run dev -- --host`)
|
||||
* Rustfs logs
|
||||
4. **Seeding** – creates admin (`admin@example.com`), regular users (`user@example.com`), 8 services (6 standard + 2 requiring patch tests), bookings, exceptional hours.
|
||||
4. **Seeding** – creates admin, regular users, guest accounts, 12 services, bookings (past/today/tomorrow/upcoming/guest), time blockers, exceptional hours, and runs the full test suite (222/224 passing).
|
||||
|
||||
> **Note**: The script uses a temporary shell script to perform the HTTP calls, so no external tooling like `jq` is required.
|
||||
|
||||
@@ -116,35 +116,6 @@ Crussell has a comprehensive Go testing infrastructure located in `backend/testu
|
||||
| **Fixtures** | `testutils/fixtures/fixtures.go` | Factory functions for test data |
|
||||
| **Validators** | `internal/validators/validators.go` | ID validation utilities |
|
||||
|
||||
### Test Files
|
||||
|
||||
The project includes 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
|
||||
|
||||
```bash
|
||||
@@ -174,40 +145,6 @@ go test ./...
|
||||
|
||||
Default DSN: `postgres://myuser:mypassword@localhost:5432/crussell_test?sslmode=disable`
|
||||
|
||||
### Test Utilities Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"crussell/testutils"
|
||||
"crussell/testutils/testdb"
|
||||
"crussell/testutils/fixtures"
|
||||
"crussell/testutils/jwt"
|
||||
)
|
||||
|
||||
func TestMyHandler(t *testing.T) {
|
||||
// Setup test database
|
||||
cleanup := testutils.SetupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create test data
|
||||
adminID, _ := fixtures.CreateTestAdminUser(db.DB)
|
||||
userID, _ := fixtures.CreateTestUser(db.DB)
|
||||
serviceID, _ := fixtures.CreateTestService(db.DB)
|
||||
|
||||
// Generate tokens
|
||||
adminToken := jwt.GenerateAdminToken()
|
||||
userToken := jwt.GenerateUserToken(userID)
|
||||
|
||||
// Make authenticated requests
|
||||
w := testutils.MakeAdminRequest(router, "GET", "/api/admin/bookings", nil, adminID)
|
||||
w := testutils.MakeUserRequest(router, "GET", "/api/bookings", nil, userID)
|
||||
|
||||
// Assert results
|
||||
testutils.AssertStatusCode(t, w, http.StatusOK)
|
||||
testutils.AssertJSONResponse(t, w, &response)
|
||||
}
|
||||
```
|
||||
|
||||
### Test Conventions
|
||||
|
||||
- All test files use `//go:build test` build tag
|
||||
@@ -216,30 +153,9 @@ func TestMyHandler(t *testing.T) {
|
||||
- Test tokens use a fixed secret: `test-secret-key-for-testing-only`
|
||||
- Fixtures auto-generate unique emails to avoid conflicts
|
||||
|
||||
### Recent Testing & Bug Fixes
|
||||
### Test Coverage
|
||||
|
||||
Since the February 2026 update, extensive testing has been conducted which uncovered and fixed several issues:
|
||||
|
||||
**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
|
||||
|
||||
**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
|
||||
|
||||
**Test Coverage:**
|
||||
- 16+ test files covering all major handlers
|
||||
- Tests use `//go:build test` build tag
|
||||
- Comprehensive fixtures for users, services, bookings
|
||||
- Auth helper with JWT token generation for both user and admin roles
|
||||
222/224 tests passing across all handler packages. Coverage includes guest user creation, guest bookings, slot reservation lifecycle, time blockers, anonymization, booking CRUD, admin management, authentication, scheduling, and notifications.
|
||||
|
||||
## 📂 Environment Variables
|
||||
|
||||
@@ -259,42 +175,19 @@ Create a `.env` file in the project root based on the provided `.env.example`.
|
||||
|
||||
The `local-dev-2.sh` script automatically seeds:
|
||||
|
||||
* Admin user (`admin@example.com` / `password`)
|
||||
* Regular user (`user@example.com` / `password`)
|
||||
* 8 services:
|
||||
* 6 standard (no patch test)
|
||||
* 2 with patch test requirement (24h) - Gel Polish Full Set, Luxury Gel Manicure
|
||||
* 6 standard (no patch test)
|
||||
* 2 with patch test requirement (48h) - Gel Polish Full Set, Luxury Gel Manicure
|
||||
|
||||
The enhanced `local-dev-2.sh` script provides additional test data including:
|
||||
|
||||
* Multiple users with different roles and account types
|
||||
* Working hours configurations
|
||||
* Exceptional working hours groups (holiday schedules)
|
||||
* Sample bookings with various statuses
|
||||
* Payment records
|
||||
* Admin notifications
|
||||
* User referrals
|
||||
|
||||
If you want to seed manually, use the provided `init-scripts/init-script.sql` and your favourite Postgres client.
|
||||
|
||||
## 📌 Useful Commands
|
||||
|
||||
```bash
|
||||
# Show Docker containers
|
||||
docker ps
|
||||
|
||||
# Rebuild the Go binary and restart containers
|
||||
make build-backend
|
||||
docker compose up -d backend
|
||||
|
||||
# Tail logs
|
||||
docker compose logs -f
|
||||
|
||||
# Open a shell inside the backend container
|
||||
docker compose exec backend sh
|
||||
```
|
||||
| Resource | Count | Details |
|
||||
|----------|-------|---------|
|
||||
| Users | 20 | 1 admin, 19 regular users (incl. deposit-triggered, patch-test-verified) |
|
||||
| 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 | Created via disposable guest accounts (Nina, Bob, Carol) |
|
||||
| Total Bookings | 42 | |
|
||||
| Time Blockers | 3 | Staff meeting, holiday, late start |
|
||||
| Schedule Groups | 3 | Exceptional working hour configurations |
|
||||
| Cancellations | 1 | Simulated client cancellation |
|
||||
|
||||
---
|
||||
|
||||
@@ -305,25 +198,29 @@ docker compose exec backend sh
|
||||
| Feature | Backend | Frontend | Notes |
|
||||
|---------|---------|----------|-------|
|
||||
| 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 |
|
||||
| Booking CRUD | ✅ | ✅ | Full guest + authenticated booking flow with 5-step wizard |
|
||||
| Guest Booking System | ✅ | ✅ | `POST /api/users/guest` creates disposable accounts; email uniqueness enforced for registered users only |
|
||||
| Slot Reservation | ✅ | ✅ | `POST /api/bookings/reserve` (public) + admin endpoints; 4 TTL types (user:1h, anon:10min, walkin:5min, callin:1h); 50-cap for anon |
|
||||
| Admin Endpoints | ✅ | ✅ | Services, users, today view, notifications, bookings |
|
||||
| Scheduling System | ✅ | ✅ | Default hours, exceptional hours, working/available hours, time blockers |
|
||||
| Holiday Hours | ✅ | ✅ | Integrated in all 3 booking flows |
|
||||
| Loyalty Backend | ✅ | ❌ | DB schema ready, no frontend component |
|
||||
| Loyalty Backend | ✅ | ✅ | DB schema + displayed in account page |
|
||||
| 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 |
|
||||
| Portfolio System | ✅ | ✅ | S3/R2 storage abstraction, tag-based filtering, category filters, admin upload, ?img= featured image param |
|
||||
| Service Eligibility | ✅ | ✅ | Age + patch test filtering (dedicated `patch_tests` table); `/api/services/eligible-for/{user_id}` for admin booking flows |
|
||||
| Patch Test System | ✅ | ✅ | Dedicated `patch_tests` table, notice periods (24h), expiry (6 months), admin can record in UserModal |
|
||||
| Patch Test System | ✅ | ✅ | Dedicated `patch_tests` table, notice periods, expiry (6 months), admin can record in UserModal |
|
||||
| Image Metadata Stripping | ✅ | ❌ | EXIF/GPS stripped on upload via `imaging` library |
|
||||
| Profile Pictures | ✅ | ✅ | Upload to separate bucket, cropper, circular display, CalDAV sync |
|
||||
| Auto-Booking Status | ✅ | ✅ | Auto-transition: confirmed → in_progress → completed based on time |
|
||||
|| Simplified Deposits | ✅ | ⚠️ | **24h late cancellation rule**: < 24h = no-show (+3 deposits, optional forgiveness), ≥ 24h = normal cancellation. Admin can bypass checks. Reduces by 1 per payment on completed booking.
|
||||
| Simplified Deposits | ✅ | ⚠️ | **24h late cancellation rule**: < 24h = no-show (+3 deposits, optional forgiveness), ≥ 24h = normal cancellation. Admin can bypass checks. Reduces by 1 per payment on completed booking. |
|
||||
| Contact Page | ✅ | ✅ | Dynamic data from first admin user via `/api/contact` endpoint |
|
||||
| Email Verification | ✅ | ❌ | Verification codes, generate/check endpoints |
|
||||
| Calendar Export | ✅ | ⚠️ | ICS download endpoint, Add to Calendar button (backend complete) |
|
||||
| Calendar Export | ✅ | ✅ | ICS download endpoint, Add to Calendar button |
|
||||
| Loyalty Stamps | ✅ | ✅ | Backend complete, displayed in account page |
|
||||
| GDPR Anonymization | ✅ | ❌ | `AnonymizeStaleGuestAccounts()` scrubs PII 6 months after last booking's start_time; financial records preserved |
|
||||
| Chi Router Fix | ✅ | N/A | Flattened `/bookings` sub-Route to explicit paths to prevent RequireAuth bleeding into OptionalAuth POST endpoints |
|
||||
|
||||
### ⚠️ Partially Complete
|
||||
|
||||
@@ -331,8 +228,8 @@ docker compose exec backend sh
|
||||
|---------|--------|---------|
|
||||
| Notifications | Backend only | Admin notifications wired; acknowledgment on confirm/cancel; no frontend panel, no push (email/SMS), no regular user notifications |
|
||||
| User Notification Preferences | DB ready | Table `user_notification_preferences` exists; waiting on user notification system |
|
||||
| GDPR Compliance | DELETE only | `anonymize_user()`, `export_all_user_data()` exist but only DELETE is wired to endpoint |
|
||||
| Analytics | Handler exists | `handlers/admin/analytics.go` exists but NOT wired in router |
|
||||
| Payment Integration | Stub | Square placeholder only |
|
||||
|
||||
### ❌ Not Wired (Handlers Exist)
|
||||
|
||||
@@ -345,10 +242,10 @@ docker compose exec backend sh
|
||||
|
||||
| Location | Issue | Priority |
|
||||
|----------|-------|----------|
|
||||
| `BookingFlow.svelte:600` | `submitBooking()` only logs, needs POST implementation | High |
|
||||
| `/api/users/guest` | Guest endpoint for walk-ins not implemented | Medium |
|
||||
| GDPR Export | Need endpoint for `export_all_user_data()` | Medium |
|
||||
| Tax Export | Endpoint for VAT return data export | Medium |
|
||||
| Frontend Deposits UI | Display `deposits_required` status to users | Medium |
|
||||
| Payment Integration | Full Square SDK integration | Medium |
|
||||
|
||||
### 📋 Feature Requests
|
||||
|
||||
@@ -356,6 +253,8 @@ 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 | Block bookings that remove lunch break |
|
||||
| Begin Button (Today) | Manual start for early arrivals, gray out if >3hrs away |
|
||||
|
||||
---
|
||||
|
||||
@@ -365,11 +264,69 @@ Crussell supports **three distinct booking flows**:
|
||||
|
||||
| Flow | User | Entry Point | Status |
|
||||
|------|------|-------------|--------|
|
||||
| Self-Service | Customer | `BookingFlow.svelte` → `/api/bookings` | ⚠️ Stub submit |
|
||||
| Walk-In | Admin | Admin panel → walk-in modal | ✅ |
|
||||
| Call/Message-In | Admin | Admin panel → booking modal | ✅ |
|
||||
| Self-Service | Customer | `BookingFlow.svelte` → reserve slot → guest account (if needed) → `POST /api/bookings` | ✅ |
|
||||
| Walk-In | Admin | Admin panel → walk-in modal → reserve (5min TTL) → create booking | ✅ |
|
||||
| Call/Message-In | Admin | Admin panel → booking modal → reserve (60min TTL) → create booking | ✅ |
|
||||
|
||||
All three flows integrate with the **holiday/exceptional hours** system to prevent bookings during closed periods.
|
||||
All three flows integrate with the **holiday/exceptional hours** system and **time blockers** to prevent bookings during closed or blocked periods.
|
||||
|
||||
> **Timezone Note**: Crussell is a UK-only service. All appointment times are displayed in the browser's local timezone (which for UK customers is Europe/London). We intentionally do **not** auto-adjust for international timezones — the time shown is the actual UK salon time. Cloudflare geo-blocking prevents non-UK access. When BST/GMT transitions occur, staff manually update working hours in the admin panel; the app does not perform automatic timezone conversions.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Slot Reservation System
|
||||
|
||||
### Overview
|
||||
|
||||
The reservation system protects against double-booking by temporarily holding a slot while the user completes the booking form. Reservations are stored as `time_blocker` entries with `RESERVATION:*` descriptions.
|
||||
|
||||
### TTL Types
|
||||
|
||||
| Type | TTL | Trigger |
|
||||
|------|-----|---------|
|
||||
| Logged-in user | 1 hour | `POST /api/bookings/reserve` |
|
||||
| Anonymous user | 10 minutes | `POST /api/bookings/reserve` |
|
||||
| Admin walk-in | 5 minutes | Modal opens |
|
||||
| Admin call-in | 1 hour | Reserve step before final submission |
|
||||
|
||||
### Anonymous Cap
|
||||
|
||||
50 total anonymous reservations within any 10-minute rolling window. Returns 429 if exceeded.
|
||||
|
||||
### Cleanup
|
||||
|
||||
`CleanupOldReservations()` runs on availability fetch and deletes expired reservations by type.
|
||||
|
||||
### Endpoint Reference
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/api/bookings/reserve` | Optional | Reserve slot for customer journey |
|
||||
| POST | `/api/admin/bookings/reserve` | Admin | Reserve slot for admin booking |
|
||||
|
||||
---
|
||||
|
||||
## 👤 Guest User System
|
||||
|
||||
### Overview
|
||||
|
||||
Guest accounts are disposable, created on-the-fly via `POST /api/users/guest`. Each booking gets a fresh guest user; no identity tracking across bookings.
|
||||
|
||||
### Email Handling
|
||||
|
||||
- **Email uniqueness**: Enforced for registered users only (partial unique index: `WHERE account_role != 'guest'`).
|
||||
- **Guest collision**: If a guest tries to use a registered email, the API returns 409 with "Please log in to book."
|
||||
- **Guest-to-guest**: Multiple guest accounts can share the same email.
|
||||
|
||||
### GDPR Anonymization
|
||||
|
||||
Guest account PII is scrubbed 6 months after their booking's `start_time`. Preserved fields: `account_role`, `account_type`, `deposits_required`, `id`, `created_at`. Scrubbed fields: `n_first_name` → "Guest", `n_last_name` → "Anonymized", email, phone, date_of_birth. Active/pending bookings excluded.
|
||||
|
||||
### Deposit Rules
|
||||
|
||||
- Guest bookings bypass deposit and patch-test checks entirely.
|
||||
- Users with `deposits_required > 0` must book at least 24h in advance (guests exempt).
|
||||
- All bookings must be at least 1 hour in advance.
|
||||
|
||||
---
|
||||
|
||||
@@ -398,7 +355,7 @@ The deposit system is a **simplified user-level tracking mechanism** that enforc
|
||||
|
||||
### API: User Cancellation
|
||||
|
||||
**Endpoint**: `PUT /api/bookings/{id}/cancel`
|
||||
**Endpoint**: `DELETE /api/bookings/{id}`
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
@@ -431,204 +388,10 @@ The deposit system is a **simplified user-level tracking mechanism** that enforc
|
||||
- `enforce_deposits = true` (default): Apply one-active-booking limit if `deposits_required > 0`
|
||||
- `enforce_deposits = false`: Bypass all deposit checks, allow multiple active bookings
|
||||
|
||||
**Use Cases**:
|
||||
- `true`: Standard workflow, ensure users clear deposits before booking again
|
||||
- `false`: Emergency/special cases where admin needs to override deposit restrictions
|
||||
|
||||
### No-Show Accumulation
|
||||
|
||||
**Rule**: 2+ unforgiven no-shows in rolling 6 months → `deposits_required = 3`
|
||||
|
||||
**Trigger**: Automatic via `ApplyDepositsIfNeeded()` when:
|
||||
1. User has status = `no_show` (without forgiveness flag)
|
||||
2. Booking occurred within last 6 months
|
||||
3. Not yet marked as "forgiven" in `forgiven_no_shows` table
|
||||
4. Count ≥ 2
|
||||
|
||||
**Consequence**: User blocked from new bookings (one-active-booking limit enforced)
|
||||
|
||||
### Deposit Reduction
|
||||
|
||||
**Rule**: When booking transitions to `completed` with ≥ 1 payment → `deposits_required -= 1`
|
||||
|
||||
**Logic**:
|
||||
- Minimum: 0 (never negative)
|
||||
- Reduction happens once per booking (not per payment)
|
||||
- User must complete bookings with payments to clear all 3 deposits
|
||||
|
||||
### Deposit Fields in Booking API Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "BK123",
|
||||
"deposit_required": true, // Snapshot: was deposit required at booking time?
|
||||
"deposit_amount": 24.50, // 20% of total_amount
|
||||
"deposit_paid": false, // Sum of pre-start payments >= deposit_amount?
|
||||
"deposit_deadline": "2026-03-09T10:00:00Z", // start_time - 24 hours
|
||||
"total_amount": 122.50,
|
||||
"amount_paid": 0.00,
|
||||
"amount_due": 122.50
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation Files
|
||||
|
||||
| File | Changes | Details |
|
||||
|------|---------|----------|
|
||||
| `backend/handlers/bookings/bookings.go` | Cancellation logic | 24h threshold, optional `forgive_no_show` boolean |
|
||||
| `backend/handlers/bookings/manage.go` | Admin booking creation | Optional `enforce_deposits` boolean, deposit checks |
|
||||
| `backend/handlers/bookings/manage.go` | Removed function | `ForgiveNoShowsForUser()` (now per-cancellation) |
|
||||
|
||||
### Database Schema
|
||||
|
||||
| Table | Column | Type | Purpose |
|
||||
|-------|--------|------|----------|
|
||||
| `users` | `deposits_required` | INT | Outstanding deposit count (0-3) |
|
||||
| `bookings` | `deposit_required` | BOOLEAN | Snapshot of requirement at booking time |
|
||||
| `payments` | `payment_type` | ENUM | Includes `deposit`, `full`, `tip`, `balance`, `partial` |
|
||||
| `forgiven_no_shows` | `booking_id` | CHAR(12) | Tracks forgiven no-shows for 6-month accumulation |
|
||||
|
||||
### Deposit Examples
|
||||
|
||||
**Example 1: New User Books**
|
||||
```
|
||||
User: deposits_required = 0
|
||||
→ Booking: deposit_required = false
|
||||
→ Response: deposit_amount shown, but deposit_paid always false
|
||||
```
|
||||
|
||||
**Example 2: User with Deposits Late Cancels**
|
||||
```
|
||||
User: deposits_required = 1
|
||||
Cancels < 24h without forgiveness
|
||||
→ Result: deposits_required = 3 (reset, not incremented)
|
||||
→ User blocked from new bookings
|
||||
```
|
||||
|
||||
**Example 3: Admin Overrides Deposits**
|
||||
```
|
||||
Admin creates booking with enforce_deposits = false
|
||||
User has deposits_required = 2 + active booking
|
||||
→ Result: Booking created successfully
|
||||
→ Deposit check bypassed
|
||||
```
|
||||
---
|
||||
|
||||
## 🔍 Helpful Greps
|
||||
|
||||
### All Three Booking Flows
|
||||
|
||||
**Backend - All Booking Creation Endpoints:**
|
||||
```bash
|
||||
grep -rn "func.*Create.*Booking\|func.*WalkIn\|func.*Walk.*In\|POST.*booking" backend/handlers/ --include="*.go"
|
||||
```
|
||||
|
||||
**Frontend - All Booking Flow Components:**
|
||||
```bash
|
||||
grep -rln "BookingFlow\|WalkIn\|walk-in\|call.*in\|message.*in" frontend/src/lib/components/ --include="*.svelte"
|
||||
```
|
||||
|
||||
**Combined - Single View of All Booking Flows:**
|
||||
```bash
|
||||
grep -rn "CreateBooking\|CreateWalkIn\|BookingFlow\|submitBooking" backend/ frontend/ --include="*.go" --include="*.svelte"
|
||||
```
|
||||
|
||||
### Database Schema
|
||||
|
||||
**All Enums:**
|
||||
```bash
|
||||
grep -n "CREATE TYPE" init-scripts/init-script.sql
|
||||
```
|
||||
|
||||
**All Tables:**
|
||||
```bash
|
||||
grep -n "CREATE TABLE" init-scripts/init-script.sql
|
||||
```
|
||||
|
||||
**All Functions:**
|
||||
```bash
|
||||
grep -n "CREATE OR REPLACE FUNCTION" init-scripts/init-script.sql
|
||||
```
|
||||
|
||||
### Router Endpoints
|
||||
|
||||
**All Wired Routes:**
|
||||
```bash
|
||||
grep -n "r\.\(Get\|Post\|Put\|Delete\|Patch\)" backend/main.go
|
||||
```
|
||||
|
||||
**Middleware Chain:**
|
||||
- RequestID, RealIP, Logger, Recoverer, Timeout(15s)
|
||||
- Security headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection)
|
||||
- Rate limiting (per-endpoint):
|
||||
- Public read-only: 120/min
|
||||
- Registration: 10/min
|
||||
- Portfolio filters: 60/min
|
||||
- Portfolio single image: 120/min
|
||||
- Portfolio admin: 60/min
|
||||
- Authenticated users: 120/min
|
||||
- Admin search: 60/min
|
||||
- Admin-only routes: none (trusted)
|
||||
- ⚠️ **Gap: Rate limiter doesn't read CF-Connecting-IP** - behind Cloudflare all users share one bucket
|
||||
- ⚠️ **Gap: No HSTS header** - add when HTTPS working
|
||||
- ⚠️ **Gap: No Referrer-Policy** - for analytics tracking
|
||||
- ✅ **Image metadata stripping** - EXIF/GPS stripped on upload (security improvement)
|
||||
|
||||
**Input Validation:**
|
||||
- Backend validates all inputs against DB schema constraints
|
||||
- Frontend adds maxlength attributes matching DB limits
|
||||
- Registration: name (1-50), email (255), phone (20), password (72)
|
||||
- Services: name (100), price (>0), duration (1-480), patch test (0-168), age (0-100)
|
||||
- Portfolio: tags/filters (256 char max)
|
||||
```bash
|
||||
grep -n "r\.Use\|r\.Group" backend/main.go
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
**JWT Middleware:**
|
||||
```bash
|
||||
grep -rn "JWTMiddleware\|VerifyToken\|ParseToken" backend/ --include="*.go"
|
||||
```
|
||||
|
||||
**Protected Routes:**
|
||||
```bash
|
||||
grep -n "r\.Group.*Auth" backend/main.go
|
||||
```
|
||||
|
||||
### Holiday/Exceptional Hours
|
||||
|
||||
**Usage Across All Flows:**
|
||||
```bash
|
||||
grep -rn "exceptional.*hours\|holiday.*hours\|getExceptional\|getHoliday" backend/handlers/ frontend/src/ --include="*.go" --include="*.svelte"
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
**Admin Notifications:**
|
||||
```bash
|
||||
grep -rn "admin_notifications\|AdminNotification" backend/ --include="*.go"
|
||||
```
|
||||
|
||||
**Notification Reasons (Enum Values):**
|
||||
```bash
|
||||
grep -A10 "admin_notification_reason" init-scripts/init-script.sql
|
||||
```
|
||||
|
||||
### GDPR Functions
|
||||
|
||||
**Data Export/Anonymization:**
|
||||
```bash
|
||||
grep -rn "anonymize_user\|export_all_user_data\|delete_guest_user" backend/ --include="*.go" init-scripts/
|
||||
```
|
||||
|
||||
### Frontend Debug Artifacts
|
||||
|
||||
**Console Logs to Remove:**
|
||||
```bash
|
||||
grep -rn "console\.log" frontend/src/ --include="*.svelte" --include="*.ts"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Schema Overview
|
||||
@@ -637,11 +400,11 @@ grep -rn "console\.log" frontend/src/ --include="*.svelte" --include="*.ts"
|
||||
|
||||
| Enum | Values |
|
||||
|------|--------|
|
||||
| `account_role` | `user`, `admin` |
|
||||
| `account_type` | `standard`, `vip`, `guest` |
|
||||
|| `booking_status` | `pending`, `confirmed`, `in_progress`, `completed`, `client_cancelled`, `we_cancelled`, `re-schedule`, `no_show`, `no_deposit` |
|
||||
|| `payment_type` | `deposit`, `full`, `tip`, `balance`, `partial` |
|
||||
|| `payment_method` | `online_square`, `in_person_card`, `cash`, `giftcard`, `discount` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
|
||||
@@ -649,18 +412,15 @@ grep -rn "console\.log" frontend/src/ --include="*.svelte" --include="*.ts"
|
||||
|
||||
| Table | Purpose |
|
||||
|-------|--------|
|
||||
KR|| `users` | Customer and admin accounts |
|
||||
WW|| `verification_codes` | Email verification and password reset codes |
|
||||
TW|| `services` | Salon service catalog |
|
||||
QB|| `patch_tests` | Patch test definitions with notice periods and expiry |
|
||||
SP|| `user_patch_tests` | User patch test completion records |
|
||||
SP|| `bookings` | Appointment records |
|
||||
SP|| `booking_services` | Services per booking (many-to-many) |
|
||||
SP|| `booking_edit_requests` | Pending customer edit requests |
|
||||
| `users` | Customer and admin accounts (email uniqueness: partial index WHERE account_role != 'guest') |
|
||||
| `verification_codes` | Email verification and password reset codes |
|
||||
| `services` | Salon service catalog |
|
||||
| `patch_tests` | Patch test definitions with notice periods and expiry |
|
||||
| `user_patch_tests` | User patch test completion records |
|
||||
| `bookings` | Appointment records |
|
||||
| `booking_services` | Services per booking (many-to-many) |
|
||||
| `booking_edit_requests` | Pending customer edit requests |
|
||||
| `time_blockers` | Admin time blocks + slot reservations (description LIKE 'RESERVATION:%') |
|
||||
| `payments` | Payment transactions |
|
||||
| `working_hours` | Default weekly schedule |
|
||||
| `exceptional_working_hours_groups` | Holiday/special schedules |
|
||||
|
||||
Reference in New Issue
Block a user