Files
pacnpal dcf890a55c feat: Implement Entity Suggestion Manager and Modal components
- Added EntitySuggestionManager.vue to manage entity suggestions and authentication.
- Created EntitySuggestionModal.vue for displaying suggestions and adding new entities.
- Integrated AuthManager for user authentication within the suggestion modal.
- Enhanced signal handling in start-servers.sh for graceful shutdown of servers.
- Improved server startup script to ensure proper cleanup and responsiveness to termination signals.
- Added documentation for signal handling fixes and usage instructions.
2025-08-25 10:46:54 -04:00

575 lines
18 KiB
Bash
Executable File

#!/bin/bash
# ThrillWiki Server Start Script
# Stops any running servers, clears caches, runs migrations, and starts both servers
# Works whether servers are currently running or not
# Usage: ./start-servers.sh
set -e # Exit on any error
# Global variables for process management
BACKEND_PID=""
FRONTEND_PID=""
CLEANUP_PERFORMED=false
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
BACKEND_DIR="$PROJECT_ROOT/backend"
FRONTEND_DIR="$PROJECT_ROOT/frontend"
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function for graceful shutdown
graceful_shutdown() {
if [ "$CLEANUP_PERFORMED" = true ]; then
return 0
fi
CLEANUP_PERFORMED=true
print_warning "Received shutdown signal - performing graceful shutdown..."
# Disable further signal handling to prevent recursive calls
trap - INT TERM
# Kill backend server if running
if [ -n "$BACKEND_PID" ] && kill -0 "$BACKEND_PID" 2>/dev/null; then
print_status "Stopping backend server (PID: $BACKEND_PID)..."
kill -TERM "$BACKEND_PID" 2>/dev/null || true
# Wait up to 10 seconds for graceful shutdown
local count=0
while [ $count -lt 10 ] && kill -0 "$BACKEND_PID" 2>/dev/null; do
sleep 1
count=$((count + 1))
done
# Force kill if still running
if kill -0 "$BACKEND_PID" 2>/dev/null; then
print_warning "Force killing backend server..."
kill -KILL "$BACKEND_PID" 2>/dev/null || true
fi
print_success "Backend server stopped"
else
print_status "Backend server not running or already stopped"
fi
# Kill frontend server if running
if [ -n "$FRONTEND_PID" ] && kill -0 "$FRONTEND_PID" 2>/dev/null; then
print_status "Stopping frontend server (PID: $FRONTEND_PID)..."
kill -TERM "$FRONTEND_PID" 2>/dev/null || true
# Wait up to 10 seconds for graceful shutdown
local count=0
while [ $count -lt 10 ] && kill -0 "$FRONTEND_PID" 2>/dev/null; do
sleep 1
count=$((count + 1))
done
# Force kill if still running
if kill -0 "$FRONTEND_PID" 2>/dev/null; then
print_warning "Force killing frontend server..."
kill -KILL "$FRONTEND_PID" 2>/dev/null || true
fi
print_success "Frontend server stopped"
else
print_status "Frontend server not running or already stopped"
fi
# Clear PID files if they exist
if [ -f "$PROJECT_ROOT/shared/logs/backend.pid" ]; then
rm -f "$PROJECT_ROOT/shared/logs/backend.pid"
fi
if [ -f "$PROJECT_ROOT/shared/logs/frontend.pid" ]; then
rm -f "$PROJECT_ROOT/shared/logs/frontend.pid"
fi
print_success "Graceful shutdown completed"
exit 0
}
# Function to kill processes by pattern
kill_processes() {
local pattern="$1"
local description="$2"
print_status "Checking for $description processes..."
# Find and kill processes
local pids=$(pgrep -f "$pattern" 2>/dev/null || true)
if [ -n "$pids" ]; then
print_status "Found $description processes, stopping them..."
echo "$pids" | xargs kill -TERM 2>/dev/null || true
sleep 2
# Force kill if still running
local remaining_pids=$(pgrep -f "$pattern" 2>/dev/null || true)
if [ -n "$remaining_pids" ]; then
print_warning "Force killing remaining $description processes..."
echo "$remaining_pids" | xargs kill -KILL 2>/dev/null || true
fi
print_success "$description processes stopped"
else
print_status "No $description processes found (this is fine)"
fi
}
# Function to clear Django cache
clear_django_cache() {
print_status "Clearing Django cache..."
cd "$BACKEND_DIR"
# Clear Django cache
if command -v uv >/dev/null 2>&1; then
if ! uv run manage.py clear_cache 2>clear_cache_error.log; then
print_error "Django clear_cache command failed:"
cat clear_cache_error.log
rm -f clear_cache_error.log
exit 1
else
rm -f clear_cache_error.log
fi
else
print_error "uv not found! Please install uv first."
exit 1
fi
# Remove Python cache files
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find . -name "*.pyc" -delete 2>/dev/null || true
find . -name "*.pyo" -delete 2>/dev/null || true
print_success "Django cache cleared"
}
# Function to clear frontend cache
clear_frontend_cache() {
print_status "Clearing frontend cache..."
cd "$FRONTEND_DIR"
# Remove node_modules/.cache if it exists
if [ -d "node_modules/.cache" ]; then
rm -rf node_modules/.cache
print_status "Removed node_modules/.cache"
fi
# Remove .nuxt cache if it exists (for Nuxt projects)
if [ -d ".nuxt" ]; then
rm -rf .nuxt
print_status "Removed .nuxt cache"
fi
# Remove dist/build directories
if [ -d "dist" ]; then
rm -rf dist
print_status "Removed dist directory"
fi
if [ -d "build" ]; then
rm -rf build
print_status "Removed build directory"
fi
# Clear pnpm cache
if command -v pnpm >/dev/null 2>&1; then
pnpm store prune 2>/dev/null || print_warning "Could not prune pnpm store"
else
print_error "pnpm not found! Please install pnpm first."
exit 1
fi
print_success "Frontend cache cleared"
}
# Function to run Django migrations
run_migrations() {
print_status "Running Django migrations..."
cd "$BACKEND_DIR"
# Check for pending migrations
if uv run python manage.py showmigrations --plan | grep -q "\[ \]"; then
print_status "Pending migrations found, applying..."
uv run python manage.py migrate
print_success "Migrations applied successfully"
else
print_status "No pending migrations found"
fi
# Run any custom management commands if needed
# uv run python manage.py collectstatic --noinput --clear 2>/dev/null || print_warning "collectstatic failed or not needed"
}
# Function to start backend server
start_backend() {
print_status "Starting Django backend server with runserver_plus (verbose output)..."
cd "$BACKEND_DIR"
# Start Django development server with runserver_plus for enhanced features and verbose output
print_status "Running: uv run python manage.py runserver_plus 8000 --verbosity=2"
uv run python manage.py runserver_plus 8000 --verbosity=2 &
BACKEND_PID=$!
# Make sure the background process can receive signals
disown -h "$BACKEND_PID" 2>/dev/null || true
# Wait a moment and check if it started successfully
sleep 3
if kill -0 $BACKEND_PID 2>/dev/null; then
print_success "Backend server started (PID: $BACKEND_PID)"
echo $BACKEND_PID > ../shared/logs/backend.pid
else
print_error "Failed to start backend server"
return 1
fi
}
# Function to start frontend server
start_frontend() {
print_status "Starting frontend server with verbose output..."
cd "$FRONTEND_DIR"
# Install dependencies if node_modules doesn't exist or package.json is newer
if [ ! -d "node_modules" ] || [ "package.json" -nt "node_modules" ]; then
print_status "Installing/updating frontend dependencies..."
pnpm install
fi
# Start frontend development server using Vite with explicit port, auto-open, and verbose output
# --port 5173: Use standard Vite port
# --open: Automatically open browser when ready
# --host localhost: Ensure it binds to localhost
# --debug: Enable debug logging
print_status "Starting Vite development server with verbose output and auto-browser opening..."
print_status "Running: pnpm vite --port 5173 --open --host localhost --debug"
pnpm vite --port 5173 --open --host localhost --debug &
FRONTEND_PID=$!
# Make sure the background process can receive signals
disown -h "$FRONTEND_PID" 2>/dev/null || true
# Wait a moment and check if it started successfully
sleep 3
if kill -0 $FRONTEND_PID 2>/dev/null; then
print_success "Frontend server started (PID: $FRONTEND_PID) - browser should open automatically"
echo $FRONTEND_PID > ../shared/logs/frontend.pid
else
print_error "Failed to start frontend server"
return 1
fi
}
# Function to detect operating system
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos";;
Linux*) echo "linux";;
*) echo "unknown";;
esac
}
# Function to open browser on the appropriate OS
open_browser() {
local url="$1"
local os=$(detect_os)
print_status "Opening browser to $url..."
case "$os" in
"macos")
if command -v open >/dev/null 2>&1; then
open "$url" 2>/dev/null || print_warning "Failed to open browser automatically"
else
print_warning "Cannot open browser: 'open' command not available"
fi
;;
"linux")
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$url" 2>/dev/null || print_warning "Failed to open browser automatically"
else
print_warning "Cannot open browser: 'xdg-open' command not available"
fi
;;
*)
print_warning "Cannot open browser automatically: Unsupported operating system"
;;
esac
}
# Function to verify frontend is responding (simplified since port is known)
verify_frontend_ready() {
local frontend_url="http://localhost:5173"
local max_checks=15
local check=0
print_status "Verifying frontend server is responding at $frontend_url..."
while [ $check -lt $max_checks ]; do
local response_code=$(curl -s -o /dev/null -w "%{http_code}" "$frontend_url" 2>/dev/null)
if [ "$response_code" = "200" ] || [ "$response_code" = "301" ] || [ "$response_code" = "302" ] || [ "$response_code" = "404" ]; then
print_success "Frontend server is responding (HTTP $response_code)"
return 0
fi
if [ $((check % 3)) -eq 0 ]; then
print_status "Waiting for frontend to respond... (attempt $((check + 1))/$max_checks)"
fi
sleep 2
check=$((check + 1))
done
print_warning "Frontend may still be starting up"
return 1
}
# Function to verify servers are responding
verify_servers_ready() {
print_status "Verifying both servers are responding..."
# Check backend
local backend_ready=false
local frontend_ready=false
local max_checks=10
local check=0
while [ $check -lt $max_checks ]; do
# Check backend
if [ "$backend_ready" = false ]; then
local backend_response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8000" 2>/dev/null)
if [ "$backend_response" = "200" ] || [ "$backend_response" = "301" ] || [ "$backend_response" = "302" ] || [ "$backend_response" = "404" ]; then
print_success "Backend server is responding (HTTP $backend_response)"
backend_ready=true
fi
fi
# Check frontend
if [ "$frontend_ready" = false ]; then
local frontend_response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:5173" 2>/dev/null)
if [ "$frontend_response" = "200" ] || [ "$frontend_response" = "301" ] || [ "$frontend_response" = "302" ] || [ "$frontend_response" = "404" ]; then
print_success "Frontend server is responding (HTTP $frontend_response)"
frontend_ready=true
fi
fi
# Both ready?
if [ "$backend_ready" = true ] && [ "$frontend_ready" = true ]; then
print_success "Both servers are responding!"
return 0
fi
sleep 2
check=$((check + 1))
done
# Show status of what's working
if [ "$backend_ready" = true ]; then
print_success "Backend is ready at http://localhost:8000"
else
print_warning "Backend may still be starting up"
fi
if [ "$frontend_ready" = true ]; then
print_success "Frontend is ready at http://localhost:5173"
else
print_warning "Frontend may still be starting up"
fi
}
# Function to create logs directory if it doesn't exist
ensure_logs_dir() {
local logs_dir="$PROJECT_ROOT/shared/logs"
if [ ! -d "$logs_dir" ]; then
mkdir -p "$logs_dir"
print_status "Created logs directory: $logs_dir"
fi
}
# Function to validate project structure
validate_project() {
if [ ! -d "$BACKEND_DIR" ]; then
print_error "Backend directory not found: $BACKEND_DIR"
exit 1
fi
if [ ! -d "$FRONTEND_DIR" ]; then
print_error "Frontend directory not found: $FRONTEND_DIR"
exit 1
fi
if [ ! -f "$BACKEND_DIR/manage.py" ]; then
print_error "Django manage.py not found in: $BACKEND_DIR"
exit 1
fi
if [ ! -f "$FRONTEND_DIR/package.json" ]; then
print_error "Frontend package.json not found in: $FRONTEND_DIR"
exit 1
fi
}
# Function to kill processes using specific ports
kill_port_processes() {
local port="$1"
local description="$2"
print_status "Checking for processes using port $port ($description)..."
# Find processes using the specific port
local pids=$(lsof -ti :$port 2>/dev/null || true)
if [ -n "$pids" ]; then
print_warning "Found processes using port $port, killing them..."
echo "$pids" | xargs kill -TERM 2>/dev/null || true
sleep 2
# Force kill if still running
local remaining_pids=$(lsof -ti :$port 2>/dev/null || true)
if [ -n "$remaining_pids" ]; then
print_warning "Force killing remaining processes on port $port..."
echo "$remaining_pids" | xargs kill -KILL 2>/dev/null || true
fi
print_success "Port $port cleared"
else
print_status "Port $port is available"
fi
}
# Function to check and clear required ports
check_and_clear_ports() {
print_status "Checking and clearing required ports..."
# Kill processes using our specific ports
kill_port_processes 8000 "Django backend"
kill_port_processes 5173 "Frontend Vite"
}
# Main execution function
main() {
print_status "ThrillWiki Server Start Script Starting..."
print_status "This script works whether servers are currently running or not."
print_status "Project root: $PROJECT_ROOT"
# Set up signal traps EARLY - before any long-running operations
print_status "Setting up signal handlers for graceful shutdown..."
trap 'graceful_shutdown' INT TERM
# Validate project structure
validate_project
# Ensure logs directory exists
ensure_logs_dir
# Check and clear ports
check_and_clear_ports
# Kill existing server processes (if any)
print_status "=== Stopping Any Running Servers ==="
print_status "Note: It's perfectly fine if no servers are currently running"
kill_processes "manage.py runserver" "Django backend"
kill_processes "pnpm.*dev\|npm.*dev\|yarn.*dev\|node.*dev" "Frontend development"
kill_processes "uvicorn\|gunicorn" "Python web servers"
# Clear caches
print_status "=== Clearing Caches ==="
clear_django_cache
clear_frontend_cache
# Run migrations
print_status "=== Running Migrations ==="
run_migrations
# Start servers
print_status "=== Starting Servers ==="
# Start backend first
if start_backend; then
print_success "Backend server is running"
else
print_error "Failed to start backend server"
exit 1
fi
# Start frontend
if start_frontend; then
print_success "Frontend server is running"
else
print_error "Failed to start frontend server"
print_status "Backend server is still running"
exit 1
fi
# Verify servers are responding
print_status "=== Verifying Servers ==="
verify_servers_ready
# Final status
print_status "=== Server Status ==="
print_success "✅ Backend server: http://localhost:8000 (Django with runserver_plus)"
print_success "✅ Frontend server: http://localhost:5173 (Vite with verbose output)"
print_status "🌐 Browser should have opened automatically via Vite --open"
print_status "🔧 To stop servers, use: kill \$(cat $PROJECT_ROOT/shared/logs/backend.pid) \$(cat $PROJECT_ROOT/shared/logs/frontend.pid)"
print_status "📋 Both servers are running with verbose output directly in your terminal"
print_success "🚀 All servers started successfully with full verbose output!"
# Keep the script running and wait for signals
wait_for_servers
}
# Wait for servers function to keep script running and handle signals
wait_for_servers() {
print_status "🚀 Servers are running! Press Ctrl+C for graceful shutdown."
print_status "📋 Backend: http://localhost:8000 | Frontend: http://localhost:5173"
# Keep the script alive and wait for signals
while [ "$CLEANUP_PERFORMED" != true ]; do
# Check if both servers are still running
if [ -n "$BACKEND_PID" ] && ! kill -0 "$BACKEND_PID" 2>/dev/null; then
print_error "Backend server has stopped unexpectedly"
graceful_shutdown
break
fi
if [ -n "$FRONTEND_PID" ] && ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
print_error "Frontend server has stopped unexpectedly"
graceful_shutdown
break
fi
# Use shorter sleep and check for signals more frequently
sleep 1
done
}
# Run main function (no traps set up initially)
main "$@"