refactor: migrate patch test schema from service-level to dedicated tables
- Remove patch_test_duration_hours from services table - Add new patch_tests table with service_ids array, notice_duration_hours, expiry_months - Add new user_patch_tests table linking users to patch_tests with tested_at - Update services handler to check patch_tests.service_ids for eligibility - Update booking creation to validate patch test requirements (24h notice, 6mo expiry) - Update booking completion to extend patch test validity (reset tested_at) - Update admin handlers for new patch test CRUD operations - Update test fixtures and test cases for new schema - Update seeding script to create patch_tests and link to gel services
This commit is contained in:
@@ -1179,6 +1179,63 @@ func CreateBookingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check patch test requirements for all services
|
||||||
|
for _, serviceID := range req.ServiceIDs {
|
||||||
|
// Find patch test for this service
|
||||||
|
var patchTestID string
|
||||||
|
var noticeHours int
|
||||||
|
err := db.DB.QueryRow(r.Context(), `
|
||||||
|
SELECT id, notice_duration_hours
|
||||||
|
FROM patch_tests
|
||||||
|
WHERE $1 = ANY(service_ids)
|
||||||
|
`, serviceID).Scan(&patchTestID, ¬iceHours)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Service requires a patch test - check if user has valid record
|
||||||
|
var testedAt time.Time
|
||||||
|
err = db.DB.QueryRow(r.Context(), `
|
||||||
|
SELECT tested_at
|
||||||
|
FROM user_patch_tests
|
||||||
|
WHERE user_id = $1 AND patch_test_id = $2
|
||||||
|
`, userID, patchTestID).Scan(&testedAt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// No valid patch test record
|
||||||
|
http.Error(w, "Patch test required for this service. Please complete a patch test first.", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if notice period has passed
|
||||||
|
eligibleFrom := testedAt.Add(time.Duration(noticeHours) * time.Hour)
|
||||||
|
if time.Now().Before(eligibleFrom) {
|
||||||
|
hoursLeft := time.Until(eligibleFrom).Hours()
|
||||||
|
http.Error(w, fmt.Sprintf("You must wait %.0f hours after your patch test before booking this service.", hoursLeft), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if patch test has expired
|
||||||
|
var expiryMonths int
|
||||||
|
err = db.DB.QueryRow(r.Context(), `SELECT expiry_months FROM patch_tests WHERE id = $1`, patchTestID).Scan(&expiryMonths)
|
||||||
|
if err == nil {
|
||||||
|
expiresAt := testedAt.AddDate(0, expiryMonths, 0)
|
||||||
|
if time.Now().After(expiresAt) {
|
||||||
|
http.Error(w, "Your patch test has expired. Please complete a new patch test.", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate start time is not in the past
|
||||||
|
if req.StartTime.IsZero() {
|
||||||
|
http.Error(w, "Start time is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.ServiceIDs) == 0 {
|
||||||
|
http.Error(w, "At least one service is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Validate start time is not in the past
|
// Validate start time is not in the past
|
||||||
if req.StartTime.Before(time.Now()) {
|
if req.StartTime.Before(time.Now()) {
|
||||||
http.Error(w, "Start time cannot be in the past", http.StatusBadRequest)
|
http.Error(w, "Start time cannot be in the past", http.StatusBadRequest)
|
||||||
@@ -1543,30 +1600,36 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Status == "completed" {
|
if req.Status == "completed" {
|
||||||
|
// When a booking is completed, extend patch test validity for any related patch tests
|
||||||
|
// Get all services in this booking
|
||||||
rows, err := db.DB.Query(r.Context(), `
|
rows, err := db.DB.Query(r.Context(), `
|
||||||
SELECT bs.service_id, s.patch_test_duration_hours
|
SELECT DISTINCT pt.id
|
||||||
FROM booking_services bs
|
FROM patch_tests pt
|
||||||
JOIN services s ON bs.service_id = s.id
|
JOIN booking_services bs ON bs.booking_id = $1
|
||||||
WHERE bs.booking_id = $1 AND s.patch_test_duration_hours > 0
|
WHERE pt.id IN (
|
||||||
|
SELECT pt_inner.id
|
||||||
|
FROM patch_tests pt_inner
|
||||||
|
WHERE bs.service_id = ANY(pt_inner.service_ids)
|
||||||
|
)
|
||||||
`, bookingID)
|
`, bookingID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to fetch services for patch test: %v", err)
|
log.Printf("Failed to fetch patch tests for booking %s: %v", bookingID, err)
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var serviceID string
|
var patchTestID string
|
||||||
var patchTestHours int
|
if err := rows.Scan(&patchTestID); err != nil {
|
||||||
if err := rows.Scan(&serviceID, &patchTestHours); err != nil {
|
log.Printf("Failed to scan patch test: %v", err)
|
||||||
log.Printf("Failed to scan service: %v", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Update or insert user_patch_tests record
|
||||||
_, err := db.DB.Exec(r.Context(), `
|
_, err := db.DB.Exec(r.Context(), `
|
||||||
INSERT INTO user_service_patch_tests (user_id, service_id, last_time)
|
INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
|
||||||
VALUES ($1, $2, NOW())
|
VALUES ($1, $2, NOW())
|
||||||
ON CONFLICT (user_id, service_id) DO UPDATE SET last_time = NOW()
|
ON CONFLICT (user_id, patch_test_id) DO UPDATE SET tested_at = NOW()
|
||||||
`, booking.User.ID, serviceID)
|
`, booking.User.ID, patchTestID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to record patch test: %v", err)
|
log.Printf("Failed to update patch test validity for user %s, patch test %s: %v", booking.User.ID, patchTestID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1599,6 +1662,7 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Return updated booking
|
// Return updated booking
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
if err := json.NewEncoder(w).Encode(booking); err != nil {
|
if err := json.NewEncoder(w).Encode(booking); err != nil {
|
||||||
log.Printf("Failed to encode booking response: %v", err)
|
log.Printf("Failed to encode booking response: %v", err)
|
||||||
|
|||||||
@@ -429,6 +429,67 @@ func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check patch test requirements for all services
|
||||||
|
for _, serviceID := range req.ServiceIDs {
|
||||||
|
// Find patch test for this service
|
||||||
|
var patchTestID string
|
||||||
|
var noticeHours int
|
||||||
|
err := db.DB.QueryRow(r.Context(), `
|
||||||
|
SELECT id, notice_duration_hours
|
||||||
|
FROM patch_tests
|
||||||
|
WHERE $1 = ANY(service_ids)
|
||||||
|
`, serviceID).Scan(&patchTestID, ¬iceHours)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Service requires a patch test - check if user has valid record
|
||||||
|
var testedAt time.Time
|
||||||
|
err = db.DB.QueryRow(r.Context(), `
|
||||||
|
SELECT tested_at
|
||||||
|
FROM user_patch_tests
|
||||||
|
WHERE user_id = $1 AND patch_test_id = $2
|
||||||
|
`, req.UserID, patchTestID).Scan(&testedAt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// No valid patch test record
|
||||||
|
http.Error(w, "Patch test required for this service. Please complete a patch test first.", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if notice period has passed
|
||||||
|
eligibleFrom := testedAt.Add(time.Duration(noticeHours) * time.Hour)
|
||||||
|
if req.StartTime.Before(eligibleFrom) {
|
||||||
|
hoursNeeded := time.Until(eligibleFrom).Hours()
|
||||||
|
http.Error(w, fmt.Sprintf("Booking time is before the %.0f hour notice period after patch test. Earliest booking: %s", hoursNeeded, eligibleFrom.Format("2006-01-02 15:04")), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if patch test has expired
|
||||||
|
var expiryMonths int
|
||||||
|
err = db.DB.QueryRow(r.Context(), `SELECT expiry_months FROM patch_tests WHERE id = $1`, patchTestID).Scan(&expiryMonths)
|
||||||
|
if err == nil {
|
||||||
|
expiresAt := testedAt.AddDate(0, expiryMonths, 0)
|
||||||
|
if req.StartTime.After(expiresAt) {
|
||||||
|
http.Error(w, "Your patch test has expired. Please complete a new patch test.", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate overrides
|
||||||
|
if req.UserID == "" {
|
||||||
|
http.Error(w, "User ID is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.StartTime.IsZero() {
|
||||||
|
http.Error(w, "Start time is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.ServiceIDs) == 0 {
|
||||||
|
http.Error(w, "At least one service is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Validate overrides
|
// Validate overrides
|
||||||
for _, override := range req.ServiceOverrides {
|
for _, override := range req.ServiceOverrides {
|
||||||
if override.ServiceID == "" {
|
if override.ServiceID == "" {
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ CREATE INDEX idx_verification_codes_expires ON verification_codes (expires_at) W
|
|||||||
-- =======================================
|
-- =======================================
|
||||||
|
|
||||||
CREATE TABLE patch_tests (
|
CREATE TABLE patch_tests (
|
||||||
|
id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
notice_duration_hours INT NOT NULL DEFAULT 24,
|
||||||
|
expiry_months INT NOT NULL DEFAULT 6,
|
||||||
|
service_ids CHAR(12)[] DEFAULT '{}'
|
||||||
|
);
|
||||||
id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
|
id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
|
||||||
name VARCHAR(100) NOT NULL,
|
name VARCHAR(100) NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
|
|||||||
+28
-14
@@ -290,14 +290,14 @@ sleep 1
|
|||||||
# 3. Create Services
|
# 3. Create Services
|
||||||
echo -e "\n${C_BLUE}💅 Creating Services...${C_RESET}"
|
echo -e "\n${C_BLUE}💅 Creating Services...${C_RESET}"
|
||||||
SERVICES=(
|
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":"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.","price":35.00,"duration_minutes":60,"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,"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":"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,"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,"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":"Gel Polish Removal","description":"Safe removal of existing gel polish.","price":10.00,"duration_minutes":20,"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":"Nail Art Add-on","description":"Custom nail art, per two fingers.","price":5.00,"duration_minutes":15,"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":"Gel Polish Full Set","description":"Full gel polish application - requires patch test 24h before.","price":45.00,"duration_minutes":60,"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}'
|
'{"name":"Luxury Gel Manicure","description":"Premium gel polish with extended massage - requires patch test 24h before.","price":55.00,"duration_minutes":75,"minimum_age_required":0}'
|
||||||
)
|
)
|
||||||
|
|
||||||
SERVICE_IDS=()
|
SERVICE_IDS=()
|
||||||
@@ -315,6 +315,26 @@ for svc in "${SERVICES[@]}"; do
|
|||||||
done
|
done
|
||||||
echo "${C_GREEN}✅ Created $success/$total Services${C_RESET}"
|
echo "${C_GREEN}✅ Created $success/$total Services${C_RESET}"
|
||||||
|
|
||||||
|
# 3b. Create Patch Tests (for gel services that require testing)
|
||||||
|
echo -e "\n${C_BLUE}🧪 Creating Patch Tests...${C_RESET}"
|
||||||
|
|
||||||
|
# Get the gel service IDs (indices 6 and 7)
|
||||||
|
GEL_SERVICE_IDS=("${SERVICE_IDS[6]}" "${SERVICE_IDS[7]}")
|
||||||
|
|
||||||
|
# Create patch test linking to gel services
|
||||||
|
docker exec postgres psql -U myuser -d mydb -c "
|
||||||
|
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
|
||||||
|
VALUES (
|
||||||
|
'Gel Allergy Test',
|
||||||
|
'Patch test for gel polish products - must be completed 24h before first gel service',
|
||||||
|
24,
|
||||||
|
6,
|
||||||
|
ARRAY['${GEL_SERVICE_IDS[0]}', '${GEL_SERVICE_IDS[1]}']
|
||||||
|
);
|
||||||
|
" > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "${C_GREEN}✅ Created patch test for gel services${C_RESET}"
|
||||||
|
|
||||||
# 4. Create Bookings
|
# 4. Create Bookings
|
||||||
echo -e "\n${C_BLUE}📅 Creating Bookings...${C_RESET}"
|
echo -e "\n${C_BLUE}📅 Creating Bookings...${C_RESET}"
|
||||||
|
|
||||||
@@ -485,12 +505,6 @@ if api_post "$BASE_URL/scheduling/exceptional-groups" "$NOV_BREAK" "November Bre
|
|||||||
if api_post "$BASE_URL/scheduling/exceptional-groups" "$XMAS_BREAK" "Christmas Holiday" "$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}"
|
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}"
|
echo -e "\n${C_GREEN}🎉 Seeding Complete!${C_RESET}"
|
||||||
echo -e "${C_YELLOW}Press ENTER to run tests...${C_RESET}"
|
echo -e "${C_YELLOW}Press ENTER to run tests...${C_RESET}"
|
||||||
read -r
|
read -r
|
||||||
|
|||||||
@@ -1,416 +0,0 @@
|
|||||||
#!/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 ---
|
|
||||||
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"
|
|
||||||
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"
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
tmux split-window -v -t $SESSION_NAME
|
|
||||||
tmux send-keys -t $SESSION_NAME "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 "cd frontend && npm run dev -- --host" Enter
|
|
||||||
tmux select-pane -t $SESSION_NAME:0.2 -T "Frontend"
|
|
||||||
|
|
||||||
# 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}'
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
# 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
|
|
||||||
echo " ✅ Confirmed: $name"
|
|
||||||
confirmed_count=$((confirmed_count+1))
|
|
||||||
else
|
|
||||||
echo " ⚠️ Failed to confirm: $name (HTTP $HTTP_CODE)"
|
|
||||||
echo " Response: $BODY"
|
|
||||||
fi
|
|
||||||
# Small sleep to prevent overwhelming the server
|
|
||||||
sleep 0.1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "${C_GREEN}✅ Confirmed $confirmed_count upcoming bookings${C_RESET}"
|
|
||||||
|
|
||||||
# 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..."
|
|
||||||
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
|
|
||||||
Reference in New Issue
Block a user