#!/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 --- # Color codes for output C_RESET=$'\033[0m' C_GREEN=$'\033[32m' C_RED=$'\033[31m' C_BLUE=$'\033[34m' C_YELLOW=$'\033[33m' # Color codes for output 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 # --- 3d. Rustfs (don't wipe data, just restart) --- log_step "Restarting Rustfs (S3-compatible storage)..." docker compose restart rustfs > /dev/null 2>&1 log_success "Rustfs restarted" sleep 2 # --- 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 # Start interactive shell only. Stats will be shown after seeding. tmux send-keys -t $SESSION_NAME "docker exec -it postgres psql -U myuser -d mydb" Enter tmux select-pane -t $SESSION_NAME:0.0 -T "DB" # Pane 1: Backend (Split Horizontally) # Source .env to ensure all env vars are available to Go 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" # --- 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 --- # --- Helper: API Request --- 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 # FIX: # 1. tr -d '\n': Ensure JSON is treated as a single line (handles pretty-printing). # 2. sed 's/"user":{[^}]*}//': Remove the "user" object entirely. # [^}]* matches everything up to the first closing brace, which is safe for UserSummary (flat object). # 3. grep/cut: Extract the remaining root 'id' (which is now the Booking ID). echo "$body" | tr -d '\n' | sed 's/"user":{[^}]*}//' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | tr -d '\r\n' 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 } 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=18 # Admin user if api_post "$BASE_URL/register" "{\"firstName\":\"Admin\",\"lastName\":\"User\",\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASS\",\"phone\":\"+447000000000\",\"dateOfBirth\":\"1985-01-01\",\"agreedToPolicy\":true}" "Register Admin" "" > /dev/null; then success=$((success+1)); fi # Original test user if api_post "$BASE_URL/register" "{\"firstName\":\"Regular\",\"lastName\":\"User\",\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASS\",\"phone\":\"+447000000001\",\"dateOfBirth\":\"1990-05-15\",\"agreedToPolicy\":true}" "Register User" "" > /dev/null; then success=$((success+1)); fi # Additional test users (16 more) 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\":\"Oliver\",\"lastName\":\"Smith\",\"email\":\"oliver.smith@example.com\",\"password\":\"password\",\"phone\":\"+447000000003\",\"dateOfBirth\":\"1992-07-14\",\"agreedToPolicy\":true}" "Register Oliver Smith" "" > /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\":\"+447000000004\",\"dateOfBirth\":\"1995-11-08\",\"agreedToPolicy\":true}" "Register Sophie Williams" "" > /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\":\"+447000000005\",\"dateOfBirth\":\"1987-02-19\",\"agreedToPolicy\":true}" "Register Harry Brown" "" > /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\":\"+447000000006\",\"dateOfBirth\":\"1993-09-30\",\"agreedToPolicy\":true}" "Register Amelia Jones" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" "{\"firstName\":\"Jack\",\"lastName\":\"Taylor\",\"email\":\"jack.taylor@example.com\",\"password\":\"password\",\"phone\":\"+447000000007\",\"dateOfBirth\":\"1991-05-12\",\"agreedToPolicy\":true}" "Register Jack Taylor" "" > /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\":\"+447000000008\",\"dateOfBirth\":\"1989-12-25\",\"agreedToPolicy\":true}" "Register Isla Davies" "" > /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\":\"+447000000009\",\"dateOfBirth\":\"1994-04-17\",\"agreedToPolicy\":true}" "Register Thomas Evans" "" > /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\":\"+447000000010\",\"dateOfBirth\":\"1996-08-03\",\"agreedToPolicy\":true}" "Register Lily Wilson" "" > /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\":\"+447000000011\",\"dateOfBirth\":\"1986-01-29\",\"agreedToPolicy\":true}" "Register George Roberts" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" "{\"firstName\":\"Poppy\",\"lastName\":\"Thompson\",\"email\":\"poppy.thompson@example.com\",\"password\":\"password\",\"phone\":\"+447000000012\",\"dateOfBirth\":\"1997-06-21\",\"agreedToPolicy\":true}" "Register Poppy Thompson" "" > /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\":\"+447000000013\",\"dateOfBirth\":\"1990-10-11\",\"agreedToPolicy\":true}" "Register Charlie Wright" "" > /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\":\"+447000000014\",\"dateOfBirth\":\"1993-03-07\",\"agreedToPolicy\":true}" "Register Ava Walker" "" > /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\":\"+447000000015\",\"dateOfBirth\":\"1988-11-16\",\"agreedToPolicy\":true}" "Register Noah Robinson" "" > /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 if api_post "$BASE_URL/register" "{\"firstName\":\"Oscar\",\"lastName\":\"Hughes\",\"email\":\"oscar.hughes@example.com\",\"password\":\"password\",\"phone\":\"+447000000017\",\"dateOfBirth\":\"1991-02-04\",\"agreedToPolicy\":true}" "Register Oscar Hughes" "" > /dev/null; then success=$((success+1)); fi # Promote Admin docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET account_role = 'admin' WHERE email = '$ADMIN_EMAIL'" > /dev/null 2>&1 echo "${C_GREEN}โœ… Registered $success/$total Users${C_RESET}" # 2. Login echo -e "\n${C_BLUE}๐Ÿ”‘ Authenticating...${C_RESET}" LOGIN_RESP=$(curl -s -X POST -H 'Content-Type: application/json' -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASS\"}" "$BASE_URL/login") # FIX 2: Clean token extraction ADMIN_TOKEN=$(echo "$LOGIN_RESP" \ | tr -d '\r\n\t ' \ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p') if [ -z "$ADMIN_TOKEN" ]; then echo "โŒ Admin auth failed. Response: $LOGIN_RESP" exit 1 fi USER_LOGIN_RESP=$(curl -s -X POST -H 'Content-Type: application/json' -d "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASS\"}" "$BASE_URL/login") USER_TOKEN=$(echo "$USER_LOGIN_RESP" \ | tr -d '\r\n\t ' \ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p') if [ -z "$USER_TOKEN" ]; then echo "โŒ User auth failed. Response: $USER_LOGIN_RESP" exit 1 fi 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,"patch_test_duration_hours":0,"minimum_age_required":0}' '{"name":"Gel Manicure (BIAB)","description":"Hard-wearing gel polish with Builder In A Bottle base.","price":35.00,"duration_minutes":60,"patch_test_duration_hours":0,"minimum_age_required":0}' '{"name":"Luxury Pedicure","description":"Foot soak, scrub, mask, extended massage, and polish.","price":45.00,"duration_minutes":75,"patch_test_duration_hours":0,"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,"patch_test_duration_hours":0,"minimum_age_required":0}' '{"name":"Gel Polish Removal","description":"Safe removal of existing gel polish.","price":10.00,"duration_minutes":20,"patch_test_duration_hours":0,"minimum_age_required":0}' '{"name":"Nail Art Add-on","description":"Custom nail art, per two fingers.","price":5.00,"duration_minutes":15,"patch_test_duration_hours":0,"minimum_age_required":0}' '{"name":"Gel Polish Full Set","description":"Full gel polish application - requires patch test 48h before.","price":45.00,"duration_minutes":60,"patch_test_duration_hours":48,"minimum_age_required":0}' '{"name":"Luxury Gel Manicure","description":"Premium gel polish with extended massage - requires patch test 48h before.","price":55.00,"duration_minutes":75,"patch_test_duration_hours":48,"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" ]]; then ID=$(echo "$ID" | tr -cd '[:alnum:]-') SERVICE_IDS+=("$ID") success=$((success+1)) fi done echo "${C_GREEN}โœ… Created $success/$total Services${C_RESET}" # 4. Create 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_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}" # Call API and capture ID local id=$(api_post "$BASE_URL/bookings" "$json" "Book: $name" "$token") if [[ -n "$id" ]]; then id=$(echo "$id" | tr -cd '[:alnum:]-') LAST_BOOKING_ID="$id" return 0 else LAST_BOOKING_ID="" return 1 fi } get_svc() { echo "${SERVICE_IDS[$1]}"; } # Counters count_past=0 count_today=0 count_tomorrow=0 count_future=0 # Array to hold IDs of upcoming bookings for confirmation step UPCOMING_BOOKING_IDS=() UPCOMING_BOOKING_NAMES=() # --- PAST BOOKINGS (8 Total) --- echo -e "\n${C_YELLOW}๐Ÿ“… Creating 8 Past Bookings (Last 8 days)...${C_RESET}" for day_offset in {1..8}; do PAST_DATE=$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d) # Alternate times if [ $((day_offset % 2)) -eq 0 ]; then TIME="14:00:00" else TIME="10:00:00" fi # Alternate services SVC_IDX=$(( (day_offset % 2) )) NAME="Past ($PAST_DATE) - $(echo "${SERVICES[$SVC_IDX]}" | grep -o '"name":"[^"]*' | cut -d'"' -f4)" if create_booking "$USER_TOKEN" "$(format_london_time "$PAST_DATE" "$TIME")" "[\"$(get_svc $SVC_IDX)\"]" "" "$NAME"; then count_past=$((count_past+1)) fi done echo "${C_GREEN}โœ… Created $count_past/8 Past Bookings${C_RESET}" # --- TODAY (3) --- TODAY=$(TZ=Europe/London date +%Y-%m-%d) TOMORROW=$(TZ=Europe/London date -d "tomorrow" +%Y-%m-%d) if create_booking "$USER_TOKEN" "$(format_london_time "$TODAY" "09:30:00")" "[\"$(get_svc 0)\"]" "" "Today - Classic Manicure"; then count_today=$((count_today+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Today - Classic Manicure"); fi if create_booking "$USER_TOKEN" "$(format_london_time "$TODAY" "11:30:00")" "[\"$(get_svc 1)\"]" "" "Today - Gel Manicure"; then count_today=$((count_today+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Today - Gel Manicure"); fi if create_booking "$USER_TOKEN" "$(format_london_time "$TODAY" "14:00:00")" "[\"$(get_svc 2)\"]" "" "Today - Luxury Pedicure"; then count_today=$((count_today+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Today - Luxury Pedicure"); fi # --- TOMORROW (4) --- if create_booking "$USER_TOKEN" "$(format_london_time "$TOMORROW" "09:00:00")" "[\"$(get_svc 3)\"]" "" "Tomorrow - Express Mani & Pedi"; then count_tomorrow=$((count_tomorrow+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Tomorrow - Express Mani & Pedi"); fi if create_booking "$USER_TOKEN" "$(format_london_time "$TOMORROW" "10:30:00")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Tomorrow - Classic + Nail Art"; then count_tomorrow=$((count_tomorrow+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Tomorrow - Classic + Nail Art"); fi if create_booking "$USER_TOKEN" "$(format_london_time "$TOMORROW" "13:00:00")" "[\"$(get_svc 1)\"]" "" "Tomorrow - Gel Manicure"; then count_tomorrow=$((count_tomorrow+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Tomorrow - Gel Manicure"); fi if create_booking "$USER_TOKEN" "$(format_london_time "$TOMORROW" "15:30:00")" "[\"$(get_svc 2)\"]" "" "Tomorrow - Luxury Pedicure"; then count_tomorrow=$((count_tomorrow+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Tomorrow - Luxury Pedicure"); fi # --- FUTURE (30) --- # Spread over the next 15 days (Day +2 to Day +16) for day_offset in {2..16}; do FUTURE_DATE=$(TZ=Europe/London date -d "$TODAY +$day_offset days" +%Y-%m-%d) # Morning Slot (10:00) if create_booking "$USER_TOKEN" "$(format_london_time "$FUTURE_DATE" "10:00:00")" "[\"$(get_svc 0)\"]" "" "Future ($FUTURE_DATE) - Classic Manicure"; then count_future=$((count_future+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Future ($FUTURE_DATE) AM"); fi # Afternoon Slot (14:30) if create_booking "$USER_TOKEN" "$(format_london_time "$FUTURE_DATE" "14:30:00")" "[\"$(get_svc 1)\"]" "" "Future ($FUTURE_DATE) - Gel Manicure"; then count_future=$((count_future+1)); UPCOMING_BOOKING_IDS+=("$LAST_BOOKING_ID"); UPCOMING_BOOKING_NAMES+=("Future ($FUTURE_DATE) PM"); fi done # Summary TOTAL=$((count_today + count_tomorrow + count_future + count_past)) echo "${C_GREEN}โœ… Created $count_today/3 Bookings (Today)${C_RESET}" echo "${C_GREEN}โœ… Created $count_tomorrow/4 Bookings (Tomorrow)${C_RESET}" echo "${C_GREEN}โœ… Created $count_future/30 Bookings (Future)${C_RESET}" echo "${C_GREEN}โœ… Created $count_past/8 Bookings (Past)${C_RESET}" echo "${C_GREEN}โœ… Created $TOTAL/45 Bookings (Total)${C_RESET}" # 5. Confirm Random Half of Upcoming Bookings echo -e "\n${C_BLUE}๐Ÿ”’ Confirming Random Upcoming Bookings...${C_RESET}" confirmed_count=0 rejected_count=0 attempted_count=0 total_upcoming=${#UPCOMING_BOOKING_IDS[@]} # Small pause to ensure backend is ready after bulk creation sleep 1 for ((i=0; i<${#UPCOMING_BOOKING_IDS[@]}; i++)); do # FIX 3: Sanitize ID again just before use to ensure no hidden characters broke the array id="${UPCOMING_BOOKING_IDS[$i]}" name="${UPCOMING_BOOKING_NAMES[$i]}" # Ensure ID is not empty if [ -z "$id" ]; then continue fi # Random coin flip (0 or 1). If 1, confirm. if [ $((RANDOM % 2)) -eq 1 ]; then attempted_count=$((attempted_count+1)) # Send proper JSON with empty serviceOverrides array RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -d '{"serviceOverrides":[]}' \ "$BASE_URL/admin/bookings/$id/confirm") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | sed '$d') if [ "$HTTP_CODE" = "200" ]; then confirmed_count=$((confirmed_count+1)) else rejected_count=$((rejected_count+1)) fi # Small sleep to prevent overwhelming the server sleep 0.1 fi done echo "${C_GREEN}โœ… Confirmed $confirmed_count/$attempted_count Bookings${C_RESET}" if [ "$rejected_count" -gt 0 ]; then echo "${C_YELLOW}โš ๏ธ Rejected $rejected_count/$attempted_count (deposit/time restrictions)${C_RESET}" fi # 6. Exceptional Groups (2 Total) echo -e "\n${C_BLUE}๐Ÿ—“๏ธ Creating Exceptional Groups...${C_RESET}" NOV_BREAK='{"name":"November Break","description":"Short break period in November","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":["2025-11-10"]}' XMAS_BREAK='{"name":"Christmas Holiday Period","description":"Reduced hours for Christmas and New Year","hours":[{"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false},{"weekday":1,"startTime":"10:00:00","endTime":"15:00:00","isOpen":true},{"weekday":2,"startTime":"10:00:00","endTime":"15:00:00","isOpen":true},{"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":["2025-12-22","2025-12-29"]}' success=0 total=2 if api_post "$BASE_URL/scheduling/exceptional-groups" "$NOV_BREAK" "November Break" "$ADMIN_TOKEN" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/scheduling/exceptional-groups" "$XMAS_BREAK" "Christmas Holiday" "$ADMIN_TOKEN" > /dev/null; then success=$((success+1)); fi echo "${C_GREEN}โœ… Created $success/$total Exceptional Groups${C_RESET}" # --- UPDATE DB PANE STATS --- if [ -n "$SESSION_NAME" ]; then echo -e "\nโณ Updating DB pane stats..." tmux send-keys -t "$SESSION_NAME:0.0" 'SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY table_name\;' fi echo -e "\n${C_GREEN}๐ŸŽ‰ Seeding Complete!${C_RESET}" read -n1 -s -p "Press any key to close this window..." # --- Main Database Seeding Complete --- echo -e "\n${C_GREEN}๐ŸŽ‰ Main Database Seeding Complete!${C_RESET}" # --- Run Tests --- echo -e "\n${C_BLUE}๐Ÿงช Running Backend Tests...${C_RESET}" cd backend TEST_OUTPUT=$(go test -tags test -v ./... 2>&1 || true) TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^=== RUN" || echo "0") PASSED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- PASS" || echo "0") FAILED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- FAIL" || echo "0") if [ "$FAILED_TESTS" -gt 0 ]; then echo -e "${C_RED}โŒ Tests Failed: $PASSED_TESTS/$TOTAL_TESTS passed${C_RESET}" echo "" echo -e "${C_RED}--- Failed Test Details ---${C_RESET}" echo "$TEST_OUTPUT" | grep -A 3 "^--- FAIL" | head -20 echo "" echo -e "${C_YELLOW}โš ๏ธ Continuing anyway (tests are non-blocking)${C_RESET}" else echo -e "${C_GREEN}โœ… All Tests Passed: $PASSED_TESTS/$TOTAL_TESTS${C_RESET}" fi cd .. echo -e "\n${C_YELLOW}Press ENTER to close this seeding pane...${C_RESET}" read -r SEED_EOF chmod +x $SEED_SCRIPT # --- 6. Execute Seed Script in Tmux --- log_step "Starting seeding process in new window..." # Pass SESSION_NAME to the seed script 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