validation Backend: - Add rate limiting middleware (mw/ratelimit.go) - in-memory per-IP limiter - Apply rate limits per endpoint group: - Public read-only: 120/min - Registration: 10/min - Portfolio filters: 60/min - Authenticated users: 120/min - Admin: none (trusted) - Add 256 char input length validation on portfolio endpoints - Validate filter categories exist in DB before querying - Secure GetImage endpoint: only allow UUID or numeric timestamp (15-20 digits) - Remove pattern-based image lookup to prevent enumeration - Add services validation: name (100), duration (1-480), patch test (0-168) Frontend: - Add maxlength=256 to portfolio tag/search inputs - Add maxlength to registration: name (50), email (255), phone (20), password (72) - Add maxlength=100 to service name input
377 lines
12 KiB
Markdown
377 lines
12 KiB
Markdown
# Crussell
|
||
|
||
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
|
||
|
||
```
|
||
Crussell/
|
||
├─ backend/ # Go 1.22 + chi router API
|
||
├─ frontend/ # SvelteKit 5 SPA (static build)
|
||
├─ sabredav/ # PHP + Composer for DAV
|
||
├─ nginx/ # Nginx reverse‑proxy for HTTP & HTTPS
|
||
├─ init-scripts/ # PostgreSQL init SQL
|
||
├─ compose.yml # Docker‑Compose definition
|
||
├─ local-dev.sh # Development helper using tmux
|
||
├─ local-dev-2.sh # Enhanced seeding script with more test data
|
||
└─ README.md
|
||
```
|
||
|
||
## ⚙️ Prerequisites
|
||
|
||
| Tool | Version |
|
||
|------|---------|
|
||
| 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.
|
||
|
||
## 📥 Getting Started
|
||
|
||
```bash
|
||
# Clone the repository
|
||
git clone http://git.popertots.com/popertots/Crussell.git
|
||
cd Crussell
|
||
|
||
# Copy the example environment file and edit it
|
||
cp .env.example .env
|
||
# Open .env and provide values for POSTGRES_*, JWT_SECRET_KEY, etc.
|
||
```
|
||
|
||
### Docker‑Compose
|
||
|
||
The simplest way to bring the whole stack up is with Docker‑Compose.
|
||
|
||
```bash
|
||
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`)
|
||
|
||
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.sh`
|
||
|
||
For a more interactive dev experience the repository ships a small helper script that launches Docker, starts a tmux session with three panes (PostgreSQL console, Go dev server, Svelte dev server) and seeds the database with an admin and a regular user plus a handful of sample services.
|
||
|
||
```bash
|
||
chmod +x local-dev.sh
|
||
./local-dev.sh
|
||
```
|
||
|
||
The script performs the following steps:
|
||
|
||
1. **Docker checks** – starts Docker if it isn't already running.
|
||
2. **PostgreSQL reset** – removes the old volume and starts a fresh container.
|
||
3. **tmux session** – creates `crussell-dev` with panes:
|
||
* `psql` console
|
||
* Go server (`go run -tags dev ./main.go`)
|
||
* Svelte dev server (`npm run dev -- --host`)
|
||
4. **Seeding** – creates an admin (`admin@example.com`) and a regular user (`user@example.com`), updates the admin role, and registers six example services.
|
||
|
||
> **Note**: The script uses a temporary shell script to perform the HTTP calls, so no external tooling like `jq` is required.
|
||
|
||
## 🔧 Building & Testing
|
||
|
||
### Backend
|
||
|
||
```bash
|
||
cd backend
|
||
# Build the binary
|
||
go build -o bin/backend ./main.go
|
||
```
|
||
|
||
The binary is then copied into the Docker image via the `Dockerfile`.
|
||
|
||
### Frontend
|
||
|
||
```bash
|
||
cd frontend
|
||
npm ci
|
||
npm run build # Production build (static)
|
||
npm run dev # Development server
|
||
```
|
||
|
||
### SabreDAV
|
||
|
||
SabreDAV is bundled with PHP‑FPM and Composer. The Docker image installs dependencies automatically during the container start‑up.
|
||
|
||
## 📂 Environment Variables
|
||
|
||
| Variable | Purpose | Example |
|
||
|----------|---------|---------|
|
||
| `POSTGRES_USER` | DB username | `myuser` |
|
||
| `POSTGRES_PASSWORD` | DB password | `mysecret` |
|
||
| `POSTGRES_DB` | DB name | `mydb` |
|
||
| `JWT_SECRET_KEY` | HMAC key for JWT (HS256) | `supersecret` |
|
||
| `SABRE_DAV_*` | Optional SabreDAV overrides | – |
|
||
|
||
Create a `.env` file in the project root based on the provided `.env.example`.
|
||
|
||
> **Note**: JWT tokens expire after 30 days. Login is rate-limited to 1 attempt per 5 seconds.
|
||
|
||
## 📊 Seeding Data
|
||
|
||
The `local-dev.sh` script automatically seeds:
|
||
|
||
* Admin user (`admin@example.com` / `password`)
|
||
* Regular user (`user@example.com` / `password`)
|
||
* Six example nail‑bar services
|
||
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ Implementation Status
|
||
|
||
### ✅ Complete
|
||
|
||
| 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 |
|
||
| Holiday Hours | ✅ | ✅ | Integrated in all 3 booking flows |
|
||
| 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 |
|
||
| Portfolio System | ✅ | ✅ | S3/R2 storage abstraction, tag-based filtering, category filters, admin upload, ?img= featured image param |
|
||
|
||
### ⚠️ Partially Complete
|
||
|
||
| Feature | Status | Details |
|
||
|---------|--------|---------|
|
||
| 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 |
|
||
|
||
### ❌ Not Wired (Handlers Exist)
|
||
|
||
| Handler | File | Notes |
|
||
|---------|------|-------|
|
||
| Social Auth | `handlers/auth/social.go` | OAuth integration placeholder |
|
||
| Analytics | `handlers/admin/analytics.go` | Statistics/dashboard endpoint |
|
||
|
||
### 🚧 Critical TODOs
|
||
|
||
| Location | Issue | Priority |
|
||
|----------|-------|----------|
|
||
| `BookingFlow.svelte:600` | `submitBooking()` only logs, needs POST implementation | High |
|
||
| `BookingCreateModal.svelte:224` | Remove `console.log(users)` debug statement | Low |
|
||
| `/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 |
|
||
|
||
### 📋 Feature Requests
|
||
|
||
| Feature | Description |
|
||
|---------|-------------|
|
||
| One-off Custom Services | Allow creating single-use services not in regular catalog |
|
||
| One-off Exceptional Hours | Single-day overrides without creating a group |
|
||
|
||
---
|
||
|
||
## 🔄 Booking Flows
|
||
|
||
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 | ✅ |
|
||
|
||
All three flows integrate with the **holiday/exceptional hours** system to prevent bookings during closed periods.
|
||
|
||
---
|
||
|
||
## 🔍 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)
|
||
|
||
**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
|
||
|
||
### Enums
|
||
|
||
| Enum | Values |
|
||
|------|--------|
|
||
| `account_role` | `user`, `admin` |
|
||
| `account_type` | `standard`, `vip`, `guest` |
|
||
| `booking_status` | `pending`, `confirmed`, `in_progress`, `completed`, `cancelled`, `no_show` |
|
||
| `payment_type` | `deposit`, `full`, `refund` |
|
||
| `payment_method` | `cash`, `card`, `bank_transfer`, `square` |
|
||
| `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` |
|
||
|
||
### Key Tables
|
||
|
||
| Table | Purpose |
|
||
|-------|---------|
|
||
| `users` | Customer and admin accounts |
|
||
| `services` | Salon service catalog |
|
||
| `bookings` | Appointment records |
|
||
| `booking_services` | Services per booking (many-to-many) |
|
||
| `payments` | Payment transactions |
|
||
| `working_hours` | Default weekly schedule |
|
||
| `exceptional_working_hours_groups` | Holiday/special schedules |
|
||
| `admin_notifications` | Admin alerts |
|
||
| `user_notification_preferences` | User notification preferences (email/sms/push) |
|
||
| `user_referrals` | Referral tracking |
|
||
|
||
### Validation Rules
|
||
|
||
| Field | Rules |
|
||
|-------|-------|
|
||
| Names | 1-50 chars, unicode letters/spaces/hyphen/apostrophe/dot |
|
||
| Phone | UK format → E.164 (+44...) |
|
||
| Age | Must be 16+ years |
|
||
| Login Rate Limit | 1 attempt per 5 seconds |
|
||
|
||
---
|
||
|
||
**Happy coding!**
|