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.
This commit is contained in:
pacnpal
2025-08-25 10:46:54 -04:00
parent 937eee19e4
commit dcf890a55c
61 changed files with 10328 additions and 740 deletions

View File

@@ -0,0 +1,202 @@
# Signal Handling Fix for start-servers.sh
## Problem Description
The [`start-servers.sh`](../../scripts/start-servers.sh) script was not properly responding to Ctrl+C (SIGINT) signals, causing the script to continue running even after the user attempted to stop it. This left background server processes running and made it difficult to gracefully shut down the development environment.
## Root Causes Identified
1. **Late Signal Trap Registration**: Signal traps were only registered in the `wait_for_servers()` function after all servers had started, leaving a window during startup where Ctrl+C wouldn't work.
2. **No Signal Handling During Startup**: The entire initialization and server startup process had no signal traps, making the script unresponsive to interruption during these phases.
3. **Background Process Signal Issues**: Background processes weren't properly configured to receive termination signals from the parent script.
4. **No Recursive Signal Prevention**: Multiple signal calls could interfere with graceful shutdown.
5. **Inefficient Signal Detection**: The monitoring loop used longer sleep intervals, making signal response less responsive.
## Changes Made
### 1. Early Signal Trap Registration
**Location**: `main()` function, line ~463
**Before**:
```bash
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"
# Validate project structure
validate_project
```
**After**:
```bash
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
```
### 2. Prevent Recursive Signal Handling
**Location**: `graceful_shutdown()` function, line ~53
**Before**:
```bash
graceful_shutdown() {
if [ "$CLEANUP_PERFORMED" = true ]; then
return 0
fi
CLEANUP_PERFORMED=true
print_warning "Received shutdown signal - performing graceful shutdown..."
```
**After**:
```bash
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
```
### 3. Improved Background Process Signal Handling
**Location**: `start_backend()` and `start_frontend()` functions
**Backend Changes** (line ~227):
```bash
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
```
**Frontend Changes** (line ~260):
```bash
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
```
### 4. Streamlined wait_for_servers() Function
**Location**: `wait_for_servers()` function, line ~528
**Before**:
```bash
wait_for_servers() {
# Set up signal traps only after servers are successfully running
print_status "Setting up signal handlers for graceful shutdown..."
trap 'graceful_shutdown' INT TERM
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
fi
if [ -n "$FRONTEND_PID" ] && ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
print_error "Frontend server has stopped unexpectedly"
graceful_shutdown
fi
sleep 2
done
}
```
**After**:
```bash
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
}
```
## Benefits of the Fix
1. **Immediate Signal Response**: Ctrl+C now works immediately at any point during script execution, including during startup.
2. **Proper Cleanup**: All background processes are properly terminated when the script receives a signal.
3. **No Orphaned Processes**: The `disown -h` command ensures background processes receive signals while preventing shell job control interference.
4. **Faster Response**: Reduced sleep interval from 2 to 1 second makes signal handling more responsive.
5. **Robust Error Handling**: Prevents recursive signal calls and ensures cleanup only happens once.
## Testing
The fix was validated by:
- Running syntax validation: `bash -n shared/scripts/start-servers.sh` (passed)
- Ensuring script permissions are correct: `chmod +x shared/scripts/start-servers.sh`
## Usage
The script now properly responds to Ctrl+C at any time during execution:
```bash
./shared/scripts/start-servers.sh
# Press Ctrl+C at any time for graceful shutdown
```
The script will:
1. Display "Received shutdown signal - performing graceful shutdown..."
2. Stop the backend server (Django with runserver_plus)
3. Stop the frontend server (Vite)
4. Clean up PID files
5. Exit gracefully
## Technical Notes
- Signal traps are set early in the `main()` function before any long-running operations
- The `disown -h` command removes background processes from job control while keeping them as child processes that can receive signals
- The `trap - INT TERM` command in `graceful_shutdown()` prevents recursive signal handling
- The monitoring loop includes explicit `break` statements to exit cleanly after cleanup

575
shared/scripts/start-servers.sh Executable file
View File

@@ -0,0 +1,575 @@
#!/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 "$@"