Two-tier notification system: new_booking (all public bookings) + pending_booking (notes/today). Priority-sorted queue, unread count polling, enriched responses with user_name/booking_start_time. Fix critical bug: edit_requested cleanup was broken (wrong reason string in 3 handlers). Add 15 new tests covering priority ordering, enrichment, and notification creation flows. Update Admin Manual, Technical Manual, and gap backlog docs.
1094 lines
56 KiB
Bash
Executable File
1094 lines
56 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
|
|
SESSION_NAME="crussell-dev"
|
|
SEED_SCRIPT="/tmp/seed_data.sh"
|
|
|
|
setopt NO_UNSET
|
|
setopt PIPE_FAIL
|
|
setopt ERR_EXIT
|
|
|
|
# --- UI Helpers ---
|
|
C_RESET=$'\033[0m'
|
|
C_GREEN=$'\033[32m'
|
|
C_RED=$'\033[31m'
|
|
C_BLUE=$'\033[34m'
|
|
C_YELLOW=$'\033[33m'
|
|
|
|
log_info() { echo "🔹 $1" }
|
|
log_success() { echo "✅ $1" }
|
|
log_error() { echo "❌ $1" }
|
|
log_step() { echo "▶️ $1" }
|
|
|
|
# --- 1. Environment Setup ---
|
|
if [ -f .env ]; then
|
|
set -a
|
|
source .env
|
|
set +a
|
|
log_success "Loaded environment variables"
|
|
else
|
|
log_error ".env file not found!"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 2. Docker Checks ---
|
|
if ! docker info > /dev/null 2>&1; then
|
|
log_info "Docker daemon not running. Starting..."
|
|
sudo systemctl start docker
|
|
sleep 2
|
|
if ! docker info > /dev/null 2>&1; then
|
|
log_error "Failed to start Docker."
|
|
exit 1
|
|
fi
|
|
log_success "Docker started"
|
|
fi
|
|
|
|
# --- 3. Database Reset ---
|
|
log_step "Resetting PostgreSQL..."
|
|
docker compose down -v postgres > /dev/null 2>&1
|
|
docker compose up postgres -d > /dev/null 2>&1
|
|
log_success "PostgreSQL reset complete"
|
|
|
|
# --- 3a. Wait for PostgreSQL to be ready ---
|
|
log_step "Waiting for PostgreSQL to initialize..."
|
|
MAX_STARTUP_ATTEMPTS=30
|
|
STARTUP_ATTEMPT=0
|
|
while [ $STARTUP_ATTEMPT -lt $MAX_STARTUP_ATTEMPTS ]; do
|
|
if docker exec postgres psql -U myuser -d mydb -c "SELECT 1;" > /dev/null 2>&1; then
|
|
log_success "PostgreSQL is ready"
|
|
break
|
|
fi
|
|
STARTUP_ATTEMPT=$((STARTUP_ATTEMPT+1))
|
|
sleep 1
|
|
done
|
|
if [ $STARTUP_ATTEMPT -eq $MAX_STARTUP_ATTEMPTS ]; then
|
|
log_error "PostgreSQL failed to start after $MAX_STARTUP_ATTEMPTS attempts"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 3a-extra. Additional wait for full PostgreSQL initialization ---
|
|
log_step "Allowing PostgreSQL to fully initialize..."
|
|
sleep 5
|
|
|
|
# --- 3b. Create Test Database ---
|
|
log_step "Setting up test database (crussell_test)..."
|
|
docker exec postgres psql -U myuser -d mydb -c "CREATE DATABASE crussell_test;" 2>&1 || log_info "Test database may already exist"
|
|
sleep 2
|
|
|
|
# --- 3c. Seed Test Database Schema ---
|
|
log_step "Seeding test database schema..."
|
|
docker exec -i postgres psql -U myuser -d crussell_test < init-scripts/init-script.sql 2>&1 | head -5 || true
|
|
log_success "Test database schema seeded"
|
|
|
|
# --- 3d. Verify test database is ready ---
|
|
log_step "Verifying test database readiness..."
|
|
MAX_ATTEMPTS=10
|
|
ATTEMPT=0
|
|
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
if docker exec postgres psql -U myuser -d crussell_test -c "SELECT 1;" > /dev/null 2>&1; then
|
|
log_success "Test database ready"
|
|
break
|
|
fi
|
|
ATTEMPT=$((ATTEMPT+1))
|
|
sleep 1
|
|
done
|
|
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
|
|
log_error "Test database failed to initialize after $MAX_ATTEMPTS attempts"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 3e. Rustfs (wipe data for fresh start) ---
|
|
log_step "Wiping Rustfs (S3 storage)..."
|
|
docker compose stop rustfs > /dev/null 2>&1 || true
|
|
docker compose rm -f rustfs > /dev/null 2>&1 || true
|
|
docker volume rm crussell_rustfs_data > /dev/null 2>&1 || true
|
|
docker compose up rustfs -d > /dev/null 2>&1
|
|
log_success "Rustfs wiped and restarted"
|
|
sleep 2
|
|
|
|
# --- 3f. SabreDAV (start early to create tables) ---
|
|
log_step "Starting SabreDAV (CardDAV/CalDAV)..."
|
|
docker compose up sabredav -d > /dev/null 2>&1
|
|
log_success "SabreDAV started"
|
|
sleep 3
|
|
|
|
# --- 4. Tmux Session Setup ---
|
|
if tmux has-session -t $SESSION_NAME 2>/dev/null; then
|
|
log_info "Killing existing tmux session..."
|
|
tmux kill-session -t $SESSION_NAME
|
|
fi
|
|
|
|
log_step "Starting tmux session '$SESSION_NAME'..."
|
|
tmux new-session -d -s $SESSION_NAME -n "Workspace"
|
|
|
|
# Pass env vars to tmux session (must be after session creation)
|
|
tmux set-environment -t $SESSION_NAME POSTGRES_USER "$POSTGRES_USER"
|
|
tmux set-environment -t $SESSION_NAME POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
|
|
tmux set-environment -t $SESSION_NAME POSTGRES_DB "$POSTGRES_DB"
|
|
tmux set-environment -t $SESSION_NAME POSTGRES_HOST "$POSTGRES_HOST"
|
|
tmux set-environment -t $SESSION_NAME POSTGRES_PORT "$POSTGRES_PORT"
|
|
tmux set-environment -t $SESSION_NAME JWT_SECRET_KEY "$JWT_SECRET_KEY"
|
|
tmux set-environment -t $SESSION_NAME S3_ENDPOINT "$S3_ENDPOINT"
|
|
tmux set-environment -t $SESSION_NAME S3_PUBLIC_URL "$S3_PUBLIC_URL"
|
|
tmux set-environment -t $SESSION_NAME S3_ACCESS_KEY "$S3_ACCESS_KEY"
|
|
tmux set-environment -t $SESSION_NAME S3_SECRET_KEY "$S3_SECRET_KEY"
|
|
tmux set-environment -t $SESSION_NAME S3_BUCKET "$S3_BUCKET"
|
|
tmux set-environment -t $SESSION_NAME AWS_REGION "$AWS_REGION"
|
|
tmux set-environment -t $SESSION_NAME VITE_BACKEND_URL "$VITE_BACKEND_URL"
|
|
|
|
# Pane 0: Database
|
|
tmux send-keys -t $SESSION_NAME 'docker exec -it postgres psql -U myuser -d mydb -c "SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY table_name;"'
|
|
tmux select-pane -t $SESSION_NAME:0.0 -T "DB"
|
|
|
|
# Pane 1: Backend (Split Horizontally)
|
|
tmux split-window -v -t $SESSION_NAME
|
|
tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd backend && go run -tags dev ./main.go" Enter
|
|
tmux select-pane -t $SESSION_NAME:0.1 -T "Backend"
|
|
|
|
# Pane 2: Frontend (Split Vertically from Backend)
|
|
tmux split-window -h -t $SESSION_NAME:0.1
|
|
tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd frontend && npm run dev -- --host" Enter
|
|
tmux select-pane -t $SESSION_NAME:0.2 -T "Frontend"
|
|
|
|
# Pane 3: Rustfs (S3-compatible storage) - Split from Frontend
|
|
tmux split-window -v -t $SESSION_NAME:0.2
|
|
tmux send-keys -t $SESSION_NAME "docker logs -f rustfs" Enter
|
|
tmux select-pane -t $SESSION_NAME:0.3 -T "Rustfs"
|
|
|
|
# Layout configuration
|
|
tmux select-layout -t $SESSION_NAME even-vertical
|
|
tmux select-pane -t $SESSION_NAME:0.0
|
|
|
|
# --- 5. Seed Script Generation ---
|
|
log_step "Generating seed script..."
|
|
|
|
cat > $SEED_SCRIPT << 'SEED_EOF'
|
|
#!/bin/bash
|
|
|
|
# --- Config ---
|
|
ADMIN_EMAIL="admin@example.com"
|
|
ADMIN_PASS="password"
|
|
USER_EMAIL="user@example.com"
|
|
USER_PASS="password"
|
|
BASE_URL="http://localhost:8080/api"
|
|
|
|
# Safe booking times — mid-morning to early afternoon to avoid closing-hour conflicts.
|
|
# Adjust these if your business hours differ.
|
|
SLOT_A="10:00:00"
|
|
SLOT_B="11:30:00"
|
|
SLOT_C="13:00:00"
|
|
SLOT_D="14:30:00"
|
|
SLOT_E="12:15:00" # Used only for pending demo bookings — avoids conflicts with A-D rotation
|
|
|
|
# --- Formatting ---
|
|
C_RESET=$'\033[0m'
|
|
C_GREEN=$'\033[32m'
|
|
C_RED=$'\033[31m'
|
|
C_BLUE=$'\033[34m'
|
|
C_YELLOW=$'\033[33m'
|
|
|
|
# --- Global for ID Capture ---
|
|
LAST_BOOKING_ID=""
|
|
|
|
# --- Helper: API Request ---
|
|
# Returns the root-level "id" from the response body on success.
|
|
api_post() {
|
|
local url="$1"
|
|
local data="$2"
|
|
local desc="$3"
|
|
local token="$4"
|
|
|
|
local curl_opts=(-s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json')
|
|
[[ -n "$token" ]] && curl_opts+=(-H "Authorization: Bearer $token")
|
|
[[ -n "$data" ]] && curl_opts+=(-d "$data")
|
|
|
|
local response=$(curl "${curl_opts[@]}" "$url")
|
|
local http_code=$(echo "$response" | tail -n1)
|
|
local body=$(echo "$response" | sed '$d')
|
|
|
|
if [[ "$http_code" =~ ^2 ]]; then
|
|
# Strip nested "user" object so we reliably get the root "id" (UUID)
|
|
local extracted
|
|
extracted=$(echo "$body" | tr -d '\n' | sed 's/"user":{[^}]*}//' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | tr -d '\r\n ')
|
|
# Only emit the value if it looks like a UUID (hex + hyphens, 8+ chars)
|
|
if [[ "$extracted" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
echo "$extracted"
|
|
fi
|
|
return 0
|
|
else
|
|
printf "${C_RED}❌ Failed: %s (HTTP %s)${C_RESET}\n" "$desc" "$http_code"
|
|
printf " Request: %s\n" "$data"
|
|
printf " Response: %s\n" "$body"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# --- Helper: Login and return token ---
|
|
login() {
|
|
local email="$1" pass="$2"
|
|
curl -s -X POST -H 'Content-Type: application/json' \
|
|
-d "{\"email\":\"$email\",\"password\":\"$pass\"}" \
|
|
"$BASE_URL/login" \
|
|
| tr -d '\r\n\t ' \
|
|
| sed -n 's/.*"token":"\([^"]*\)".*/\1/p'
|
|
}
|
|
|
|
wait_for_backend() {
|
|
echo -ne "⏳ Waiting for backend..."
|
|
for ((i=1; i<=60; i++)); do
|
|
if curl -s --connect-timeout 2 http://localhost:8080/api/register > /dev/null 2>&1; then
|
|
echo -e "\r⏳ Waiting for backend... ${C_GREEN}Ready!${C_RESET}"
|
|
return 0
|
|
fi
|
|
echo -n "."
|
|
sleep 1
|
|
done
|
|
echo -e "\r⏳ Waiting for backend... ${C_RED}Timed out${C_RESET}"
|
|
exit 1
|
|
}
|
|
|
|
# --- Main Execution ---
|
|
wait_for_backend
|
|
|
|
# ===========================================================================
|
|
# 1. REGISTER USERS
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}👤 Registering Users...${C_RESET}"
|
|
success=0
|
|
total=20
|
|
|
|
# Admin
|
|
if api_post "$BASE_URL/register" '{"firstName":"Chelsea","lastName":"Russell","email":"admin@example.com","password":"password","phone":"+447000000000","dateOfBirth":"1985-01-01","agreedToPolicy":true}' "Register Admin" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Primary test user (no-deposit, easy to book with)
|
|
if api_post "$BASE_URL/register" '{"firstName":"Regular","lastName":"User","email":"user@example.com","password":"password","phone":"+447000000001","dateOfBirth":"1990-05-15","agreedToPolicy":true}' "Register User" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Loyal regulars
|
|
if api_post "$BASE_URL/register" '{"firstName":"Emma","lastName":"Johnson","email":"emma.johnson@example.com","password":"password","phone":"+447000000002","dateOfBirth":"1988-03-22","agreedToPolicy":true}' "Register Emma Johnson" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Sophie","lastName":"Williams","email":"sophie.williams@example.com","password":"password","phone":"+447000000003","dateOfBirth":"1995-11-08","agreedToPolicy":true}' "Register Sophie Williams" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Amelia","lastName":"Jones","email":"amelia.jones@example.com","password":"password","phone":"+447000000004","dateOfBirth":"1993-09-30","agreedToPolicy":true}' "Register Amelia Jones" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Isla","lastName":"Davies","email":"isla.davies@example.com","password":"password","phone":"+447000000005","dateOfBirth":"1989-12-25","agreedToPolicy":true}' "Register Isla Davies" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Lily","lastName":"Wilson","email":"lily.wilson@example.com","password":"password","phone":"+447000000006","dateOfBirth":"1996-08-03","agreedToPolicy":true}' "Register Lily Wilson" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Ava","lastName":"Walker","email":"ava.walker@example.com","password":"password","phone":"+447000000007","dateOfBirth":"1993-03-07","agreedToPolicy":true}' "Register Ava Walker" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Occasional clients
|
|
if api_post "$BASE_URL/register" '{"firstName":"Oliver","lastName":"Smith","email":"oliver.smith@example.com","password":"password","phone":"+447000000008","dateOfBirth":"1992-07-14","agreedToPolicy":true}' "Register Oliver Smith" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Harry","lastName":"Brown","email":"harry.brown@example.com","password":"password","phone":"+447000000009","dateOfBirth":"1987-02-19","agreedToPolicy":true}' "Register Harry Brown" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"George","lastName":"Roberts","email":"george.roberts@example.com","password":"password","phone":"+447000000010","dateOfBirth":"1986-01-29","agreedToPolicy":true}' "Register George Roberts" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Charlie","lastName":"Wright","email":"charlie.wright@example.com","password":"password","phone":"+447000000011","dateOfBirth":"1990-10-11","agreedToPolicy":true}' "Register Charlie Wright" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Noah","lastName":"Robinson","email":"noah.robinson@example.com","password":"password","phone":"+447000000012","dateOfBirth":"1988-11-16","agreedToPolicy":true}' "Register Noah Robinson" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Thomas","lastName":"Evans","email":"thomas.evans@example.com","password":"password","phone":"+447000000013","dateOfBirth":"1994-04-17","agreedToPolicy":true}' "Register Thomas Evans" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Oscar","lastName":"Hughes","email":"oscar.hughes@example.com","password":"password","phone":"+447000000014","dateOfBirth":"1991-02-04","agreedToPolicy":true}' "Register Oscar Hughes" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Deposit-required users (demonstrate deposit snapshot behaviour)
|
|
if api_post "$BASE_URL/register" '{"firstName":"Poppy","lastName":"Thompson","email":"poppy.thompson@example.com","password":"password","phone":"+447000000015","dateOfBirth":"1997-06-21","agreedToPolicy":true}' "Register Poppy Thompson" "" > /dev/null; then success=$((success+1)); fi
|
|
if api_post "$BASE_URL/register" '{"firstName":"Mia","lastName":"White","email":"mia.white@example.com","password":"password","phone":"+447000000016","dateOfBirth":"1995-07-28","agreedToPolicy":true}' "Register Mia White" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Young client (for age-restricted service testing)
|
|
if api_post "$BASE_URL/register" '{"firstName":"Chloe","lastName":"Park","email":"chloe.park@example.com","password":"password","phone":"+447000000017","dateOfBirth":"2009-04-12","agreedToPolicy":true}' "Register Chloe Park" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# Patch-test-complete user (will have gel allergy test recorded)
|
|
if api_post "$BASE_URL/register" '{"firstName":"Grace","lastName":"Fletcher","email":"grace.fletcher@example.com","password":"password","phone":"+447000000018","dateOfBirth":"1991-08-30","agreedToPolicy":true}' "Register Grace Fletcher" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# No-show history user (to demonstrate 48h booking restriction)
|
|
if api_post "$BASE_URL/register" '{"firstName":"Liam","lastName":"Caldwell","email":"liam.caldwell@example.com","password":"password","phone":"+447000000019","dateOfBirth":"1989-03-14","agreedToPolicy":true}' "Register Liam Caldwell" "" > /dev/null; then success=$((success+1)); fi
|
|
|
|
# --- Promote admin and set deposit flags via DB ---
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET account_role = 'admin' WHERE email = 'admin@example.com'" > /dev/null 2>&1
|
|
# Primary test user: zero deposit requirement for easy booking
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 0 WHERE email = 'user@example.com'" > /dev/null 2>&1
|
|
# Loyal regulars: zero deposit requirement
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 0 WHERE email IN ('emma.johnson@example.com','sophie.williams@example.com','amelia.jones@example.com','isla.davies@example.com','lily.wilson@example.com','ava.walker@example.com','grace.fletcher@example.com')" > /dev/null 2>&1
|
|
# Deposit-required users: 3 no-shows on record
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 3 WHERE email IN ('poppy.thompson@example.com','mia.white@example.com')" > /dev/null 2>&1
|
|
# Liam: 1 no-show, in 48h restriction window
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 1 WHERE email = 'liam.caldwell@example.com'" > /dev/null 2>&1
|
|
|
|
echo "${C_GREEN}✅ Registered $success/$total Users${C_RESET}"
|
|
user_success=$success
|
|
user_total=$total
|
|
|
|
# ===========================================================================
|
|
# 2. LOGIN
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}🔑 Authenticating...${C_RESET}"
|
|
|
|
ADMIN_TOKEN=$(login "$ADMIN_EMAIL" "$ADMIN_PASS")
|
|
if [ -z "$ADMIN_TOKEN" ]; then echo "❌ Admin auth failed"; exit 1; fi
|
|
|
|
USER_TOKEN=$(login "$USER_EMAIL" "$USER_PASS")
|
|
if [ -z "$USER_TOKEN" ]; then echo "❌ User auth failed"; exit 1; fi
|
|
|
|
EMMA_TOKEN=$(login "emma.johnson@example.com" "password")
|
|
SOPHIE_TOKEN=$(login "sophie.williams@example.com" "password")
|
|
AMELIA_TOKEN=$(login "amelia.jones@example.com" "password")
|
|
ISLA_TOKEN=$(login "isla.davies@example.com" "password")
|
|
LILY_TOKEN=$(login "lily.wilson@example.com" "password")
|
|
AVA_TOKEN=$(login "ava.walker@example.com" "password")
|
|
POPPY_TOKEN=$(login "poppy.thompson@example.com" "password")
|
|
MIA_TOKEN=$(login "mia.white@example.com" "password")
|
|
GRACE_TOKEN=$(login "grace.fletcher@example.com" "password")
|
|
|
|
# Look up user IDs for admin booking creation
|
|
get_user_id() {
|
|
docker exec postgres psql -U myuser -d mydb -tAc "SELECT id FROM users WHERE email='$1';" 2>/dev/null | tr -d '\r\n\t '
|
|
}
|
|
|
|
ADMIN_USER_ID=$(get_user_id "admin@example.com")
|
|
USER_USER_ID=$(get_user_id "user@example.com")
|
|
EMMA_ID=$(get_user_id "emma.johnson@example.com")
|
|
SOPHIE_ID=$(get_user_id "sophie.williams@example.com")
|
|
AMELIA_ID=$(get_user_id "amelia.jones@example.com")
|
|
ISLA_ID=$(get_user_id "isla.davies@example.com")
|
|
LILY_ID=$(get_user_id "lily.wilson@example.com")
|
|
AVA_ID=$(get_user_id "ava.walker@example.com")
|
|
OLIVER_ID=$(get_user_id "oliver.smith@example.com")
|
|
HARRY_ID=$(get_user_id "harry.brown@example.com")
|
|
CHARLIE_ID=$(get_user_id "charlie.wright@example.com")
|
|
NOAH_ID=$(get_user_id "noah.robinson@example.com")
|
|
POPPY_ID=$(get_user_id "poppy.thompson@example.com")
|
|
MIA_ID=$(get_user_id "mia.white@example.com")
|
|
GRACE_ID=$(get_user_id "grace.fletcher@example.com")
|
|
LIAM_ID=$(get_user_id "liam.caldwell@example.com")
|
|
|
|
echo "${C_GREEN}✅ Authentication successful${C_RESET}"
|
|
sleep 1
|
|
|
|
# ===========================================================================
|
|
# 3. CREATE SERVICES
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}💅 Creating Services...${C_RESET}"
|
|
|
|
SERVICES=(
|
|
'{"name":"Classic Manicure","description":"Nail shaping, cuticle care, hand massage, and polish.","price":25.00,"duration_minutes":45,"minimum_age_required":0}'
|
|
'{"name":"Gel Manicure (BIAB)","description":"Hard-wearing gel polish with Builder In A Bottle base. Long-lasting with added nail strength.","price":35.00,"duration_minutes":60,"minimum_age_required":0}'
|
|
'{"name":"Luxury Pedicure","description":"Foot soak, scrub, mask, extended massage, and polish.","price":45.00,"duration_minutes":75,"minimum_age_required":0}'
|
|
'{"name":"Express Mani & Pedi","description":"Quick file, shape, and polish for both hands and feet.","price":40.00,"duration_minutes":60,"minimum_age_required":0}'
|
|
'{"name":"Gel Polish Removal","description":"Safe soak-off removal of existing gel polish, including aftercare oil treatment.","price":10.00,"duration_minutes":20,"minimum_age_required":0}'
|
|
'{"name":"Nail Art Add-on","description":"Custom nail art per two fingers — French tips, florals, gems, and more.","price":5.00,"duration_minutes":15,"minimum_age_required":0}'
|
|
'{"name":"Gel Polish Full Set","description":"Full gel polish application over natural nails. Requires patch test 24h before first appointment.","price":45.00,"duration_minutes":60,"minimum_age_required":0}'
|
|
'{"name":"Luxury Gel Manicure","description":"Premium gel polish with extended hand massage and cuticle treatment. Requires patch test 24h before first appointment.","price":55.00,"duration_minutes":75,"minimum_age_required":0}'
|
|
'{"name":"Acrylic Full Set","description":"Full set of acrylic extensions, shaped and polished to your preference.","price":55.00,"duration_minutes":90,"minimum_age_required":16}'
|
|
'{"name":"Acrylic Infill","description":"Maintenance infill for existing acrylic extensions.","price":35.00,"duration_minutes":60,"minimum_age_required":16}'
|
|
'{"name":"Paraffin Wax Treatment","description":"Deeply moisturising paraffin wax hand or foot treatment, great as an add-on.","price":12.00,"duration_minutes":20,"minimum_age_required":0}'
|
|
'{"name":"Bridal Nail Package","description":"Luxury manicure and pedicure with nail art, paraffin wax, and extended massage for the big day.","price":120.00,"duration_minutes":150,"minimum_age_required":0}'
|
|
)
|
|
|
|
SERVICE_IDS=()
|
|
success=0
|
|
total=${#SERVICES[@]}
|
|
|
|
for svc in "${SERVICES[@]}"; do
|
|
NAME=$(echo "$svc" | grep -o '"name":"[^"]*' | cut -d'"' -f4)
|
|
ID=$(api_post "$BASE_URL/admin/services" "$svc" "Create $NAME" "$ADMIN_TOKEN")
|
|
if [[ -n "$ID" && "$ID" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
SERVICE_IDS+=("$ID")
|
|
success=$((success+1))
|
|
fi
|
|
done
|
|
echo "${C_GREEN}✅ Created $success/$total Services${C_RESET}"
|
|
|
|
get_svc() { echo "${SERVICE_IDS[$1]}"; }
|
|
|
|
# ===========================================================================
|
|
# 3b. CREATE PATCH TESTS (seeded directly via SQL — no creation API endpoint)
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}🧪 Creating Patch Tests...${C_RESET}"
|
|
|
|
# Gel Allergy Test covers: Gel Polish Full Set (idx 6), Luxury Gel Manicure (idx 7)
|
|
GEL_SVC_1="${SERVICE_IDS[6]}"
|
|
GEL_SVC_2="${SERVICE_IDS[7]}"
|
|
|
|
# Insert patch test directly via SQL (no creation API endpoint exists)
|
|
# service_ids cast explicitly to uuid[] to handle typed columns
|
|
PATCH_TEST_RESULT=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|
"INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids) VALUES ('Gel Allergy Test', 'Mandatory patch test for all gel polish services. Must be completed at least 24 hours before your first gel appointment.', 24, 6, ARRAY['${GEL_SVC_1}','${GEL_SVC_2}']) RETURNING id;" 2>&1)
|
|
PATCH_TEST_ID=$(echo "$PATCH_TEST_RESULT" | head -1 | tr -d '\r\t ')
|
|
|
|
# Validate it looks like a UUID; print raw output if not to help diagnose
|
|
if [[ -z "$PATCH_TEST_ID" || ! "$PATCH_TEST_ID" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
echo "${C_YELLOW}⚠️ Patch test SQL failed. Raw output: $PATCH_TEST_RESULT${C_RESET}"
|
|
PATCH_TEST_ID=""
|
|
fi
|
|
|
|
# Record that Grace has already completed her patch test via the API
|
|
if [[ -n "$PATCH_TEST_ID" && -n "$GRACE_ID" ]]; then
|
|
api_post "$BASE_URL/admin/users/$GRACE_ID/patch-tests" \
|
|
"{\"patch_test_id\":\"$PATCH_TEST_ID\"}" \
|
|
"Record patch test for Grace" "$ADMIN_TOKEN" > /dev/null
|
|
echo "${C_GREEN}✅ Patch test seeded (SQL) and recorded for Grace Fletcher${C_RESET}"
|
|
else
|
|
echo "${C_YELLOW}⚠️ Patch test seeding failed (check DB connection or patch_tests table)${C_RESET}"
|
|
fi
|
|
|
|
# ===========================================================================
|
|
# 4. BOOKINGS
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}📅 Creating Bookings...${C_RESET}"
|
|
|
|
format_london_time() {
|
|
TZ=Europe/London date -d "$1 $2" +"%Y-%m-%dT%H:%M:%S%:z"
|
|
}
|
|
|
|
# Create a booking as a regular user, capture LAST_BOOKING_ID
|
|
create_booking() {
|
|
local token=$1 time=$2 services=$3 notes=$4 name=$5
|
|
local json="{\"start_time\":\"$time\",\"service_ids\":$services"
|
|
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
|
json="$json}"
|
|
|
|
local id=$(api_post "$BASE_URL/bookings" "$json" "Book: $name" "$token")
|
|
if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
LAST_BOOKING_ID="$id"
|
|
return 0
|
|
else
|
|
LAST_BOOKING_ID=""
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Create a booking as admin (no advance restriction, always confirmed)
|
|
create_admin_booking() {
|
|
local user_id=$1 time=$2 services=$3 notes=$4 name=$5
|
|
local json="{\"user_id\":\"$user_id\",\"start_time\":\"$time\",\"service_ids\":$services,\"service_overrides\":[],\"enforce_deposits\":false"
|
|
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
|
json="$json}"
|
|
|
|
local id=$(api_post "$BASE_URL/admin/bookings" "$json" "Admin Book: $name" "$ADMIN_TOKEN")
|
|
if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
LAST_BOOKING_ID="$id"
|
|
return 0
|
|
else
|
|
LAST_BOOKING_ID=""
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Returns the nearest OPEN business day at or after the given date.
|
|
# dow: date's day-of-week as 0=Sun,...,6=Sat (GNU date %w)
|
|
open_day() {
|
|
local d="$1"
|
|
local max=7
|
|
for ((i=0; i<max; i++)); do
|
|
local dow
|
|
dow=$(TZ=Europe/London date -d "$d" +%w)
|
|
if [[ "$dow" == "0" || "$dow" == "6" ]]; then
|
|
d=$(TZ=Europe/London date -d "$d +1 day" +%Y-%m-%d)
|
|
else
|
|
echo "$d"
|
|
return
|
|
fi
|
|
done
|
|
echo "$d"
|
|
}
|
|
|
|
# Same but shifts BACKWARDS to find an open day (for past bookings)
|
|
open_day_past() {
|
|
local d="$1"
|
|
local max=7
|
|
for ((i=0; i<max; i++)); do
|
|
local dow
|
|
dow=$(TZ=Europe/London date -d "$d" +%w)
|
|
if [[ "$dow" == "0" || "$dow" == "6" ]]; then
|
|
d=$(TZ=Europe/London date -d "$d -1 day" +%Y-%m-%d)
|
|
else
|
|
echo "$d"
|
|
return
|
|
fi
|
|
done
|
|
echo "$d"
|
|
}
|
|
|
|
TODAY=$(TZ=Europe/London date +%Y-%m-%d)
|
|
TOMORROW=$(open_day "$(TZ=Europe/London date -d "tomorrow" +%Y-%m-%d)")
|
|
|
|
# Arrays to collect upcoming booking IDs that need notes confirmed
|
|
PENDING_BOOKING_IDS=()
|
|
PENDING_BOOKING_NAMES=()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PAST BOOKINGS — all via admin (bypasses past-time rejection)
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_YELLOW}📅 Creating Past Bookings...${C_RESET}"
|
|
count_past=0
|
|
|
|
# Emma — loyal weekly gel client
|
|
for day_offset in 7 14 21 28; do
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
|
if create_admin_booking "$EMMA_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure $D"; then count_past=$((count_past+1)); fi
|
|
done
|
|
|
|
# Sophie — pedicure every few weeks
|
|
for day_offset in 6 20; do
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
|
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure $D"; then count_past=$((count_past+1)); fi
|
|
done
|
|
|
|
# Amelia — classic manicure regular
|
|
for day_offset in 5 19; do
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
|
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$D" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "Amelia - Classic Manicure $D"; then count_past=$((count_past+1)); fi
|
|
done
|
|
|
|
# Isla — mixed services
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -3 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$ISLA_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 3)\"]" "" "Isla - Express Mani & Pedi"; then count_past=$((count_past+1)); fi
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -17 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$ISLA_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Isla - Classic + Nail Art"; then count_past=$((count_past+1)); fi
|
|
|
|
# Lily — gel with nail art combo
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -10 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$LILY_ID" "$(format_london_time "$D" "$SLOT_D")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "" "Lily - Gel + Nail Art"; then count_past=$((count_past+1)); fi
|
|
|
|
# Ava — occasional pedicure
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -12 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$AVA_ID" "$(format_london_time "$D" "$SLOT_B")" "[\"$(get_svc 2)\"]" "" "Ava - Luxury Pedicure"; then count_past=$((count_past+1)); fi
|
|
|
|
# Grace — gel client (patch test already recorded)
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -9 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$GRACE_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set"; then count_past=$((count_past+1)); fi
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -23 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$GRACE_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 7)\"]" "" "Grace - Luxury Gel Manicure"; then count_past=$((count_past+1)); fi
|
|
|
|
# Primary test user — variety of past bookings
|
|
for day_offset in 2 4 8 11 15; do
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
|
SVC_IDX=$(( (day_offset % 4) ))
|
|
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc $SVC_IDX)\"]" "" "User - Past booking $D"; then count_past=$((count_past+1)); fi
|
|
done
|
|
|
|
# Poppy / Mia — deposit-required past visits
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -5 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$POPPY_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Poppy - Past (deposit snapshotted)"; then count_past=$((count_past+1)); fi
|
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -13 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$MIA_ID" "$(format_london_time "$D" "$SLOT_D")" "[\"$(get_svc 2)\"]" "" "Mia - Past"; then count_past=$((count_past+1)); fi
|
|
|
|
echo "${C_GREEN}✅ Created $count_past Past Bookings${C_RESET}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TODAY'S BOOKINGS — all via admin (bypass 1-hour advance)
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_YELLOW}📅 Creating Today's Bookings...${C_RESET}"
|
|
count_today=0
|
|
|
|
if create_admin_booking "$EMMA_ID" "$(format_london_time "$TODAY" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$TODAY" "$SLOT_B")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$TODAY" "$SLOT_C")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Amelia - Classic + Nail Art (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$TODAY" "$SLOT_D")" "[\"$(get_svc 3)\"]" "" "User - Express Mani & Pedi (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$ISLA_ID" "$(format_london_time "$TODAY" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Isla - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$GRACE_ID" "$(format_london_time "$TODAY" "$SLOT_D")" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set (today)"; then count_today=$((count_today+1)); fi
|
|
if create_admin_booking "$LILY_ID" "$(format_london_time "$TODAY" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Lily - Classic Manicure (today)"; then count_today=$((count_today+1)); fi
|
|
|
|
echo "${C_GREEN}✅ Created $count_today Today's Bookings${C_RESET}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TOMORROW'S BOOKINGS — all via admin (avoids booking_status enum bug in
|
|
# POST /bookings). We simulate the pending/notes scenario by creating 2
|
|
# bookings via user tokens (which intentionally hit the notes path) and
|
|
# leaving them unconfirmed for admin review.
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_YELLOW}📅 Creating Tomorrow's Bookings...${C_RESET}"
|
|
count_tomorrow=0
|
|
|
|
# Bulk of tomorrow's bookings — admin-created, all confirmed
|
|
if create_admin_booking "$EMMA_ID" "$(format_london_time "$TOMORROW" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
|
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$TOMORROW" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
|
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$TOMORROW" "$SLOT_D")" "[\"$(get_svc 3)\"]" "" "Amelia - Express Mani & Pedi (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
|
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$TOMORROW" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "User - Classic Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
|
if create_admin_booking "$LILY_ID" "$(format_london_time "$TOMORROW" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Lily - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
|
|
|
# Pending bookings — admin-created (avoids enum bug), then force to pending via DB.
|
|
# Notes are included so the admin UI shows the request context.
|
|
if create_admin_booking "$ISLA_ID" "$(format_london_time "$TOMORROW" "$SLOT_E")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "Would love a French tip look with some floral art on the ring fingers if possible?" "Isla - Gel + Nail Art (tomorrow, pending)"; then
|
|
count_tomorrow=$((count_tomorrow+1))
|
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
|
PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Isla - Gel + Nail Art (pending)")
|
|
fi
|
|
fi
|
|
if create_admin_booking "$AVA_ID" "$(format_london_time "$TOMORROW" "$SLOT_E")" "[\"$(get_svc 0)\"]" "Could I get a specific nail shape — stiletto if possible?" "Ava - Classic Manicure (tomorrow, pending)"; then
|
|
count_tomorrow=$((count_tomorrow+1))
|
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
|
PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Ava - Classic Manicure (pending)")
|
|
fi
|
|
fi
|
|
|
|
echo "${C_GREEN}✅ Created $count_tomorrow Tomorrow's Bookings${C_RESET}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# UPCOMING — next 14 days, all via admin to avoid booking_status enum bug.
|
|
# Bridal package uses user token (notes → pending) as the one exception.
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_YELLOW}📅 Creating Upcoming Bookings (next 14 days)...${C_RESET}"
|
|
count_future=0
|
|
|
|
UP_IDS=( "$EMMA_ID" "$SOPHIE_ID" "$AMELIA_ID" "$ISLA_ID" "$LILY_ID" "$AVA_ID" "$GRACE_ID" "$USER_USER_ID" )
|
|
UP_SVCS=( "1" "2" "0" "3" "1" "2" "7" "0" )
|
|
UP_SLOTS=( "$SLOT_A" "$SLOT_C" "$SLOT_D" "$SLOT_B" "$SLOT_C" "$SLOT_A" "$SLOT_D" "$SLOT_B" )
|
|
UP_NAMES=( "Gel Manicure" "Luxury Pedicure" "Classic Manicure" "Express Mani & Pedi" "Gel Manicure" "Luxury Pedicure" "Luxury Gel Manicure" "Classic Manicure" )
|
|
|
|
for day_offset in {2..15}; do
|
|
RAW_DATE=$(TZ=Europe/London date -d "$TODAY +$day_offset days" +%Y-%m-%d)
|
|
FUTURE_DATE=$(open_day "$RAW_DATE")
|
|
idx=$(( (day_offset - 2) % ${#UP_IDS[@]} ))
|
|
uid="${UP_IDS[$idx]}"
|
|
svc_idx="${UP_SVCS[$idx]}"
|
|
slot="${UP_SLOTS[$idx]}"
|
|
label="${UP_NAMES[$idx]}"
|
|
|
|
if create_admin_booking "$uid" "$(format_london_time "$FUTURE_DATE" "$slot")" "[\"$(get_svc $svc_idx)\"]" "" "$label ($FUTURE_DATE)"; then
|
|
count_future=$((count_future+1))
|
|
# NOTE: do NOT add admin bookings to PENDING_BOOKING_IDS — they're already confirmed
|
|
fi
|
|
done
|
|
|
|
# Multi-service admin bookings for variety
|
|
D2=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D2" "$SLOT_B")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "User - Classic + Nail Art (+3 days)"; then
|
|
count_future=$((count_future+1))
|
|
fi
|
|
|
|
D3=$(open_day "$(TZ=Europe/London date -d "$TODAY +6 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$EMMA_ID" "$(format_london_time "$D3" "$SLOT_A")" "[\"$(get_svc 1)\",\"$(get_svc 4)\"]" "" "Emma - Gel Manicure + Removal (+6 days)"; then
|
|
count_future=$((count_future+1))
|
|
fi
|
|
|
|
# Bridal package — admin-created with notes, then forced to pending via DB.
|
|
D4=$(open_day "$(TZ=Europe/London date -d "$TODAY +12 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$D4" "$SLOT_D")" "[\"$(get_svc 11)\"]" "Bride-to-be — please can we discuss nail art options beforehand?" "Sophie - Bridal Package (+12 days, pending)"; then
|
|
count_future=$((count_future+1))
|
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
|
PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Sophie - Bridal Package (pending)")
|
|
fi
|
|
fi
|
|
|
|
# Deposit-required user — admin-created to bypass deposit check
|
|
D5=$(open_day "$(TZ=Europe/London date -d "$TODAY +4 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$POPPY_ID" "$(format_london_time "$D5" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Poppy - Future (deposit snapshotted)"; then
|
|
count_future=$((count_future+1))
|
|
fi
|
|
|
|
echo "${C_GREEN}✅ Created $count_future Upcoming Bookings${C_RESET}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CONFIRM roughly 2/3 of pending bookings, leave ~1/3 for admin review
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_BLUE}🔒 Confirming Upcoming Bookings (approx. 2/3)...${C_RESET}"
|
|
confirmed_count=0
|
|
skipped_count=0
|
|
sleep 1
|
|
|
|
for ((i=0; i<${#PENDING_BOOKING_IDS[@]}; i++)); do
|
|
id="${PENDING_BOOKING_IDS[$i]}"
|
|
[[ -z "$id" ]] && continue
|
|
[[ ! "$id" =~ ^[0-9a-f-]{8,}$ ]] && continue
|
|
|
|
if [ $((RANDOM % 3)) -ne 0 ]; then
|
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
-d '{"service_overrides":[],"notes":""}' \
|
|
"$BASE_URL/admin/bookings/$id/confirm")
|
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
[ "$HTTP_CODE" = "200" ] && confirmed_count=$((confirmed_count+1))
|
|
sleep 0.05
|
|
else
|
|
skipped_count=$((skipped_count+1))
|
|
fi
|
|
done
|
|
echo "${C_GREEN}✅ Confirmed $confirmed_count bookings, left $skipped_count pending for admin review${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# 5b. USER BOOKINGS WITH NOTES (triggers pending_booking notifications)
|
|
# These use the public endpoint so notifications are created.
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}📝 Creating User Bookings with Notes (notification triggers)..."
|
|
|
|
USER_NOTE_COUNT=0
|
|
|
|
# Emma — booking with special request notes (triggers new_booking + pending_booking)
|
|
D_NOTE1=$(open_day "$(TZ=Europe/London date -d "$TODAY +8 days" +%Y-%m-%d)")
|
|
if create_booking "$EMMA_TOKEN" "$(format_london_time "$D_NOTE1" "$SLOT_E")" "[\"$(get_svc 1)\"]" "Hi! Could I please have a nude base with white french tips and a single gold foil accent on the ring finger? Also I have a slight nail ridge on my left thumb - nothing major but worth noting." "Emma - French tips + gold foil (+8 days)"; then
|
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
|
fi
|
|
|
|
# Isla — booking with notes for a different day (triggers new_booking + pending_booking)
|
|
D_NOTE2=$(open_day "$(TZ=Europe/London date -d "$TODAY +10 days" +%Y-%m-%d)")
|
|
if create_booking "$ISLA_TOKEN" "$(format_london_time "$D_NOTE2" "$SLOT_E")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "I'd like a chrome/mirror-ball effect on all nails if possible. Also I'm thinking of getting married soon so want to trial a bridal look - can we discuss options?" "Isla - Chrome trial (+10 days)"; then
|
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
|
fi
|
|
|
|
echo "${C_GREEN}✅ Created $USER_NOTE_COUNT User Bookings with Notes (pending notifications)${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# 6. GUEST BOOKINGS
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}👤 Creating Guest Accounts & Bookings...${C_RESET}"
|
|
|
|
create_guest() {
|
|
local name="$1" email="$2" phone="$3"
|
|
curl -s -X POST -H 'Content-Type: application/json' \
|
|
-d "{\"firstName\":\"$name\",\"lastName\":\"Guest\",\"email\":\"$email\",\"phone\":\"$phone\"}" \
|
|
"$BASE_URL/users/guest" \
|
|
| tr -d '\r\n\t ' \
|
|
| sed -n 's/.*"id":"\([^"]*\)".*/\1/p'
|
|
}
|
|
|
|
GUEST1_ID=$(create_guest "Nina" "nina.guest@example.com" "+447000000020")
|
|
GUEST2_ID=$(create_guest "Bob" "bob.guest@example.com" "+447000000021")
|
|
GUEST3_ID=$(create_guest "Carol" "carol.guest@example.com" "+447000000022")
|
|
GUEST4_ID=$(create_guest "Diana" "nina.guest@example.com" "+447000000023")
|
|
GUEST5_ID=$(create_guest "Evil" "$USER_EMAIL" "+447000000024")
|
|
|
|
count_guest=0
|
|
|
|
guest_book() {
|
|
local gid="$1" time="$2" svc_idx="$3"
|
|
[[ -z "$gid" ]] && return
|
|
local reserve_json="{\"start_time\":\"$time\",\"service_ids\":[\"$(get_svc $svc_idx)\"]}"
|
|
local res_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$reserve_json" "$BASE_URL/bookings/reserve")
|
|
local res_code=$(echo "$res_resp" | tail -n1)
|
|
[[ ! "$res_code" =~ ^2 ]] && return
|
|
local book_json="{\"user_id\":\"$gid\",\"start_time\":\"$time\",\"service_ids\":[\"$(get_svc $svc_idx)\"]}"
|
|
local book_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$book_json" "$BASE_URL/bookings")
|
|
local book_code=$(echo "$book_resp" | tail -n1)
|
|
[[ "$book_code" =~ ^2 ]] && count_guest=$((count_guest+1))
|
|
}
|
|
|
|
[[ -n "$GUEST1_ID" ]] && guest_book "$GUEST1_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +16 days" +%Y-%m-%d)")" "$SLOT_B")" 0
|
|
[[ -n "$GUEST2_ID" ]] && guest_book "$GUEST2_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +20 days" +%Y-%m-%d)")" "$SLOT_C")" 3
|
|
[[ -n "$GUEST3_ID" ]] && guest_book "$GUEST3_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +22 days" +%Y-%m-%d)")" "$SLOT_D")" 1
|
|
|
|
echo "${C_GREEN}✅ Created $count_guest Guest Bookings${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# 7. TIME BLOCKERS (admin-blocked time)
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}🚫 Creating Time Blockers...${C_RESET}"
|
|
count_blockers=0
|
|
|
|
tb() {
|
|
local time="$1" dur="$2" desc="$3"
|
|
local json="{\"start_time\":\"$time\",\"duration_minutes\":$dur,\"description\":\"$desc\"}"
|
|
local resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$json" "$BASE_URL/admin/time-blockers")
|
|
local code=$(echo "$resp" | tail -n1)
|
|
[[ "$code" =~ ^2 ]] && count_blockers=$((count_blockers+1))
|
|
}
|
|
|
|
tb "$(format_london_time "$TOMORROW" "14:00:00")" 60 "Staff meeting"
|
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")" "$SLOT_A")" 120 "Holiday — closed morning"
|
|
tb "$(format_london_time "$TOMORROW" "09:00:00")" 120 "Late start — closed until 11am"
|
|
|
|
echo "${C_GREEN}✅ Created $count_blockers Time Blockers${C_RESET}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CANCELLATIONS
|
|
# Both POST /admin/bookings/{id}/cancel and DELETE /bookings/{id} have backend
|
|
# bugs (enum value mismatch + FK violation). Patching status directly in DB.
|
|
# Auto-detects the actual enum values from pg_enum to avoid hardcoding.
|
|
# ---------------------------------------------------------------------------
|
|
echo -e "\n${C_BLUE}🚫 Simulating Cancellations...${C_RESET}"
|
|
cancel_count=0
|
|
|
|
# Discover actual booking_status enum values
|
|
ENUM_VALUES=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|
"SELECT string_agg(enumlabel, ',' ORDER BY enumsortorder) FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid WHERE t.typname = 'booking_status';" 2>/dev/null | tr -d '\r\n\t ')
|
|
|
|
CLIENT_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -i 'client' | head -1)
|
|
ADMIN_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -iE 'we_|admin' | head -1)
|
|
[[ -z "$CLIENT_CANCEL_STATUS" ]] && CLIENT_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -i 'cancel' | head -1)
|
|
[[ -z "$ADMIN_CANCEL_STATUS" ]] && ADMIN_CANCEL_STATUS="$CLIENT_CANCEL_STATUS"
|
|
|
|
db_cancel() {
|
|
local booking_id="$1" status="$2" reason="$3"
|
|
[[ -z "$booking_id" || -z "$status" ]] && return 1
|
|
# Discover the cancel reason column name (varies by schema)
|
|
local reason_col
|
|
reason_col=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|
"SELECT column_name FROM information_schema.columns WHERE table_name='bookings' AND column_name IN ('cancel_reason','cancellation_reason','reason') LIMIT 1;" 2>/dev/null | head -1 | tr -d '\r\t ')
|
|
local sql
|
|
if [[ -n "$reason_col" ]]; then
|
|
sql="UPDATE bookings SET status = '$status', $reason_col = '$reason' WHERE id = '$booking_id' RETURNING id;"
|
|
else
|
|
sql="UPDATE bookings SET status = '$status' WHERE id = '$booking_id' RETURNING id;"
|
|
fi
|
|
local result
|
|
result=$(docker exec postgres psql -U myuser -d mydb -tAc "$sql" 2>&1)
|
|
local rows
|
|
rows=$(echo "$result" | head -1 | tr -d '\r\t ')
|
|
if [[ -n "$rows" && "$rows" =~ ^[0-9a-f-]{8,}$ ]]; then
|
|
return 0
|
|
else
|
|
echo "${C_RED} SQL: $sql${C_RESET}"
|
|
echo "${C_RED} Result: $result${C_RESET}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
D_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$AVA_ID" "$(format_london_time "$D_CANCEL" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "Ava - to be cancelled"; then
|
|
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Something came up, really sorry!"; then
|
|
cancel_count=$((cancel_count+1))
|
|
else
|
|
echo "${C_RED}❌ DB cancel failed for Ava${C_RESET}"
|
|
fi
|
|
fi
|
|
|
|
D_CANCEL2=$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D_CANCEL2" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "User - to be cancelled"; then
|
|
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Plans changed, apologies for the late notice."; then
|
|
cancel_count=$((cancel_count+1))
|
|
else
|
|
echo "${C_RED}❌ DB cancel failed for User${C_RESET}"
|
|
fi
|
|
fi
|
|
|
|
D_ADMIN_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +9 days" +%Y-%m-%d)")
|
|
if create_admin_booking "$NOAH_ID" "$(format_london_time "$D_ADMIN_CANCEL" "$SLOT_B")" "[\"$(get_svc 1)\"]" "" "Noah - to be admin-cancelled"; then
|
|
if db_cancel "$LAST_BOOKING_ID" "$ADMIN_CANCEL_STATUS" "Slot no longer available due to schedule change."; then
|
|
cancel_count=$((cancel_count+1))
|
|
else
|
|
echo "${C_RED}❌ DB cancel failed for Noah${C_RESET}"
|
|
fi
|
|
fi
|
|
|
|
echo "${C_GREEN}✅ Simulated $cancel_count cancellations${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# 6b. PAYMENTS (seeded via SQL — no Square integration yet)
|
|
# Payment over the booking total counts as a tip.
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}💳 Creating Payments...${C_RESET}"
|
|
|
|
# Mark past confirmed bookings as completed so payments can be seeded
|
|
docker exec postgres psql -U myuser -d mydb -c \
|
|
"UPDATE bookings SET status = 'completed'
|
|
WHERE status = 'confirmed' AND start_time < NOW() - INTERVAL '1 hour'" > /dev/null 2>&1
|
|
completed_count=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|
"SELECT COUNT(*) FROM bookings WHERE status = 'completed'" 2>/dev/null)
|
|
|
|
# Pure SQL payment seeding — randomized per booking, no bash loops
|
|
docker exec -i postgres psql -U myuser -d mydb << 'PAYMENT_SQL' > /dev/null 2>&1
|
|
DO $$
|
|
DECLARE
|
|
rec RECORD;
|
|
booking_total NUMERIC(10,2);
|
|
scenario INT;
|
|
deposit NUMERIC(10,2);
|
|
partial NUMERIC(10,2);
|
|
balance NUMERIC(10,2);
|
|
tip NUMERIC(10,2);
|
|
paid NUMERIC(10,2);
|
|
BEGIN
|
|
FOR rec IN
|
|
SELECT b.id,
|
|
COALESCE(SUM(COALESCE(bs.override_price, s.price)), 0) as total
|
|
FROM bookings b
|
|
JOIN booking_services bs ON bs.booking_id = b.id
|
|
JOIN services s ON bs.service_id = s.id
|
|
WHERE b.status = 'completed'
|
|
GROUP BY b.id
|
|
LOOP
|
|
booking_total := rec.total;
|
|
scenario := floor(random() * 5)::INT;
|
|
|
|
CASE scenario
|
|
WHEN 0 THEN
|
|
deposit := ROUND(booking_total * 0.25, 2);
|
|
balance := ROUND(booking_total - deposit, 2);
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'deposit', 'in_person_card', deposit, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'balance', 'in_person_card', balance, 'completed');
|
|
WHEN 1 THEN
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'full', 'in_person_card', booking_total, 'completed');
|
|
WHEN 2 THEN
|
|
tip := ROUND((random() * 15 + 5)::NUMERIC, 2);
|
|
paid := ROUND(booking_total + tip, 2);
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'full', 'in_person_card', paid, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'tip', 'in_person_card', tip, 'completed');
|
|
WHEN 3 THEN
|
|
partial := ROUND(booking_total * 0.5, 2);
|
|
balance := ROUND(booking_total - partial, 2);
|
|
tip := ROUND((random() * 10 + 3)::NUMERIC, 2);
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'partial', 'in_person_card', partial, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'balance', 'in_person_card', balance, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'tip', 'in_person_card', tip, 'completed');
|
|
WHEN 4 THEN
|
|
deposit := ROUND(booking_total * 0.20, 2);
|
|
partial := ROUND(booking_total * 0.30, 2);
|
|
balance := ROUND(booking_total - deposit - partial, 2);
|
|
tip := ROUND((random() * 20 + 5)::NUMERIC, 2);
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'deposit', 'in_person_card', deposit, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'partial', 'in_person_card', partial, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'balance', 'in_person_card', balance, 'completed');
|
|
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status)
|
|
VALUES (rec.id, 'tip', 'in_person_card', tip, 'completed');
|
|
END CASE;
|
|
END LOOP;
|
|
END $$;
|
|
PAYMENT_SQL
|
|
|
|
payment_count=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|
"SELECT COUNT(DISTINCT booking_id) FROM payments" 2>/dev/null)
|
|
echo "${C_GREEN}✅ Created payments for $payment_count completed bookings${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# 5. EXCEPTIONAL SCHEDULING GROUPS
|
|
# ===========================================================================
|
|
echo -e "\n${C_BLUE}🗓️ Creating Exceptional Schedule Groups...${C_RESET}"
|
|
sched_success=0
|
|
|
|
# Easter break — fully closed
|
|
EASTER_BREAK='{
|
|
"name":"Easter Break",
|
|
"description":"Closed for Easter weekend",
|
|
"hours":[
|
|
{"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":1,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":2,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":3,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":4,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":5,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}
|
|
],
|
|
"weekStarts":["2026-04-06"]
|
|
}'
|
|
|
|
# Summer half-term — reduced hours (mornings only, Tue-Sat)
|
|
HALF_TERM='{
|
|
"name":"Spring Half-Term",
|
|
"description":"Reduced hours during half-term week — mornings only",
|
|
"hours":[
|
|
{"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":1,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true},
|
|
{"weekday":2,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true},
|
|
{"weekday":3,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true},
|
|
{"weekday":4,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true},
|
|
{"weekday":5,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true},
|
|
{"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}
|
|
],
|
|
"weekStarts":["2026-06-01"]
|
|
}'
|
|
|
|
# Christmas holiday — fully closed across two weeks
|
|
XMAS_BREAK='{
|
|
"name":"Christmas Holiday Period",
|
|
"description":"Closed for Christmas and New Year",
|
|
"hours":[
|
|
{"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":1,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":2,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":3,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":4,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":5,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},
|
|
{"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}
|
|
],
|
|
"weekStarts":["2026-12-21","2026-12-28"]
|
|
}'
|
|
|
|
if api_post "$BASE_URL/scheduling/exceptional-groups" "$EASTER_BREAK" "Easter Break" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi
|
|
if api_post "$BASE_URL/scheduling/exceptional-groups" "$HALF_TERM" "Spring Half-Term" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi
|
|
if api_post "$BASE_URL/scheduling/exceptional-groups" "$XMAS_BREAK" "Christmas Break" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi
|
|
|
|
echo "${C_GREEN}✅ Created $sched_success/3 Exceptional Schedule Groups${C_RESET}"
|
|
|
|
# ===========================================================================
|
|
# SUMMARY
|
|
# ===========================================================================
|
|
TOTAL_BOOKINGS=$((count_past + count_today + count_tomorrow + count_future))
|
|
echo ""
|
|
echo -e "${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}"
|
|
echo -e "${C_GREEN}🎉 Seeding Complete!${C_RESET}"
|
|
echo -e "${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}"
|
|
echo -e " Users registered : $user_success/$user_total"
|
|
echo -e " Services created : ${#SERVICE_IDS[@]}/${#SERVICES[@]}"
|
|
if [[ -n "$PATCH_TEST_ID" ]]; then
|
|
echo -e " Patch tests : 1 (Gel Allergy Test, recorded for Grace)"
|
|
else
|
|
echo -e " Patch tests : ⚠️ failed — see output above"
|
|
fi
|
|
echo -e " Bookings — past : $count_past"
|
|
echo -e " Bookings — today : $count_today"
|
|
echo -e " Bookings — tomorrow: $count_tomorrow"
|
|
echo -e " Bookings — future : $count_future"
|
|
echo -e " Bookings — total : $TOTAL_BOOKINGS"
|
|
echo -e " Cancellations : $cancel_count"
|
|
echo -e " Confirmed : $confirmed_count | Still pending: $skipped_count"
|
|
echo -e " Bookings — guest : $count_guest"
|
|
echo -e " Bookings — w/ notes: $USER_NOTE_COUNT (pending notifications)"
|
|
echo -e " Payments : $payment_count completed bookings"
|
|
echo -e " Time blockers : $count_blockers"
|
|
echo -e " Schedule groups : $sched_success/3"
|
|
echo ""
|
|
echo -e " Quick login creds (all pass: ${C_YELLOW}password${C_RESET})"
|
|
echo -e " Admin : ${C_YELLOW}admin@example.com${C_RESET}"
|
|
echo -e " User : ${C_YELLOW}user@example.com${C_RESET}"
|
|
echo -e " Deposit: ${C_YELLOW}poppy.thompson@example.com${C_RESET}"
|
|
echo -e " Gel ✓ : ${C_YELLOW}grace.fletcher@example.com${C_RESET} (patch test done)"
|
|
echo -e "${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}"
|
|
|
|
echo -e "\n${C_YELLOW}Press ENTER to run tests...${C_RESET}"
|
|
read -r
|
|
echo -e "${C_GREEN}⏳ Running tests...${C_RESET}"
|
|
|
|
cd /home/popertots/Crussell/backend
|
|
export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_DB GO_TESTING=1
|
|
TEST_OUTPUT_FILE=$(mktemp)
|
|
go test -tags test -v -p 1 -count=1 ./... 2>&1 | tee "$TEST_OUTPUT_FILE" || true
|
|
TEST_OUTPUT=$(cat "$TEST_OUTPUT_FILE")
|
|
rm -f "$TEST_OUTPUT_FILE"
|
|
cd ..
|
|
|
|
TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep "^=== RUN" | grep -cv "/" || echo "0")
|
|
FAILED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- FAIL" || echo "0")
|
|
SKIPPED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- SKIP" || echo "0")
|
|
PASSED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- PASS" || echo "0")
|
|
TOTAL_TESTS=${TOTAL_TESTS:-0}
|
|
PASSED_TESTS=${PASSED_TESTS:-0}
|
|
FAILED_TESTS=${FAILED_TESTS:-0}
|
|
SKIPPED_TESTS=${SKIPPED_TESTS:-0}
|
|
|
|
echo ""
|
|
if [ "$SKIPPED_TESTS" -gt 0 ] 2>/dev/null; then
|
|
echo -e "${C_YELLOW}⚠️ Tests Skipped: $SKIPPED_TESTS${C_RESET}"
|
|
fi
|
|
if [ "$FAILED_TESTS" -gt 0 ] 2>/dev/null; then
|
|
echo -e "${C_RED}❌ Tests Failed: $FAILED_TESTS/$TOTAL_TESTS failed${C_RESET}"
|
|
echo ""
|
|
echo -e "${C_RED}--- Failed Tests ---${C_RESET}"
|
|
echo "$TEST_OUTPUT" | grep "^--- FAIL" | head -20
|
|
else
|
|
echo -e "${C_GREEN}✅ All Tests Passed: $PASSED_TESTS/$TOTAL_TESTS${C_RESET}"
|
|
fi
|
|
echo ""
|
|
|
|
echo -e "${C_YELLOW}Press ENTER to restore 4-pane layout...${C_RESET}"
|
|
read -r
|
|
|
|
tmux select-window -t $SESSION_NAME:0
|
|
tmux select-pane -t $SESSION_NAME:0.0
|
|
|
|
SEED_EOF
|
|
|
|
chmod +x $SEED_SCRIPT
|
|
|
|
# --- 6. Execute Seed Script in Tmux ---
|
|
log_step "Starting seeding process in new window..."
|
|
tmux new-window -t $SESSION_NAME -n "Seeding" "SESSION_NAME=$SESSION_NAME $SEED_SCRIPT"
|
|
|
|
# --- 7. Finalize ---
|
|
trap 'rm -f $SEED_SCRIPT' EXIT
|
|
log_success "Environment ready. Attaching to session..."
|
|
tmux attach-session -t $SESSION_NAME
|