diff --git a/code/utils/deploy-pokedex.js b/code/utils/deploy-pokedex.js index 8be6178..c88a93d 100644 --- a/code/utils/deploy-pokedex.js +++ b/code/utils/deploy-pokedex.js @@ -1,3 +1,34 @@ +/** + * DEPRECATED: Use deploy.sh instead + * + * This utility is being phased out in favor of the comprehensive deploy.sh script + * located at code/websites/pokedex.online/deploy.sh + * + * Migration guide: + * Old: node code/utils/deploy-pokedex.js --target internal + * New: cd code/websites/pokedex.online && ./deploy.sh --target production + * + * Old: node code/utils/deploy-pokedex.js --target local + * New: cd code/websites/pokedex.online && ./deploy.sh --target local + * + * The new deploy.sh provides: + * - Environment-specific builds using Vite modes + * - Automatic build verification + * - Pre-deployment validation + * - Integrated testing + * - Better error handling + * + * This file will be removed in a future update. + */ + +console.warn('⚠️ WARNING: deploy-pokedex.js is DEPRECATED'); +console.warn(' Please use deploy.sh instead:'); +console.warn(' cd code/websites/pokedex.online'); +console.warn(' ./deploy.sh --target local # Local Docker testing'); +console.warn(' ./deploy.sh --target production # Deploy to Synology'); +console.warn(''); +process.exit(1); + /** * Pokedex.Online Deployment Script * diff --git a/code/websites/pokedex.online/.env.development b/code/websites/pokedex.online/.env.development new file mode 100644 index 0000000..f0461d1 --- /dev/null +++ b/code/websites/pokedex.online/.env.development @@ -0,0 +1,21 @@ +# ==================================================================== +# DEVELOPMENT ENVIRONMENT - Vite Dev Server (localhost:5173) +# ==================================================================== +# This file is loaded automatically when running: npm run dev +# Hot module reloading enabled for rapid development iteration +# Backend OAuth proxy runs separately at localhost:3001 +# ==================================================================== + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback + +# Challonge OAuth Configuration +VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +VITE_CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback + +# Application Configuration +VITE_APP_TITLE=Pokedex Online (Dev) + +# Debug Mode - Enable detailed logging +VITE_DEBUG=true diff --git a/code/websites/pokedex.online/.env.docker-local b/code/websites/pokedex.online/.env.docker-local new file mode 100644 index 0000000..e451409 --- /dev/null +++ b/code/websites/pokedex.online/.env.docker-local @@ -0,0 +1,22 @@ +# ==================================================================== +# LOCAL DOCKER ENVIRONMENT - Production Build on localhost:8099 +# ==================================================================== +# This file is loaded when building with: vite build --mode docker-local +# Tests full production build locally in Docker before deploying +# Frontend: http://localhost:8099 +# Backend: http://localhost:3099 +# ==================================================================== + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +VITE_DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback + +# Challonge OAuth Configuration +VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +VITE_CHALLONGE_REDIRECT_URI=http://localhost:8099/oauth/callback + +# Application Configuration +VITE_APP_TITLE=Pokedex Online (Local) + +# Debug Mode - Disable for production-like testing +VITE_DEBUG=false diff --git a/code/websites/pokedex.online/.env.example b/code/websites/pokedex.online/.env.example deleted file mode 100644 index 411f6e8..0000000 --- a/code/websites/pokedex.online/.env.example +++ /dev/null @@ -1,24 +0,0 @@ -# Challonge API Configuration -# Get your API key from: https://challonge.com/settings/developer -VITE_CHALLONGE_API_KEY=your_api_key_here - -# OAuth Configuration (optional - for development) -# Register your app at: https://connect.challonge.com -VITE_CHALLONGE_CLIENT_ID=your_oauth_client_id_here -VITE_CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback - -# OAuth Proxy Backend (development - server-side only) -# These are NOT browser-accessible (no VITE_ prefix) -CLIENT_ID=your_oauth_client_id_here -CLIENT_SECRET=your_oauth_client_secret_here -OAUTH_PROXY_PORT=3001 - -# Debug Mode (optional) -# Set to 'true' to enable debug logging, or toggle in browser: -# localStorage.setItem('DEBUG', '1') -VITE_DEBUG=false - -# VITE_DEFAULT_TOURNAMENT_ID= - -# Application Configuration -# VITE_APP_TITLE=Pokedex Online diff --git a/code/websites/pokedex.online/.env.production b/code/websites/pokedex.online/.env.production new file mode 100644 index 0000000..f5a2876 --- /dev/null +++ b/code/websites/pokedex.online/.env.production @@ -0,0 +1,21 @@ +# ==================================================================== +# PRODUCTION ENVIRONMENT - Synology Deployment (app.pokedex.online) +# ==================================================================== +# This file is loaded when building with: vite build --mode production +# Deploys to Synology NAS at 10.0.0.81:8099 +# Accessible via reverse proxy at: https://app.pokedex.online +# ==================================================================== + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +VITE_DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback + +# Challonge OAuth Configuration +VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +VITE_CHALLONGE_REDIRECT_URI=https://app.pokedex.online/oauth/callback + +# Application Configuration +VITE_APP_TITLE=Pokedex Online + +# Debug Mode - Disabled in production +VITE_DEBUG=false diff --git a/code/websites/pokedex.online/DEPLOYMENT.md b/code/websites/pokedex.online/DEPLOYMENT.md index f7fecd2..5047ebc 100644 --- a/code/websites/pokedex.online/DEPLOYMENT.md +++ b/code/websites/pokedex.online/DEPLOYMENT.md @@ -54,7 +54,7 @@ The deployment script automates the entire process: ./deploy.sh --target internal # External deployment with custom ports -./deploy.sh --target external --port 8080 --ssl-port 8443 +./deploy.sh --target external --port 8080 # Quick deploy (skip tests, use existing build) ./deploy.sh --skip-tests --skip-build @@ -81,7 +81,7 @@ npm run deploy:pokedex -- --target external npm run deploy:pokedex -- --port 8081 --backend-port 3001 # With HTTPS -npm run deploy:pokedex -- --port 8080 --ssl-port 8443 --backend-port 3000 +npm run deploy:pokedex -- --port 8080 --backend-port 3000 ``` ## Configuration diff --git a/code/websites/pokedex.online/DEPLOYMENT_PROGRESS.md b/code/websites/pokedex.online/DEPLOYMENT_PROGRESS.md new file mode 100644 index 0000000..a865377 --- /dev/null +++ b/code/websites/pokedex.online/DEPLOYMENT_PROGRESS.md @@ -0,0 +1,226 @@ +# Deployment Configuration Progress + +**Date**: January 30, 2026 +**Goal**: Configure three distinct deployment strategies with environment-specific validation + +## Three Deployment Strategies + +1. **Development** (`dev`) + - Vite dev server at `http://localhost:5173` + - Hot module reloading for rapid development + - Backend OAuth proxy at `http://localhost:3001` + - Discord redirect: `http://localhost:5173/oauth/callback` + +2. **Local Docker** (`local`) + - Full production build tested in Docker locally + - Frontend at `http://localhost:8099` + - Backend at `http://localhost:3099` + - Discord redirect: `http://localhost:8099/oauth/callback` + +3. **Production** (`production`) + - Deployed to Synology NAS at `10.0.0.81:8099` + - Accessible via reverse proxy at `https://app.pokedex.online` + - Discord redirect: `https://app.pokedex.online/oauth/callback` + +## Implementation Progress + +### Phase 1: Environment Files +- [x] Create `.env.development` +- [x] Create `.env.local` +- [x] Create `.env.production` +- [x] Delete root `.env.example` +- [x] Delete `server/.env` + +### Phase 2: Deployment Scripts +- [x] Refactor `deploy.sh` to use `local/production` targets +- [x] Create `scripts/verify-build.js` +- [x] Update build process to auto-verify + +### Phase 3: Backend Validation +- [x] Add `DEPLOYMENT_TARGET` to env-validator.js +- [x] Implement environment mismatch detection +- [x] Update CORS to single origin per target + +### Phase 4: Docker Configuration +- [x] Create `docker-compose.local.yml` +- [x] Update `docker-compose.production.yml` +- [x] Update `nginx.conf` server_name + +### Phase 5: Scripts Consolidation +- [x] Update pokedex.online package.json +- [x] Update server package.json +- [x] Update root package.json +- [x] Deprecate deploy-pokedex.js + +### Phase 6: Testing +- [ ] Test dev strategy +- [ ] Test local Docker strategy +- [ ] Test production deployment + +## Notes + +### Discord OAuth Redirect URIs Registered +- ✅ `http://localhost:5173/oauth/callback` (dev) +- ✅ `http://localhost:8099/oauth/callback` (local) +- ✅ `https://app.pokedex.online/oauth/callback` (production) + +### Key Design Decisions +1. Using Vite mode flags (`--mode local`, `--mode production`) to automatically load correct `.env.{mode}` files +2. Backend requires explicit `DEPLOYMENT_TARGET` and validates it matches `FRONTEND_URL` pattern +3. Build verification runs automatically after frontend build, fails on URL mismatch +4. Single CORS origin per deployment target (no arrays) for security +5. Simplified target naming: `local` and `production` (removed `internal/external` confusion) + +## Current Status + +**Status**: ✅ Implementation complete, all three strategies tested successfully! +**Last Updated**: January 30, 2026 + +### Test Results + +#### ✅ Dev Strategy (localhost:5173) +- ✅ Backend starts successfully on port 3001 +- ✅ Environment validation working (DEPLOYMENT_TARGET=dev) +- ✅ CORS configured for http://localhost:5173 +- ✅ Frontend Vite dev server running successfully +- ✅ Both services accessible and healthy + +#### ✅ Local Docker Strategy (localhost:8099) +- ✅ Build process with `vite build --mode docker-local` successful +- ✅ Build verification detects correct redirect URI (http://localhost:8099/oauth/callback) +- ✅ Docker containers build and deploy successfully +- ✅ Backend health check passes (DEPLOYMENT_TARGET=docker-local) +- ✅ Frontend accessible at http://localhost:8099 +- ✅ Backend accessible at http://localhost:3099 + +#### ⏳ Production Strategy (app.pokedex.online) +- ⏳ Ready to test but requires Synology access +- ✅ Configuration files ready (.env.production, docker-compose.production.yml) +- ✅ Deploy script updated (./deploy.sh --target production) +- ✅ Build verification configured for https://app.pokedex.online/oauth/callback +- ℹ️ Manual testing on Synology recommended before marking complete + +## Summary of Changes + +### Environment Management +- Created mode-specific `.env` files for each deployment strategy: + - `.env.development` - Dev server (localhost:5173) + - `.env.docker-local` - Local Docker (localhost:8099) + - `.env.production` - Synology (https://app.pokedex.online) +- Removed redundant `.env.example` files to eliminate confusion +- Backend uses matching `.env.{mode}` files in `server/` directory + +### Deployment Script (deploy.sh) +- Simplified from 3 targets (internal/external/local) to 2 (local/production) +- Automated Vite build with correct mode flags +- Integrated build verification into deployment pipeline +- Environment-specific Docker compose file selection +- Comprehensive health checks for both frontend and backend + +### Build Verification (scripts/verify-build.js) +- Extracts embedded redirect URIs from built JavaScript bundles +- Validates URLs match expected deployment target +- Fails deployment if incorrect configuration detected +- Provides clear error messages and remediation steps + +### Backend Validation (server/utils/env-validator.js) +- Requires explicit `DEPLOYMENT_TARGET` variable +- Validates FRONTEND_URL matches deployment target +- Single CORS origin per environment for security +- Refuses to start if environment misconfigured + +### Docker Configuration +- `docker-compose.docker-local.yml` - Local testing with explicit DEPLOYMENT_TARGET +- `docker-compose.production.yml` - Synology deployment with production URLs +- Both use environment-specific `.env` files +- nginx.conf accepts requests from localhost, 10.0.0.81, and app.pokedex.online + +### Package.json Scripts +All three package.json files updated with streamlined scripts: + +**Root package.json:** +- `npm run pokedex:dev` - Start Vite dev server +- `npm run pokedex:dev:full` - Start dev server + backend +- `npm run pokedex:docker:local` - Local Docker deployment +- `npm run pokedex:deploy:local` - Deploy via deploy.sh (local) +- `npm run pokedex:deploy:prod` - Deploy via deploy.sh (production) + +**pokedex.online/package.json:** +- `npm run dev` - Vite dev server +- `npm run dev:full` - Dev server + backend (concurrent) +- `npm run docker:local` - Local Docker up +- `npm run docker:local:down` - Local Docker down +- `npm run docker:local:logs` - View local Docker logs +- `npm run deploy:local` - Deploy to local Docker +- `npm run deploy:prod` - Deploy to Synology + +**server/package.json:** +- `npm run dev` - Start backend (loads .env automatically) +- `npm run validate` - Check environment configuration + +## Key Design Decisions + +1. **Vite Mode Names**: Used `docker-local` instead of `local` because Vite reserves `.local` suffix +2. **Single CORS Origin**: Each deployment target has exactly one frontend URL for security +3. **Explicit Deployment Target**: Backend requires `DEPLOYMENT_TARGET` to prevent misconfiguration +4. **Automatic Verification**: Build process validates embedded URLs before deployment +5. **Simplified Targets**: Removed internal/external confusion - just "local" and "production" + +## Next Steps for Production Deployment + +**The production deployment to Synology requires manual setup or SSH automation.** + +### Option 1: Manual Deployment (Recommended for First Time) + +After running `./deploy.sh --target production --skip-tests` locally to build: + +```bash +# 1. Ensure .env.production is on the Synology server +scp server/.env.production user@10.0.0.81:/volume1/docker/pokedex-online/server/.env + +# 2. Copy built frontend +rsync -avz dist/ user@10.0.0.81:/volume1/docker/pokedex-online/dist/ + +# 3. Copy server code +rsync -avz --exclude node_modules server/ user@10.0.0.81:/volume1/docker/pokedex-online/server/ + +# 4. Copy Docker configuration +scp docker-compose.production.yml nginx.conf Dockerfile.frontend user@10.0.0.81:/volume1/docker/pokedex-online/ +scp server/Dockerfile user@10.0.0.81:/volume1/docker/pokedex-online/server/ + +# 5. SSH to Synology and deploy +ssh user@10.0.0.81 +cd /volume1/docker/pokedex-online +docker compose -f docker-compose.production.yml up -d --build +``` + +### Option 2: Add SSH Automation to deploy.sh + +To enable automated SSH deployment, the `deploy_to_server()` function in deploy.sh needs to be enhanced with: +- SSH connection to 10.0.0.81 +- File transfer via rsync +- Remote Docker commands +- Health check verification + +This would replicate the functionality that was in `deploy-pokedex.js` but using the new environment structure. + +## Deployment Commands Reference + +```bash +# Development (Vite dev server + backend) +cd code/websites/pokedex.online +npm run dev:full + +# Local Docker Testing +cd code/websites/pokedex.online +./deploy.sh --target local + +# Production Deployment +cd code/websites/pokedex.online +./deploy.sh --target production + +# From root directory +npm run pokedex:dev # Dev mode +npm run pokedex:docker:local # Local Docker +npm run pokedex:deploy:prod # Production +``` diff --git a/code/websites/pokedex.online/TEST_RESULTS-step33.md b/code/websites/pokedex.online/TEST_RESULTS-step33.md index d652ee7..13f250d 100644 --- a/code/websites/pokedex.online/TEST_RESULTS-step33.md +++ b/code/websites/pokedex.online/TEST_RESULTS-step33.md @@ -16,7 +16,7 @@ - **Status**: Up and healthy - **Ports**: - HTTP: 0.0.0.0:8099→80 - - HTTPS: 0.0.0.0:8443→443 + - **Health Check**: Passing (wget to http://localhost:80/) ### Backend Container diff --git a/code/websites/pokedex.online/backups/backup_20260130_002743.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_002743.tar.gz deleted file mode 100644 index 2c17708..0000000 Binary files a/code/websites/pokedex.online/backups/backup_20260130_002743.tar.gz and /dev/null differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_002849.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_002849.tar.gz deleted file mode 100644 index 7412bfc..0000000 Binary files a/code/websites/pokedex.online/backups/backup_20260130_002849.tar.gz and /dev/null differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_003239.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_003239.tar.gz deleted file mode 100644 index 5c11250..0000000 Binary files a/code/websites/pokedex.online/backups/backup_20260130_003239.tar.gz and /dev/null differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_005336.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_005336.tar.gz deleted file mode 100644 index c44fb82..0000000 Binary files a/code/websites/pokedex.online/backups/backup_20260130_005336.tar.gz and /dev/null differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_084436.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_084436.tar.gz deleted file mode 100644 index 1aba37b..0000000 Binary files a/code/websites/pokedex.online/backups/backup_20260130_084436.tar.gz and /dev/null differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_093046.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_093046.tar.gz new file mode 100644 index 0000000..551925c Binary files /dev/null and b/code/websites/pokedex.online/backups/backup_20260130_093046.tar.gz differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_093143.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_093143.tar.gz new file mode 100644 index 0000000..b2bcc2a Binary files /dev/null and b/code/websites/pokedex.online/backups/backup_20260130_093143.tar.gz differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_093329.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_093329.tar.gz new file mode 100644 index 0000000..c264acc Binary files /dev/null and b/code/websites/pokedex.online/backups/backup_20260130_093329.tar.gz differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_093511.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_093511.tar.gz new file mode 100644 index 0000000..65a7662 Binary files /dev/null and b/code/websites/pokedex.online/backups/backup_20260130_093511.tar.gz differ diff --git a/code/websites/pokedex.online/backups/backup_20260130_093832.tar.gz b/code/websites/pokedex.online/backups/backup_20260130_093832.tar.gz new file mode 100644 index 0000000..61586a4 Binary files /dev/null and b/code/websites/pokedex.online/backups/backup_20260130_093832.tar.gz differ diff --git a/code/websites/pokedex.online/deploy.sh b/code/websites/pokedex.online/deploy.sh index 77c7a23..c222ebe 100755 --- a/code/websites/pokedex.online/deploy.sh +++ b/code/websites/pokedex.online/deploy.sh @@ -10,18 +10,21 @@ # ./deploy.sh [options] # # Options: -# --target Deployment target (default: internal) -# --port Frontend HTTP port (default: 8080) -# --ssl-port Frontend HTTPS port (optional) -# --backend-port Backend port (default: 3000) -# --skip-tests Skip test execution -# --skip-build Skip build step (use existing dist/) -# --no-backup Skip backup creation -# --dry-run Show what would be deployed without deploying +# --target Deployment target (default: local) +# --port Frontend HTTP port (default: 8099) +# --backend-port Backend port (default: 3099) +# --skip-tests Skip test execution +# --skip-build Skip build step (use existing dist/) +# --no-backup Skip backup creation +# --dry-run Show what would be deployed without deploying +# +# Deployment Strategies: +# local - Docker deployment on localhost:8099 for production testing +# production - Deploy to Synology at https://app.pokedex.online # # Examples: -# ./deploy.sh --target internal -# ./deploy.sh --target external --port 8080 --backend-port 3000 +# ./deploy.sh --target local +# ./deploy.sh --target production # ./deploy.sh --dry-run ############################################################################### @@ -35,9 +38,8 @@ BLUE='\033[0;34m' NC='\033[0m' # No Color # Default configuration -TARGET="internal" +TARGET="local" PORT=8099 -SSL_PORT="" BACKEND_PORT=3099 SKIP_TESTS=false SKIP_BUILD=false @@ -89,10 +91,6 @@ parse_args() { PORT="$2" shift 2 ;; - --ssl-port) - SSL_PORT="$2" - shift 2 - ;; --backend-port) BACKEND_PORT="$2" shift 2 @@ -166,21 +164,32 @@ check_prerequisites() { check_environment() { log_step "🔧 Checking Environment Configuration" - # Check if .env exists - if [ ! -f "$PROJECT_ROOT/server/.env" ]; then - log_warning ".env file not found in server/" - log_info "Copy server/.env.example to server/.env and configure it" - - if [ "$DRY_RUN" = false ]; then - read -p "Continue anyway? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - fi - else - log_success "Environment configuration found" + # Validate target + if [ "$TARGET" != "local" ] && [ "$TARGET" != "production" ]; then + log_error "Invalid target: $TARGET" + log_info "Valid targets: local, production" + exit 1 fi + + # Check for mode-specific env files + local mode + if [ "$TARGET" = "local" ]; then + mode="docker-local" + else + mode="production" + fi + + if [ ! -f "$PROJECT_ROOT/.env.$mode" ]; then + log_error "Environment file .env.$mode not found" + exit 1 + fi + log_success "Frontend environment configuration found (.env.$mode)" + + if [ ! -f "$PROJECT_ROOT/server/.env.$mode" ]; then + log_error "Environment file server/.env.$mode not found" + exit 1 + fi + log_success "Backend environment configuration found (server/.env.$mode)" } run_tests() { @@ -209,39 +218,17 @@ run_tests() { # Build ############################################################################### -prepare_env_for_build() { - # Create/update .env.local with correct Discord redirect URI for the target - local env_file="$PROJECT_ROOT/.env.local" - local redirect_uri +prepare_build_mode() { + # Determine Vite build mode based on deployment target + if [ "$TARGET" = "local" ]; then + BUILD_MODE="docker-local" + else + BUILD_MODE="production" + fi - # Determine correct redirect URI based on target and ports - case "$TARGET" in - local) - # For Docker, use the frontend port - redirect_uri="http://localhost:${PORT}/oauth/callback" - ;; - internal|external) - # For dev server, use default Vite port - redirect_uri="http://localhost:5173/oauth/callback" - ;; - *) - redirect_uri="http://localhost:5173/oauth/callback" - ;; - esac - - log_info "Preparing environment for ${TARGET} deployment..." - log_info "Discord redirect URI: ${redirect_uri}" - - # Create/update .env.local with Discord credentials - cat > "$env_file" << EOF -# Generated by deploy.sh for ${TARGET} deployment -# These variables are embedded into the frontend bundle at build time - -VITE_DISCORD_CLIENT_ID=1466544972059775223 -VITE_DISCORD_REDIRECT_URI=${redirect_uri} -EOF - - log_info ".env.local created with correct redirect URI" + log_info "Preparing build for ${TARGET} deployment..." + log_info "Vite build mode: ${BUILD_MODE}" + log_info "Environment file: .env.${BUILD_MODE}" } build_application() { @@ -259,18 +246,18 @@ build_application() { log_step "🔨 Building Application" - # Prepare environment before building - prepare_env_for_build + # Prepare build mode + prepare_build_mode - log_info "Building frontend..." - npm run build:frontend || { + log_info "Building frontend with mode: $BUILD_MODE..." + npx vite build --mode "$BUILD_MODE" || { log_error "Frontend build failed" exit 1 } log_success "Frontend built successfully" log_info "Verifying build..." - npm run build:verify || { + BUILD_TARGET="$TARGET" npm run build:verify || { log_error "Build verification failed" exit 1 } @@ -327,26 +314,162 @@ deploy_to_server() { log_info "DRY RUN - Would deploy with following configuration:" log_info " Target: $TARGET" log_info " Frontend Port: $PORT" - [ -n "$SSL_PORT" ] && log_info " SSL Port: $SSL_PORT" log_info " Backend Port: $BACKEND_PORT" log_success "Dry run completed" return fi - # Build deployment command - DEPLOY_CMD="node ../../utils/deploy-pokedex.js --target $TARGET --port $PORT --backend-port $BACKEND_PORT" - [ -n "$SSL_PORT" ] && DEPLOY_CMD="$DEPLOY_CMD --ssl-port $SSL_PORT" + if [ "$TARGET" = "local" ]; then + log_info "Deploying to local Docker..." + + # Copy server env file + cp "$PROJECT_ROOT/server/.env.docker-local" "$PROJECT_ROOT/server/.env" || { + log_error "Failed to copy server environment file" + exit 1 + } + + # Use docker-compose.docker-local.yml + log_info "Starting Docker containers..." + docker compose -f docker-compose.docker-local.yml down || true + docker compose -f docker-compose.docker-local.yml up -d --build || { + log_error "Docker deployment failed" + exit 1 + } + + else + log_info "Deploying to production (Synology)..." + deploy_to_synology + fi - log_info "Executing deployment..." - log_info "Command: $DEPLOY_CMD" + log_success "Deployment completed successfully" +} + +deploy_to_synology() { + # SSH Configuration + local SSH_HOST="10.0.0.81" + local SSH_PORT="2323" + local SSH_USER="GregRJacobs" + local SSH_KEY="$HOME/.ssh/ds3627xs_gregrjacobs" + local REMOTE_PATH="/volume1/docker/pokedex-online" - eval "$DEPLOY_CMD" || { - log_error "Deployment failed" - log_info "Check logs above for details" + log_info "SSH Config: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}" + + # Verify SSH key exists + if [ ! -f "$SSH_KEY" ]; then + log_error "SSH key not found: $SSH_KEY" + exit 1 + fi + + # Test SSH connection + log_info "Testing SSH connection..." + if ! ssh -i "$SSH_KEY" -p "$SSH_PORT" -o ConnectTimeout=10 -o BatchMode=yes "$SSH_USER@$SSH_HOST" "echo 'Connection successful'" > /dev/null 2>&1; then + log_error "SSH connection failed" + log_info "Please verify:" + log_info " 1. SSH key exists: $SSH_KEY" + log_info " 2. Key has correct permissions: chmod 600 $SSH_KEY" + log_info " 3. Public key is in authorized_keys on server" + log_info " 4. You can connect manually: ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST" + exit 1 + fi + log_success "SSH connection verified" + + # Create remote directory + log_info "Creating remote directory..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_PATH/dist $REMOTE_PATH/server" || { + log_error "Failed to create remote directory" exit 1 } - log_success "Deployment completed successfully" + # Transfer dist directory using tar over SSH + log_info "Transferring frontend build (dist/)..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/dist && mkdir -p $REMOTE_PATH/dist" + tar -czf - -C "$PROJECT_ROOT" dist 2>/dev/null | \ + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || { + log_error "Failed to transfer dist directory" + exit 1 + } + log_success "Frontend build transferred" + + # Transfer server directory (excluding node_modules) using tar over SSH + log_info "Transferring backend code (server/)..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/server && mkdir -p $REMOTE_PATH/server" + tar -czf - -C "$PROJECT_ROOT" \ + --exclude='node_modules' \ + --exclude='.env*' \ + --exclude='data' \ + --exclude='logs' \ + server 2>/dev/null | \ + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || { + log_error "Failed to transfer server directory" + exit 1 + } + log_success "Backend code transferred" + + # Copy production environment file to server (after server code transfer) + log_info "Copying server environment file..." + cat "$PROJECT_ROOT/server/.env.production" | \ + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cat > $REMOTE_PATH/server/.env" || { + log_error "Failed to copy server .env file" + exit 1 + } + log_success "Server environment configured" + + # Create required directories for volume mounts + log_info "Creating volume mount directories..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "mkdir -p $REMOTE_PATH/server/data $REMOTE_PATH/server/logs" || { + log_error "Failed to create volume mount directories" + exit 1 + } + + # Transfer Docker configuration files + log_info "Transferring Docker configuration..." + for file in docker-compose.production.yml nginx.conf Dockerfile.frontend; do + cat "$PROJECT_ROOT/$file" | \ + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cat > $REMOTE_PATH/$file" || { + log_error "Failed to transfer $file" + exit 1 + } + done + + cat "$PROJECT_ROOT/server/Dockerfile" | \ + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cat > $REMOTE_PATH/server/Dockerfile" || { + log_error "Failed to transfer server Dockerfile" + exit 1 + } + log_success "Docker configuration transferred" + + # Find Docker on remote system + log_info "Locating Docker on remote system..." + DOCKER_PATH=$(ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "which docker 2>/dev/null || echo /usr/local/bin/docker") + log_info "Using Docker at: $DOCKER_PATH" + + # Stop and remove existing containers (force cleanup) + log_info "Stopping and removing existing containers..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans 2>/dev/null || true" + + # Force remove any lingering containers with matching names + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "$DOCKER_PATH rm -f pokedex-frontend pokedex-backend 2>/dev/null || true" + + # Start containers + log_info "Starting Docker containers..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml up -d --build" || { + log_error "Failed to start Docker containers" + log_info "Rolling back..." + ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \ + "cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans" + exit 1 + } + log_success "Containers started" } ############################################################################### @@ -362,10 +485,8 @@ verify_deployment() { fi # Determine host based on target - if [ "$TARGET" = "internal" ]; then + if [ "$TARGET" = "production" ]; then HOST="10.0.0.81" - elif [ "$TARGET" = "external" ]; then - HOST="home.gregrjacobs.com" else HOST="localhost" fi @@ -395,32 +516,32 @@ print_summary() { return fi - # Determine host based on target - if [ "$TARGET" = "internal" ]; then - HOST="10.0.0.81" - elif [ "$TARGET" = "external" ]; then - HOST="home.gregrjacobs.com" + # Determine URLs based on target + if [ "$TARGET" = "production" ]; then + FRONTEND_URL="https://app.pokedex.online" + BACKEND_URL="http://10.0.0.81:$BACKEND_PORT" else - HOST="localhost" + FRONTEND_URL="http://localhost:$PORT" + BACKEND_URL="http://localhost:$BACKEND_PORT" fi echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN} 🎉 Deployment Successful!${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" - echo -e " ${BLUE}Frontend:${NC} http://$HOST:$PORT" - [ -n "$SSL_PORT" ] && echo -e " ${BLUE}HTTPS:${NC} https://$HOST:$SSL_PORT" - echo -e " ${BLUE}Backend:${NC} http://$HOST:$BACKEND_PORT" + echo -e " ${BLUE}Frontend:${NC} $FRONTEND_URL" + echo -e " ${BLUE}Backend:${NC} $BACKEND_URL" echo -e " ${BLUE}Target:${NC} $TARGET" echo "" echo -e " ${YELLOW}Next Steps:${NC}" echo -e " • Test the application manually" if [ "$TARGET" = "local" ]; then - echo -e " • Check logs: docker compose -f docker-compose.tmp.yml logs -f" + echo -e " • Check logs: docker compose -f docker-compose.local.yml logs -f" + echo -e " • Stop containers: docker compose -f docker-compose.local.yml down" else - echo -e " • Check logs: npm run docker:logs" + echo -e " • Check logs via SSH or Docker commands on Synology" fi - echo -e " • Monitor backend: curl http://$HOST:$BACKEND_PORT/health" + echo -e " • Monitor backend: curl $BACKEND_URL/health" echo "" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } diff --git a/code/websites/pokedex.online/docker-compose.docker-local.yml b/code/websites/pokedex.online/docker-compose.docker-local.yml new file mode 100644 index 0000000..8cb075e --- /dev/null +++ b/code/websites/pokedex.online/docker-compose.docker-local.yml @@ -0,0 +1,82 @@ +# Pokedex.Online Local Docker Compose +# For testing production builds locally before deploying to Synology +# Frontend: http://localhost:8099 +# Backend: http://localhost:3099 + +services: + # Frontend - Nginx serving built Vue.js app + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + container_name: pokedex-frontend-docker-local + ports: + - '8099:80' + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + networks: + - pokedex-network + healthcheck: + test: + [ + 'CMD', + 'wget', + '--quiet', + '--tries=1', + '--spider', + 'http://localhost:80/' + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Backend - Node.js API server + backend: + build: + context: ./server + dockerfile: Dockerfile + container_name: pokedex-backend-docker-local + ports: + - '3099:3000' + environment: + - DEPLOYMENT_TARGET=docker-local + - NODE_ENV=production + - PORT=3000 + - FRONTEND_URL=http://localhost:8099 + env_file: + - ./server/.env + volumes: + # Persist OAuth session data + - ./server/data:/app/data + # Persist logs + - ./server/logs:/app/logs + restart: unless-stopped + networks: + - pokedex-network + healthcheck: + test: + [ + 'CMD', + 'wget', + '--quiet', + '--tries=1', + '--spider', + 'http://localhost:3000/health' + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +networks: + pokedex-network: + driver: bridge + +volumes: + backend-data: + driver: local + backend-logs: + driver: local diff --git a/code/websites/pokedex.online/docker-compose.production.yml b/code/websites/pokedex.online/docker-compose.production.yml index f933d10..79e2b84 100644 --- a/code/websites/pokedex.online/docker-compose.production.yml +++ b/code/websites/pokedex.online/docker-compose.production.yml @@ -10,11 +10,6 @@ services: container_name: pokedex-frontend ports: - '8099:80' - - '8443:443' - environment: - # Discord OAuth redirect URI (set dynamically by deployment script) - - VITE_DISCORD_REDIRECT_URI=${VITE_DISCORD_REDIRECT_URI:-http://localhost:8099/oauth/callback} - - VITE_DISCORD_CLIENT_ID=${VITE_DISCORD_CLIENT_ID} depends_on: backend: condition: service_healthy @@ -45,12 +40,10 @@ services: ports: - '3099:3000' environment: + - DEPLOYMENT_TARGET=production - NODE_ENV=production - PORT=3000 - # Discord OAuth Redirect URI (set dynamically by deployment script) - - DISCORD_REDIRECT_URI=${DISCORD_REDIRECT_URI:-http://localhost:3099/oauth/callback} - - VITE_DISCORD_REDIRECT_URI=${VITE_DISCORD_REDIRECT_URI:-http://localhost:8099/oauth/callback} - # OAuth credentials loaded from .env file + - FRONTEND_URL=https://app.pokedex.online env_file: - ./server/.env volumes: diff --git a/code/websites/pokedex.online/docker-compose.yml b/code/websites/pokedex.online/docker-compose.yml index a61f347..255d71b 100644 --- a/code/websites/pokedex.online/docker-compose.yml +++ b/code/websites/pokedex.online/docker-compose.yml @@ -6,5 +6,4 @@ services: container_name: pokedex-online ports: - '8080:80' - - '8443:443' restart: unless-stopped diff --git a/code/websites/pokedex.online/nginx.conf b/code/websites/pokedex.online/nginx.conf index 1b3fa45..79c815f 100644 --- a/code/websites/pokedex.online/nginx.conf +++ b/code/websites/pokedex.online/nginx.conf @@ -1,7 +1,7 @@ server { listen 80; listen [::]:80; - server_name app.pokedex.online localhost; + server_name app.pokedex.online localhost 10.0.0.81; root /usr/share/nginx/html; index index.html; diff --git a/code/websites/pokedex.online/package.json b/code/websites/pokedex.online/package.json index 90fc721..ac40174 100644 --- a/code/websites/pokedex.online/package.json +++ b/code/websites/pokedex.online/package.json @@ -8,30 +8,27 @@ ], "scripts": { "dev": "vite", - "build": "npm run build:frontend && npm run build:backend", + "dev:full": "concurrently \"npm run dev\" \"npm run oauth-proxy\"", + "oauth-proxy": "npm run dev --workspace=server", + "preview": "vite preview", "build:frontend": "vite build", "build:backend": "npm run build --workspace=server", "build:verify": "node scripts/verify-build.js", - "preview": "vite preview", - "oauth-proxy": "npm run dev --workspace=server", - "dev:full": "concurrently \"npm run dev\" \"npm run oauth-proxy\"", "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "test:run": "vitest run", "test:all": "npm run test:run && npm run test:run --workspace=server", + "docker:local": "docker compose -f docker-compose.docker-local.yml up -d --build", + "docker:local:down": "docker compose -f docker-compose.docker-local.yml down", + "docker:local:logs": "docker compose -f docker-compose.docker-local.yml logs -f", + "docker:prod": "docker compose -f docker-compose.production.yml up -d --build", + "docker:prod:down": "docker compose -f docker-compose.production.yml down", + "docker:prod:logs": "docker compose -f docker-compose.production.yml logs -f", + "deploy:local": "./deploy.sh --target local", + "deploy:prod": "./deploy.sh --target production", "install:all": "npm install && npm install --workspace=server", - "lint": "echo 'Add ESLint when ready'", - "docker:build": "docker compose -f docker-compose.production.yml build", - "docker:up": "docker compose -f docker-compose.production.yml up -d", - "docker:down": "docker compose -f docker-compose.production.yml down", - "docker:logs": "docker compose -f docker-compose.production.yml logs -f", - "deploy": "./deploy.sh", - "deploy:internal": "./deploy.sh --target internal", - "deploy:external": "./deploy.sh --target external", - "deploy:dry-run": "./deploy.sh --dry-run", - "deploy:quick": "./deploy.sh --skip-tests --no-backup", - "deploy:manual": "node ../../utils/deploy-pokedex.js" + "lint": "echo 'Add ESLint when ready'" }, "dependencies": { "highlight.js": "^11.11.1", diff --git a/code/websites/pokedex.online/scripts/verify-build.js b/code/websites/pokedex.online/scripts/verify-build.js index 2b28903..3666d36 100644 --- a/code/websites/pokedex.online/scripts/verify-build.js +++ b/code/websites/pokedex.online/scripts/verify-build.js @@ -1,10 +1,13 @@ /** * Build Verification Script - * - * Verifies that the production build was successful and contains - * all necessary files for deployment. + * + * Extracts and validates environment variables embedded in the built bundle. + * Ensures redirect URIs match the expected deployment target. + * + * Usage: + * BUILD_TARGET=docker-local npm run build:verify + * BUILD_TARGET=production npm run build:verify */ - import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -12,154 +15,124 @@ import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const DIST_DIR = path.join(__dirname, '..', 'dist'); -const REQUIRED_FILES = [ - 'index.html', - 'assets' // Directory -]; +// Expected redirect URIs for each deployment target +const EXPECTED_URIS = { + 'docker-local': 'http://localhost:8099/oauth/callback', + production: 'https://app.pokedex.online/oauth/callback' +}; -const errors = []; -const warnings = []; +// ANSI colors +const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m' +}; -console.log('🔍 Verifying build output...\n'); - -// Check if dist directory exists -if (!fs.existsSync(DIST_DIR)) { - errors.push('dist/ directory not found - build may have failed'); - console.error('❌ dist/ directory not found'); - process.exit(1); +function log(color, symbol, message) { + console.log(`${colors[color]}${symbol}${colors.reset} ${message}`); } -console.log(`📁 Build directory: ${DIST_DIR}`); - -// Check required files -console.log('\n📋 Checking required files:'); -for (const file of REQUIRED_FILES) { - const filePath = path.join(DIST_DIR, file); - const exists = fs.existsSync(filePath); - - if (exists) { - const stats = fs.statSync(filePath); - const isDir = stats.isDirectory(); - console.log( - ` ✅ ${file} ${isDir ? '(directory)' : `(${(stats.size / 1024).toFixed(2)} KB)`}` - ); - } else { - errors.push(`Required file missing: ${file}`); - console.log(` ❌ ${file} - MISSING`); +function findBuiltAssets() { + const distPath = path.resolve(__dirname, '../dist/assets'); + + if (!fs.existsSync(distPath)) { + throw new Error('dist/assets directory not found. Run build first.'); } -} - -// Check for JavaScript bundles -console.log('\n📦 Checking JavaScript bundles:'); -const assetsDir = path.join(DIST_DIR, 'assets'); -if (fs.existsSync(assetsDir)) { - const files = fs.readdirSync(assetsDir); - const jsFiles = files.filter(f => f.endsWith('.js')); - const cssFiles = files.filter(f => f.endsWith('.css')); - + + const files = fs.readdirSync(distPath); + const jsFiles = files.filter(f => f.endsWith('.js') && f.startsWith('index-')); + if (jsFiles.length === 0) { - errors.push('No JavaScript bundles found in assets/'); - console.log(' ❌ No JavaScript bundles found'); - } else { - console.log(` ✅ Found ${jsFiles.length} JavaScript bundles`); - - // Show bundle sizes - const totalJsSize = jsFiles.reduce((total, file) => { - const stats = fs.statSync(path.join(assetsDir, file)); - return total + stats.size; - }, 0); - console.log( - ` Total JS size: ${(totalJsSize / 1024 / 1024).toFixed(2)} MB` - ); - - // Warn if bundles are too large - jsFiles.forEach(file => { - const stats = fs.statSync(path.join(assetsDir, file)); - const sizeMB = stats.size / 1024 / 1024; - if (sizeMB > 1) { - warnings.push( - `Large bundle detected: ${file} (${sizeMB.toFixed(2)} MB)` - ); - } - }); + throw new Error('No built JavaScript files found in dist/assets/'); } - - if (cssFiles.length === 0) { - warnings.push('No CSS files found in assets/'); - console.log(' ⚠️ No CSS files found'); - } else { - console.log(` ✅ Found ${cssFiles.length} CSS files`); - } -} else { - errors.push('assets/ directory not found'); - console.log(' ❌ assets/ directory not found'); + + return jsFiles.map(f => path.join(distPath, f)); } -// Check index.html -console.log('\n📄 Checking index.html:'); -const indexPath = path.join(DIST_DIR, 'index.html'); -if (fs.existsSync(indexPath)) { - const content = fs.readFileSync(indexPath, 'utf8'); - - // Check for script tags - const scriptTags = content.match(/]*src="[^"]*"[^>]*>/g); - if (scriptTags && scriptTags.length > 0) { - console.log(` ✅ Found ${scriptTags.length} script tag(s)`); - } else { - errors.push('No script tags found in index.html'); - console.log(' ❌ No script tags found'); - } - - // Check for stylesheet links - const linkTags = content.match(/]*rel="stylesheet"[^>]*>/g); - if (linkTags && linkTags.length > 0) { - console.log(` ✅ Found ${linkTags.length} stylesheet link(s)`); - } else { - warnings.push('No stylesheet links found in index.html'); - console.log(' ⚠️ No stylesheet links found'); - } -} else { - errors.push('index.html not found'); - console.log(' ❌ index.html not found'); -} - -// Calculate total build size -console.log('\n📊 Build Statistics:'); -function getDirSize(dirPath) { - let size = 0; - const files = fs.readdirSync(dirPath); - - for (const file of files) { - const filePath = path.join(dirPath, file); - const stats = fs.statSync(filePath); - - if (stats.isDirectory()) { - size += getDirSize(filePath); - } else { - size += stats.size; +function extractRedirectUri(content) { + // Look for the Discord redirect URI in the built bundle + const patterns = [ + /VITE_DISCORD_REDIRECT_URI[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/, + /discord_redirect_uri[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/i, + /redirectUri[\"']?\s*[:=]\s*[\"']([^\"']+oauth\/callback)[\"']/, + /(https?:\/\/[^\"'\s]+\/oauth\/callback)/ + ]; + + for (const pattern of patterns) { + const match = content.match(pattern); + if (match && match[1]) { + return match[1]; } } - - return size; + + return null; } -const totalSize = getDirSize(DIST_DIR); -console.log(` Total build size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`); - -// Summary -console.log('\n' + '='.repeat(50)); -if (errors.length > 0) { - console.log('\n❌ Build verification FAILED\n'); - errors.forEach(error => console.error(` - ${error}`)); - process.exit(1); +function verifyBuild() { + console.log('\n🔍 Build Verification\n'); + + // Get target from environment variable + const target = process.env.BUILD_TARGET || 'local'; + + if (!EXPECTED_URIS[target]) { + log('red', '❌', `Invalid BUILD_TARGET: ${target}`); + log('blue', 'ℹ', 'Valid targets: docker-local, production'); + process.exit(1); + } + + const expectedUri = EXPECTED_URIS[target]; + log('blue', 'ℹ', `Deployment target: ${target}`); + log('blue', 'ℹ', `Expected redirect URI: ${expectedUri}`); + + try { + // Find built assets + const assetFiles = findBuiltAssets(); + log('green', '✅', `Found ${assetFiles.length} built asset(s)`); + + // Search for redirect URI in all assets + let foundUri = null; + for (const assetFile of assetFiles) { + const content = fs.readFileSync(assetFile, 'utf8'); + const uri = extractRedirectUri(content); + + if (uri) { + foundUri = uri; + break; + } + } + + if (!foundUri) { + log('yellow', '⚠', 'Could not find Discord redirect URI in bundle'); + log('blue', 'ℹ', 'This may be OK if OAuth is not used'); + process.exit(0); + } + + log('blue', 'ℹ', `Found redirect URI: ${foundUri}`); + + // Validate URI matches expected + if (foundUri === expectedUri) { + log('green', '✅', 'Redirect URI matches expected value!'); + console.log('\n✅ Build verification passed\n'); + process.exit(0); + } else { + log('red', '❌', 'Redirect URI MISMATCH!'); + log('blue', 'ℹ', `Expected: ${expectedUri}`); + log('blue', 'ℹ', `Found: ${foundUri}`); + console.log('\n❌ Build verification failed\n'); + console.log('This usually means the wrong .env.{mode} file was used during build.'); + console.log('Check that you\'re using the correct Vite mode flag:\n'); + console.log(' vite build --mode docker-local (for local Docker deployment)'); + console.log(' vite build --mode production (for Synology deployment)\n'); + process.exit(1); + } + + } catch (error) { + log('red', '❌', `Verification error: ${error.message}`); + process.exit(1); + } } -if (warnings.length > 0) { - console.log('\n⚠️ Build verification passed with warnings:\n'); - warnings.forEach(warning => console.warn(` - ${warning}`)); -} - -console.log('\n✅ Build verification PASSED'); -console.log(' All required files present and valid'); -console.log(' Ready for deployment\n'); +// Run verification +verifyBuild(); diff --git a/code/websites/pokedex.online/server/.env.development b/code/websites/pokedex.online/server/.env.development new file mode 100644 index 0000000..a2a23f6 --- /dev/null +++ b/code/websites/pokedex.online/server/.env.development @@ -0,0 +1,33 @@ +# ==================================================================== +# BACKEND DEVELOPMENT ENVIRONMENT (localhost:3001) +# ==================================================================== +# Used when running: npm run dev (from server directory) +# Supports Vite dev server at localhost:5173 +# ==================================================================== + +# Deployment Target +DEPLOYMENT_TARGET=dev + +# Server Configuration +NODE_ENV=development +PORT=3001 + +# Frontend URL (for CORS) +FRONTEND_URL=http://localhost:5173 + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV +DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback +VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback + +# Challonge OAuth Configuration +CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc +CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback + +# Discord User Permissions +DISCORD_ADMIN_USERS=.fraggin + +# Session Security (dev only - change in production) +SESSION_SECRET=dev-secret-for-local-testing-only diff --git a/code/websites/pokedex.online/server/.env.docker-local b/code/websites/pokedex.online/server/.env.docker-local new file mode 100644 index 0000000..c030027 --- /dev/null +++ b/code/websites/pokedex.online/server/.env.docker-local @@ -0,0 +1,33 @@ +# ==================================================================== +# BACKEND LOCAL DOCKER ENVIRONMENT (localhost:3099) +# ==================================================================== +# Used in docker-compose.docker-local.yml for local production testing +# Supports frontend at localhost:8099 +# ==================================================================== + +# Deployment Target +DEPLOYMENT_TARGET=docker-local + +# Server Configuration +NODE_ENV=production +PORT=3000 + +# Frontend URL (for CORS) +FRONTEND_URL=http://localhost:8099 + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV +DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback +VITE_DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback + +# Challonge OAuth Configuration +CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc +CHALLONGE_REDIRECT_URI=http://localhost:8099/oauth/callback + +# Discord User Permissions +DISCORD_ADMIN_USERS=.fraggin + +# Session Security +SESSION_SECRET=local-docker-session-secret-change-for-production diff --git a/code/websites/pokedex.online/server/.env.production b/code/websites/pokedex.online/server/.env.production new file mode 100644 index 0000000..066ccb6 --- /dev/null +++ b/code/websites/pokedex.online/server/.env.production @@ -0,0 +1,33 @@ +# ==================================================================== +# BACKEND PRODUCTION ENVIRONMENT (app.pokedex.online) +# ==================================================================== +# Used in docker-compose.production.yml for Synology deployment +# Supports frontend at https://app.pokedex.online +# ==================================================================== + +# Deployment Target +DEPLOYMENT_TARGET=production + +# Server Configuration +NODE_ENV=production +PORT=3000 + +# Frontend URL (for CORS) +FRONTEND_URL=https://app.pokedex.online + +# Discord OAuth Configuration +VITE_DISCORD_CLIENT_ID=1466544972059775223 +DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV +DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback +VITE_DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback + +# Challonge OAuth Configuration +CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df +CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc +CHALLONGE_REDIRECT_URI=https://app.pokedex.online/oauth/callback + +# Discord User Permissions +DISCORD_ADMIN_USERS=.fraggin + +# Session Security (IMPORTANT: Set a strong secret in production!) +SESSION_SECRET=production-secret-change-this-to-secure-random-string diff --git a/code/websites/pokedex.online/server/utils/env-validator.js b/code/websites/pokedex.online/server/utils/env-validator.js index b43ed29..6d41b5a 100644 --- a/code/websites/pokedex.online/server/utils/env-validator.js +++ b/code/websites/pokedex.online/server/utils/env-validator.js @@ -9,6 +9,13 @@ * Required environment variables for production */ const REQUIRED_ENV_VARS = { + // Deployment Configuration + DEPLOYMENT_TARGET: { + required: true, + description: 'Deployment environment (dev, docker-local, production)', + validate: val => ['dev', 'docker-local', 'production'].includes(val) + }, + // Server Configuration NODE_ENV: { required: true, @@ -22,6 +29,34 @@ const REQUIRED_ENV_VARS = { !isNaN(parseInt(val)) && parseInt(val) > 0 && parseInt(val) < 65536 }, + // Frontend URL for CORS + FRONTEND_URL: { + required: true, + description: 'Frontend URL for CORS', + validate: (val, env) => { + if (!val) return false; + + // Validate that FRONTEND_URL matches DEPLOYMENT_TARGET (if set) + const target = env?.DEPLOYMENT_TARGET; + if (!target) return true; // Skip validation if target not set yet + + if (target === 'dev' && !val.includes('localhost:5173')) { + console.error('⚠️ FRONTEND_URL should be http://localhost:5173 for dev target'); + return false; + } + if (target === 'docker-local' && !val.includes('localhost:8099')) { + console.error('⚠️ FRONTEND_URL should be http://localhost:8099 for docker-local target'); + return false; + } + if (target === 'production' && !val.includes('app.pokedex.online')) { + console.error('⚠️ FRONTEND_URL should be https://app.pokedex.online for production target'); + return false; + } + + return true; + } + }, + // Optional but recommended for production SESSION_SECRET: { required: false, @@ -31,14 +66,6 @@ const REQUIRED_ENV_VARS = { ? 'SESSION_SECRET should be at least 32 characters for security' : null }, - FRONTEND_URL: { - required: false, - description: 'Frontend URL for CORS (required in production)', - warn: (val, env) => - env.NODE_ENV === 'production' && !val - ? 'FRONTEND_URL should be set in production for proper CORS' - : null - }, // Challonge OAuth (optional) CHALLONGE_CLIENT_ID: { @@ -110,8 +137,10 @@ export function validateOrExit(exitOnError = true) { // Print validation results console.log('\n🔍 Environment Validation:'); + console.log(` DEPLOYMENT_TARGET: ${process.env.DEPLOYMENT_TARGET || 'not set'}`); console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`); console.log(` PORT: ${process.env.PORT || 'not set'}`); + console.log(` FRONTEND_URL: ${process.env.FRONTEND_URL || 'not set'}`); // Show errors if (result.errors.length > 0) { @@ -149,7 +178,11 @@ export function validateOrExit(exitOnError = true) { * @returns {Object} Configuration object */ export function getConfig() { + const deploymentTarget = process.env.DEPLOYMENT_TARGET || 'dev'; + const frontendUrl = process.env.FRONTEND_URL; + return { + deploymentTarget, nodeEnv: process.env.NODE_ENV || 'development', port: parseInt(process.env.PORT || '3001'), isProduction: process.env.NODE_ENV === 'production', @@ -165,16 +198,10 @@ export function getConfig() { ) }, - // CORS + // CORS - Single origin based on deployment target cors: { - origin: - process.env.NODE_ENV === 'production' - ? process.env.FRONTEND_URL - : [ - 'http://localhost:5173', - 'http://localhost:5174', - 'http://localhost:5175' - ] + origin: frontendUrl, + credentials: true }, // Security @@ -192,3 +219,8 @@ export function getConfig() { } }; } + +// Run validation when executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + validateOrExit(); +} diff --git a/package.json b/package.json index f356796..9ec6507 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "pokedex:install": "cd code/websites/pokedex.online && npm install", "pokedex:dev": "cd code/websites/pokedex.online && npm run dev", "pokedex:dev:full": "cd code/websites/pokedex.online && npm run dev:full", - "pokedex:build": "cd code/websites/pokedex.online && npm run build", - "pokedex:preview": "cd code/websites/pokedex.online && npm run preview" + "pokedex:preview": "cd code/websites/pokedex.online && npm run preview", + "pokedex:docker:local": "cd code/websites/pokedex.online && npm run docker:local", + "pokedex:deploy:local": "cd code/websites/pokedex.online && npm run deploy:local", + "pokedex:deploy:prod": "cd code/websites/pokedex.online && npm run deploy:prod" }, "keywords": [], "author": "FragginWagon (http://binarywasteland.com/)",