12 KiB
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
# 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.
docker compose up --build -d
postgres– PostgreSQL 17backend– Go API (exposed on:8080)sabredav– PHP‑based WebDAV (served by Nginx)nginx– Reverse‑proxy (HTTP on:80and 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.
chmod +x local-dev.sh
./local-dev.sh
The script performs the following steps:
- Docker checks – starts Docker if it isn't already running.
- PostgreSQL reset – removes the old volume and starts a fresh container.
- tmux session – creates
crussell-devwith panes:psqlconsole- Go server (
go run -tags dev ./main.go) - Svelte dev server (
npm run dev -- --host)
- 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
jqis required.
🔧 Building & Testing
Backend
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
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
# 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:
grep -rn "func.*Create.*Booking\|func.*WalkIn\|func.*Walk.*In\|POST.*booking" backend/handlers/ --include="*.go"
Frontend - All Booking Flow Components:
grep -rln "BookingFlow\|WalkIn\|walk-in\|call.*in\|message.*in" frontend/src/lib/components/ --include="*.svelte"
Combined - Single View of All Booking Flows:
grep -rn "CreateBooking\|CreateWalkIn\|BookingFlow\|submitBooking" backend/ frontend/ --include="*.go" --include="*.svelte"
Database Schema
All Enums:
grep -n "CREATE TYPE" init-scripts/init-script.sql
All Tables:
grep -n "CREATE TABLE" init-scripts/init-script.sql
All Functions:
grep -n "CREATE OR REPLACE FUNCTION" init-scripts/init-script.sql
Router Endpoints
All Wired Routes:
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
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)
grep -n "r\.Use\|r\.Group" backend/main.go
Authentication
JWT Middleware:
grep -rn "JWTMiddleware\|VerifyToken\|ParseToken" backend/ --include="*.go"
Protected Routes:
grep -n "r\.Group.*Auth" backend/main.go
Holiday/Exceptional Hours
Usage Across All Flows:
grep -rn "exceptional.*hours\|holiday.*hours\|getExceptional\|getHoliday" backend/handlers/ frontend/src/ --include="*.go" --include="*.svelte"
Notifications
Admin Notifications:
grep -rn "admin_notifications\|AdminNotification" backend/ --include="*.go"
Notification Reasons (Enum Values):
grep -A10 "admin_notification_reason" init-scripts/init-script.sql
GDPR Functions
Data Export/Anonymization:
grep -rn "anonymize_user\|export_all_user_data\|delete_guest_user" backend/ --include="*.go" init-scripts/
Frontend Debug Artifacts
Console Logs to Remove:
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!