From 50bd998ecf5dc50647ef180ace315b8a5a5e732b Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 6 Feb 2026 17:56:56 -0600 Subject: [PATCH] CLAUDE: Add native development workflow for instant startup and hot-reload - Add dev-native.sh script for fast local development (10s vs 3-5min Docker builds) - Fix cookie security flags to respect APP_ENV (dev uses Secure=false, prod uses Secure=true) - Pin backend to Python 3.13 (3.14 not yet supported by pydantic-core) - Add comprehensive NATIVE_DEV_SETUP.md documentation - Update CLAUDE.md to recommend native dev workflow - Add .pids/ and .logs/ to .gitignore for native dev artifacts Benefits: - Instant startup (5-10 seconds) - Hot-reload enabled (backend + frontend) - Native debugging support - No Docker rebuilds needed - Discord OAuth works on localhost with proper cookie settings --- .gitignore | 4 + CLAUDE.md | 25 ++- NATIVE_DEV_SETUP.md | 255 ++++++++++++++++++++++++++ backend/.python-version | 1 + backend/app/utils/cookies.py | 21 ++- dev-native.sh | 338 +++++++++++++++++++++++++++++++++++ 6 files changed, 631 insertions(+), 13 deletions(-) create mode 100644 NATIVE_DEV_SETUP.md create mode 100644 backend/.python-version create mode 100755 dev-native.sh diff --git a/.gitignore b/.gitignore index a95a149..622bd26 100644 --- a/.gitignore +++ b/.gitignore @@ -201,3 +201,7 @@ redis_data/ .env.local .env.*.local .env.production + +# Native dev mode artifacts +.pids/ +.logs/ diff --git a/CLAUDE.md b/CLAUDE.md index b570267..99e4d51 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,14 +88,31 @@ strat-gameplay-webapp/ └── scripts/env-switch.sh # Environment profile switcher ``` -## Quick Start (All-Docker Workflow) +## Quick Start -The entire stack runs in Docker with a single command. No local Python or Node.js required. +### 🚀 Recommended: Native Development (Fast) -> **⚠️ ALWAYS USE PROD MODE**: Discord OAuth does not work in dev mode due to cookie/CORS configuration. Since this system isn't live yet, always build and run with `prod` mode for testing. +For day-to-day development, use native mode for instant startup and hot-reload: ```bash -# Production (optimized build) - USE THIS +# Start everything natively (5-10 seconds, hot-reload enabled) +./dev-native.sh start + +# View logs +./dev-native.sh logs + +# Stop when done +./dev-native.sh stop +``` + +**See [NATIVE_DEV_SETUP.md](NATIVE_DEV_SETUP.md) for full setup instructions.** + +### 🐳 Alternative: Docker Workflow + +For testing production builds or Docker-specific scenarios: + +```bash +# Production (optimized build) ./start.sh prod # Development - DO NOT USE (auth broken) diff --git a/NATIVE_DEV_SETUP.md b/NATIVE_DEV_SETUP.md new file mode 100644 index 0000000..d9cdf25 --- /dev/null +++ b/NATIVE_DEV_SETUP.md @@ -0,0 +1,255 @@ +# Native Development Setup + +Fast development workflow with instant startup, hot-reload, and no Docker rebuilds. + +## Why Native Development? + +**Problem**: Docker production builds take 3-5 minutes to rebuild on every change. + +**Solution**: Run backend and frontend natively on your machine, use Docker only for Redis. + +**Benefits**: +- ⚡ **Instant startup** (seconds, not minutes) +- 🔥 **Hot-reload** on file changes (backend + frontend) +- 🐛 **Native debugging** (attach debugger directly) +- 💾 **No rebuild overhead** (just save and refresh) + +## Prerequisites + +### 1. Install Dependencies + +```bash +# UV (Python package manager) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Node.js 22+ (if not already installed) +# https://nodejs.org/ + +# Docker (for Redis only) +# Already installed if you've been using ./start.sh +``` + +### 2. Configure Discord OAuth (One-Time Setup) + +You need to add `http://localhost:8000` as an allowed redirect URI in your Discord application. + +**Steps**: + +1. Go to [Discord Developer Portal](https://discord.com/developers/applications) +2. Select your application (ID: `1441192438055178420`) +3. Navigate to **OAuth2** → **General** +4. Under **Redirects**, click **Add Redirect** +5. Add: `http://localhost:8000/api/auth/discord/callback/server` +6. Click **Save Changes** + +**Your redirect URIs should now include**: +- ✅ `http://localhost:8000/api/auth/discord/callback/server` (dev) +- ✅ `https://gameplay-demo.manticorum.com/api/auth/discord/callback/server` (prod) + +## Quick Start + +### Option 1: New Native Dev Script (Recommended) + +```bash +# Start everything (instant, hot-reload) +./dev-native.sh start + +# View logs +./dev-native.sh logs + +# Stop everything +./dev-native.sh stop + +# Restart everything +./dev-native.sh restart +``` + +**What it does**: +1. Starts Redis in Docker (seconds) +2. Starts backend natively with `uv run uvicorn --reload` (instant) +3. Starts frontend natively with `npm run dev` (instant) + +**No rebuilds. Ever.** + +### Option 2: Manual Setup (Full Control) + +If you prefer to run each service in separate terminals: + +```bash +# Terminal 1: Redis (Docker) +docker compose up redis + +# Terminal 2: Backend (native) +cd backend +uv run uvicorn app.main:socket_app --host 0.0.0.0 --port 8000 --reload + +# Terminal 3: Frontend (native) +cd frontend-sba +npm run dev +``` + +## What Changed? + +### 1. Cookie Security Flags Now Respect `APP_ENV` + +**Before**: +- Cookies always used `Secure=true` + `SameSite=none` +- Required HTTPS (prod URLs only) +- Broke on localhost + +**After** (in `backend/app/utils/cookies.py`): +- `APP_ENV=development` → `Secure=false` + `SameSite=lax` (works on localhost) +- `APP_ENV=production` → `Secure=true` + `SameSite=none` (works cross-domain) + +### 2. Environment Files Already Configured + +The `.env.dev` files already have localhost URLs: + +**Backend** (`backend/.env.dev`): +```bash +DISCORD_SERVER_REDIRECT_URI=http://localhost:8000/api/auth/discord/callback/server +FRONTEND_URL=http://localhost:3000 +CORS_ORIGINS=["http://localhost:3000","http://localhost:3001"] +``` + +**Frontend** (`frontend-sba/.env.dev`): +```bash +NUXT_PUBLIC_API_URL=http://localhost:8000 +NUXT_PUBLIC_WS_URL=http://localhost:8000 +NUXT_PUBLIC_DISCORD_REDIRECT_URI=http://localhost:3000/auth/callback +``` + +The `dev-native.sh` script automatically copies these to `.env` when you run it. + +## Usage Patterns + +### Typical Development Flow + +```bash +# Start once +./dev-native.sh start + +# Make changes to backend/frontend code +# Changes auto-reload instantly! + +# View logs if needed +./dev-native.sh logs + +# Stop when done +./dev-native.sh stop +``` + +### Debugging + +**Backend** (Python): +```bash +# Stop the background process +./dev-native.sh stop + +# Run backend in foreground with debugger +cd backend +uv run python -m debugpy --listen 5678 -m uvicorn app.main:socket_app --host 0.0.0.0 --port 8000 --reload + +# Attach your IDE's debugger to localhost:5678 +``` + +**Frontend** (Vue): +```bash +# Browser DevTools work automatically with HMR +# Or use Vue DevTools browser extension +``` + +### Switching Between Native and Docker + +**To Native**: +```bash +# Stop Docker services +./start.sh stop + +# Start native +./dev-native.sh start +``` + +**To Docker** (for testing prod builds): +```bash +# Stop native +./dev-native.sh stop + +# Start Docker prod +./start.sh prod +``` + +## Troubleshooting + +### Discord OAuth Still Fails + +**Symptoms**: Redirect to Discord works, but callback fails with "Invalid redirect_uri" + +**Fixes**: +1. Verify you added `http://localhost:8000/api/auth/discord/callback/server` to Discord app +2. Check `backend/.env` has `APP_ENV=development` +3. Check `backend/.env` has `DISCORD_SERVER_REDIRECT_URI=http://localhost:8000/api/auth/discord/callback/server` +4. Restart backend: `./dev-native.sh restart` + +### Backend Won't Start + +**Check logs**: +```bash +tail -f .logs/backend.log +``` + +**Common issues**: +- Port 8000 already in use: `lsof -i :8000` (kill the process) +- Missing Python dependencies: `cd backend && uv sync` +- Database connection error: Check PostgreSQL is running + +### Frontend Won't Start + +**Check logs**: +```bash +tail -f .logs/frontend.log +``` + +**Common issues**: +- Port 3000 already in use: `lsof -i :3000` (kill the process) +- Missing node_modules: `cd frontend-sba && npm ci` +- Build error: Delete `.nuxt` and `.output` folders, restart + +### Redis Connection Error + +**Symptoms**: Backend logs show "Redis connection refused" + +**Fix**: +```bash +# Check Redis status +docker compose ps redis + +# Restart Redis if needed +docker compose restart redis +``` + +## Performance Comparison + +| Method | Initial Start | After Code Change | Rebuild Time | +|--------|--------------|-------------------|--------------| +| **Native Dev** | 5-10 seconds | Instant (HMR) | Never | +| Docker Dev | 30-60 seconds | Instant (volume mount) | 2-3 minutes | +| Docker Prod | **3-5 minutes** | **3-5 minutes** | **3-5 minutes** | + +## Notes + +- **Redis still uses Docker** (lightweight, starts in seconds) +- **PostgreSQL** is external (on `10.10.0.42`), not affected +- `.pids/` and `.logs/` directories are gitignored +- Backend uses `uvicorn --reload` (watches Python files) +- Frontend uses `nuxt dev` (HMR for Vue files) + +## When to Use Docker Prod Mode + +Use `./start.sh prod` when: +- Testing production builds before deployment +- Debugging Docker-specific issues +- Testing HTTPS/cross-origin scenarios +- Running integration tests + +For day-to-day development, use `./dev-native.sh start` for maximum speed. diff --git a/backend/.python-version b/backend/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/backend/app/utils/cookies.py b/backend/app/utils/cookies.py index ee7f777..900e215 100644 --- a/backend/app/utils/cookies.py +++ b/backend/app/utils/cookies.py @@ -9,6 +9,7 @@ Date: 2025-11-27 """ from fastapi import Response + from app.config import get_settings settings = get_settings() @@ -45,27 +46,29 @@ def set_auth_cookies( Security settings: - HttpOnly: Prevents XSS access to tokens - - Secure: True (required for SameSite=None) - - SameSite=None: Required for Safari to send cookies with fetch/XHR requests + - Secure: Respects APP_ENV (false in dev, true in prod) + - SameSite: "lax" in dev (localhost), "none" in prod (cross-origin) - Path: Limits cookie scope - Note: Safari's ITP treats SameSite=Lax cookies as "not sent" for XHR/fetch - requests even on same-origin. SameSite=None with Secure=true fixes this. + Note: In production with SameSite=None, Secure=true is required. + In development (localhost), we use SameSite=Lax which doesn't require Secure. Args: response: FastAPI Response object access_token: JWT access token refresh_token: JWT refresh token """ + secure = is_secure_context() + samesite = "none" if secure else "lax" + # Access token - short-lived, sent to all requests (needed for SSR cookie forwarding) - # Using SameSite=None for Safari compatibility (requires Secure=true) response.set_cookie( key=ACCESS_TOKEN_COOKIE, value=access_token, max_age=ACCESS_TOKEN_MAX_AGE, httponly=True, - secure=True, # Required for SameSite=None - samesite="none", # Safari requires this for fetch() to include cookies + secure=secure, + samesite=samesite, path="/", ) @@ -75,8 +78,8 @@ def set_auth_cookies( value=refresh_token, max_age=REFRESH_TOKEN_MAX_AGE, httponly=True, - secure=True, # Required for SameSite=None - samesite="none", # Safari requires this for fetch() to include cookies + secure=secure, + samesite=samesite, path="/api/auth", ) diff --git a/dev-native.sh b/dev-native.sh new file mode 100755 index 0000000..f979048 --- /dev/null +++ b/dev-native.sh @@ -0,0 +1,338 @@ +#!/bin/bash +# +# Paper Dynasty Game Engine - Native Development Mode +# +# Fast development workflow - no Docker rebuilds, instant restarts, hot-reload +# +# Usage: +# ./dev-native.sh start Start all services natively +# ./dev-native.sh stop Stop all services +# ./dev-native.sh logs Show logs from all services +# ./dev-native.sh restart Restart all services +# +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_status() { echo -e "${BLUE}[INFO]${NC} $1"; } +print_success() { echo -e "${GREEN}[OK]${NC} $1"; } +print_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +PID_DIR="$SCRIPT_DIR/.pids" +LOG_DIR="$SCRIPT_DIR/.logs" + +# Create directories +mkdir -p "$PID_DIR" "$LOG_DIR" + +# Check dependencies +check_dependencies() { + local missing=0 + + if ! command -v uv &> /dev/null; then + print_error "uv not found - install from https://docs.astral.sh/uv/" + missing=1 + fi + + if ! command -v node &> /dev/null; then + print_error "node not found - install Node.js 22+" + missing=1 + fi + + if ! command -v docker &> /dev/null; then + print_error "docker not found - needed for Redis" + missing=1 + fi + + if [[ $missing -eq 1 ]]; then + exit 1 + fi + + print_success "All dependencies found" +} + +# Setup environment files +setup_env() { + print_status "Setting up development environment files..." + + # Backend + if [[ ! -f "backend/.env" ]] || ! grep -q "localhost:8000" "backend/.env" 2>/dev/null; then + print_status "Copying backend/.env.dev -> backend/.env" + cp backend/.env.dev backend/.env + fi + + # Frontend + if [[ ! -f "frontend-sba/.env" ]] || ! grep -q "localhost:8000" "frontend-sba/.env" 2>/dev/null; then + print_status "Copying frontend-sba/.env.dev -> frontend-sba/.env" + cp frontend-sba/.env.dev frontend-sba/.env + fi + + print_success "Environment files ready" +} + +# Start Redis (Docker) +start_redis() { + print_status "Starting Redis (Docker)..." + + if docker ps | grep -q "strat-gameplay-webapp-redis"; then + print_warning "Redis already running" + return 0 + fi + + docker compose up -d redis + + # Wait for Redis to be healthy + local max_wait=30 + local waited=0 + + while [[ $waited -lt $max_wait ]]; do + if docker compose ps redis 2>/dev/null | grep -q "healthy"; then + print_success "Redis is healthy" + return 0 + fi + sleep 1 + waited=$((waited + 1)) + done + + print_error "Redis failed to start" + return 1 +} + +# Start Backend (native Python with uv) +start_backend() { + print_status "Starting backend (native uvicorn)..." + + cd backend + + # Check if backend is already running + if [[ -f "$PID_DIR/backend.pid" ]]; then + local pid=$(cat "$PID_DIR/backend.pid") + if ps -p "$pid" > /dev/null 2>&1; then + print_warning "Backend already running (PID: $pid)" + cd .. + return 0 + fi + fi + + # Start backend in background + nohup uv run python -m uvicorn app.main:socket_app \ + --host 0.0.0.0 \ + --port 8000 \ + --reload \ + > "$LOG_DIR/backend.log" 2>&1 & + + local pid=$! + echo $pid > "$PID_DIR/backend.pid" + + cd .. + + # Wait for backend to respond + local max_wait=10 + local waited=0 + + while [[ $waited -lt $max_wait ]]; do + if curl -sf http://localhost:8000/api/health > /dev/null 2>&1; then + print_success "Backend started (PID: $pid)" + return 0 + fi + sleep 1 + waited=$((waited + 1)) + done + + print_error "Backend failed to start - check logs: tail -f $LOG_DIR/backend.log" + return 1 +} + +# Start Frontend (native npm) +start_frontend() { + print_status "Starting frontend (native nuxt dev)..." + + cd frontend-sba + + # Check if frontend is already running + if [[ -f "$PID_DIR/frontend.pid" ]]; then + local pid=$(cat "$PID_DIR/frontend.pid") + if ps -p "$pid" > /dev/null 2>&1; then + print_warning "Frontend already running (PID: $pid)" + cd .. + return 0 + fi + fi + + # Ensure node_modules exists + if [[ ! -d "node_modules" ]]; then + print_status "Installing frontend dependencies..." + npm ci + fi + + # Start frontend in background + nohup npm run dev > "$LOG_DIR/frontend.log" 2>&1 & + + local pid=$! + echo $pid > "$PID_DIR/frontend.pid" + + cd .. + + # Wait for frontend to respond + local max_wait=30 + local waited=0 + + while [[ $waited -lt $max_wait ]]; do + if curl -sf http://localhost:3000 > /dev/null 2>&1; then + print_success "Frontend started (PID: $pid)" + return 0 + fi + sleep 1 + waited=$((waited + 1)) + done + + print_error "Frontend failed to start - check logs: tail -f $LOG_DIR/frontend.log" + return 1 +} + +# Stop all services +stop_services() { + print_status "Stopping all services..." + + # Stop backend + if [[ -f "$PID_DIR/backend.pid" ]]; then + local pid=$(cat "$PID_DIR/backend.pid") + if ps -p "$pid" > /dev/null 2>&1; then + print_status "Stopping backend (PID: $pid)..." + kill $pid + rm "$PID_DIR/backend.pid" + fi + fi + + # Stop frontend + if [[ -f "$PID_DIR/frontend.pid" ]]; then + local pid=$(cat "$PID_DIR/frontend.pid") + if ps -p "$pid" > /dev/null 2>&1; then + print_status "Stopping frontend (PID: $pid)..." + kill $pid + rm "$PID_DIR/frontend.pid" + fi + fi + + # Stop Redis + print_status "Stopping Redis..." + docker compose stop redis + + print_success "All services stopped" +} + +# Show logs +show_logs() { + print_status "Showing logs (Ctrl+C to exit)..." + echo "" + + # Use multitail if available, otherwise tail + if command -v multitail &> /dev/null; then + multitail -s 2 \ + -l "tail -f $LOG_DIR/backend.log" \ + -l "tail -f $LOG_DIR/frontend.log" + else + print_warning "Install 'multitail' for better log viewing" + tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" + fi +} + +# Start all services +start_all() { + print_status "Starting native development environment..." + echo "" + + check_dependencies + setup_env + + start_redis || exit 1 + start_backend || exit 1 + start_frontend || exit 1 + + echo "" + print_success "Development environment ready!" + echo "" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN} Native Development Mode${NC}" + echo -e "${GREEN}========================================${NC}" + echo "" + echo " Backend API: http://localhost:8000" + echo " API Docs: http://localhost:8000/docs" + echo " Frontend: http://localhost:3000" + echo "" + echo " Features:" + echo " ✓ Hot-reload enabled (backend + frontend)" + echo " ✓ No Docker rebuilds" + echo " ✓ Instant restarts" + echo "" + echo " Logs:" + echo " Backend: tail -f $LOG_DIR/backend.log" + echo " Frontend: tail -f $LOG_DIR/frontend.log" + echo " All: ./dev-native.sh logs" + echo "" + echo " Commands:" + echo " ./dev-native.sh logs View logs" + echo " ./dev-native.sh stop Stop services" + echo " ./dev-native.sh restart Restart services" + echo "" +} + +# Restart all services +restart_all() { + stop_services + sleep 2 + start_all +} + +# Show usage +show_usage() { + echo "Paper Dynasty - Native Development Mode" + echo "" + echo "Usage: ./dev-native.sh " + echo "" + echo "Commands:" + echo " start Start all services natively (instant, hot-reload)" + echo " stop Stop all services" + echo " logs Tail logs from all services" + echo " restart Restart all services" + echo "" + echo "Benefits:" + echo " • No Docker rebuilds (saves minutes)" + echo " • Instant startup" + echo " • Hot-reload on file changes (backend + frontend)" + echo " • Native debugging support" + echo "" + echo "Requirements:" + echo " • uv (Python package manager)" + echo " • Node.js 22+" + echo " • Docker (for Redis only)" + echo "" +} + +# Main +case "${1:-}" in + start) + start_all + ;; + stop) + stop_services + ;; + logs) + show_logs + ;; + restart) + restart_all + ;; + *) + show_usage + exit 1 + ;; +esac