Backend Complete: Interface Refactoring & Service Container Enhancements
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Build System Helper - Shows available build commands and their descriptions
|
||||
*/
|
||||
|
||||
const commands = {
|
||||
'Development Commands': {
|
||||
'npm run dev': 'Start development server with hot reload',
|
||||
'npm run watch': 'Watch mode TypeScript compilation',
|
||||
'npm run typecheck': 'Type checking without code generation'
|
||||
},
|
||||
'Build Commands': {
|
||||
'npm run build': 'Standard build: clean → compile → copy assets',
|
||||
'npm run build:clean': 'Clean the dist directory',
|
||||
'npm run build:compile': 'Compile TypeScript to JavaScript',
|
||||
'npm run build:copy-assets': 'Copy non-TS files to dist directory',
|
||||
'npm run build:docker': 'Build for Docker (no tests/migrations)'
|
||||
},
|
||||
'Production Build Commands': {
|
||||
'npm run build:production': 'Full production build with linting, tests, and migrations',
|
||||
'npm run build:advanced': 'Advanced build script with custom options',
|
||||
'npm run build:advanced:prod': 'Advanced production build with all validations',
|
||||
'npm run build:advanced:ci': 'CI/CD friendly build (skips linting)',
|
||||
'npm run deploy:prod': 'Build for production deployment'
|
||||
},
|
||||
'Database Commands': {
|
||||
'npm run migration:run': 'Run pending database migrations',
|
||||
'npm run migration:show': 'Show migration status',
|
||||
'npm run migration:generate <name>': 'Generate new migration',
|
||||
'npm run migration:create <name>': 'Create empty migration',
|
||||
'npm run migration:revert': 'Revert last migration',
|
||||
'npm run migration:full <name>': 'Create, generate, and run migration'
|
||||
},
|
||||
'Testing Commands': {
|
||||
'npm test': 'Run all tests',
|
||||
'npm run test:watch': 'Run tests in watch mode',
|
||||
'npm run test:coverage': 'Run tests with coverage report',
|
||||
'npm run test:redis': 'Run Redis-specific tests'
|
||||
},
|
||||
'Deployment Scripts': {
|
||||
'scripts/deploy.sh': 'Full Linux/Mac deployment script',
|
||||
'scripts/deploy.bat': 'Full Windows deployment script'
|
||||
}
|
||||
};
|
||||
|
||||
function showCommands() {
|
||||
console.log('🔧 SerpentRace Backend Build System\n');
|
||||
|
||||
Object.entries(commands).forEach(([category, categoryCommands]) => {
|
||||
console.log(`\x1b[36m${category}\x1b[0m`);
|
||||
console.log('=' .repeat(category.length));
|
||||
|
||||
Object.entries(categoryCommands).forEach(([command, description]) => {
|
||||
console.log(` \x1b[32m${command.padEnd(35)}\x1b[0m ${description}`);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('\x1b[33mQuick Start:\x1b[0m');
|
||||
console.log(' npm run build # Basic build');
|
||||
console.log(' npm run build:production # Production build');
|
||||
console.log(' npm run dev # Development server\n');
|
||||
|
||||
console.log('\x1b[33mDocumentation:\x1b[0m');
|
||||
console.log(' See BUILD.md for detailed documentation');
|
||||
}
|
||||
|
||||
function checkBuildStatus() {
|
||||
const distPath = path.join(__dirname, '..', 'dist');
|
||||
|
||||
if (fs.existsSync(distPath)) {
|
||||
const stats = fs.statSync(distPath);
|
||||
console.log(`\x1b[32m✅ Last build:\x1b[0m ${stats.mtime.toLocaleString()}`);
|
||||
|
||||
const indexPath = path.join(distPath, 'Api', 'index.js');
|
||||
if (fs.existsSync(indexPath)) {
|
||||
console.log('\x1b[32m✅ Main entry point built successfully\x1b[0m');
|
||||
} else {
|
||||
console.log('\x1b[31m❌ Main entry point missing\x1b[0m');
|
||||
}
|
||||
} else {
|
||||
console.log('\x1b[33m⚠️ No build found - run "npm run build" first\x1b[0m');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
showCommands();
|
||||
} else if (args.includes('--status') || args.includes('-s')) {
|
||||
checkBuildStatus();
|
||||
} else if (args.includes('--quick') || args.includes('-q')) {
|
||||
console.log('🚀 Quick build starting...');
|
||||
try {
|
||||
execSync('npm run build', { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error('❌ Quick build failed');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
showCommands();
|
||||
checkBuildStatus();
|
||||
|
||||
console.log('\n\x1b[33mOptions:\x1b[0m');
|
||||
console.log(' --help, -h Show this help');
|
||||
console.log(' --status, -s Show build status only');
|
||||
console.log(' --quick, -q Run quick build');
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Comprehensive Build Script for SerpentRace Backend
|
||||
* Handles TypeScript compilation, migrations, asset copying, and validation
|
||||
*/
|
||||
|
||||
interface BuildOptions {
|
||||
runMigrations?: boolean;
|
||||
runTests?: boolean;
|
||||
skipLinting?: boolean;
|
||||
production?: boolean;
|
||||
}
|
||||
|
||||
class BuildManager {
|
||||
private distDir = join(__dirname, '..', 'dist');
|
||||
|
||||
constructor(private options: BuildOptions = {}) {}
|
||||
|
||||
private log(message: string, level: 'info' | 'error' | 'warn' = 'info') {
|
||||
const timestamp = new Date().toISOString();
|
||||
const prefix = {
|
||||
info: '🔧',
|
||||
error: '❌',
|
||||
warn: '⚠️'
|
||||
}[level];
|
||||
console.log(`${prefix} [${timestamp}] ${message}`);
|
||||
}
|
||||
|
||||
private execute(command: string, description: string) {
|
||||
this.log(`${description}...`);
|
||||
try {
|
||||
execSync(command, {
|
||||
stdio: 'inherit',
|
||||
cwd: join(__dirname, '..')
|
||||
});
|
||||
this.log(`✅ ${description} completed successfully`);
|
||||
} catch (error) {
|
||||
this.log(`❌ ${description} failed`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async clean() {
|
||||
this.log('Cleaning previous build...');
|
||||
if (existsSync(this.distDir)) {
|
||||
rmSync(this.distDir, { recursive: true, force: true });
|
||||
this.log('✅ Previous build cleaned');
|
||||
} else {
|
||||
this.log('No previous build found');
|
||||
}
|
||||
}
|
||||
|
||||
async typecheck() {
|
||||
this.execute('npx tsc --noEmit', 'Type checking');
|
||||
}
|
||||
|
||||
async lint() {
|
||||
if (this.options.skipLinting) {
|
||||
this.log('Skipping linting...', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
// For now, just check if TypeScript compiles without errors
|
||||
this.log('Linting (basic type checking)...');
|
||||
await this.typecheck();
|
||||
}
|
||||
|
||||
async runTests() {
|
||||
if (!this.options.runTests) {
|
||||
this.log('Skipping tests...', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
this.execute('npm test', 'Running tests');
|
||||
}
|
||||
|
||||
async runMigrations() {
|
||||
if (!this.options.runMigrations) {
|
||||
this.log('Skipping database migrations...', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.log('Checking migration status...');
|
||||
execSync('npm run migration:show', {
|
||||
stdio: 'pipe',
|
||||
cwd: join(__dirname, '..')
|
||||
});
|
||||
|
||||
this.execute('npm run migration:run', 'Running database migrations');
|
||||
} catch (error) {
|
||||
this.log('Migration check/run failed - this might be expected in CI/CD environments', 'warn');
|
||||
if (this.options.production) {
|
||||
throw error; // In production builds, migrations should work
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compile() {
|
||||
this.execute('npx tsc', 'Compiling TypeScript');
|
||||
}
|
||||
|
||||
async copyAssets() {
|
||||
this.execute('node scripts/copy-assets.js', 'Copying assets');
|
||||
}
|
||||
|
||||
async validateBuild() {
|
||||
this.log('Validating build output...');
|
||||
|
||||
const expectedFiles = [
|
||||
'dist/Api/index.js',
|
||||
'dist/Api/index.d.ts'
|
||||
];
|
||||
|
||||
const missingFiles = expectedFiles.filter(file =>
|
||||
!existsSync(join(__dirname, '..', file))
|
||||
);
|
||||
|
||||
if (missingFiles.length > 0) {
|
||||
this.log(`Missing expected build files: ${missingFiles.join(', ')}`, 'error');
|
||||
throw new Error('Build validation failed');
|
||||
}
|
||||
|
||||
this.log('✅ Build validation completed');
|
||||
}
|
||||
|
||||
async build() {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
this.log('🚀 Starting SerpentRace Backend build process...');
|
||||
|
||||
// Step 1: Clean previous build
|
||||
await this.clean();
|
||||
|
||||
// Step 2: Lint code (if not skipped)
|
||||
await this.lint();
|
||||
|
||||
// Step 3: Run tests (if enabled)
|
||||
await this.runTests();
|
||||
|
||||
// Step 4: Run migrations (if enabled)
|
||||
await this.runMigrations();
|
||||
|
||||
// Step 5: Compile TypeScript
|
||||
await this.compile();
|
||||
|
||||
// Step 6: Copy assets
|
||||
await this.copyAssets();
|
||||
|
||||
// Step 7: Validate build
|
||||
await this.validateBuild();
|
||||
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
this.log(`🎉 Build completed successfully in ${duration}s`);
|
||||
|
||||
} catch (error) {
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
this.log(`💥 Build failed after ${duration}s`, 'error');
|
||||
|
||||
if (error instanceof Error) {
|
||||
this.log(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const options: BuildOptions = {
|
||||
runMigrations: args.includes('--migrations'),
|
||||
runTests: args.includes('--test'),
|
||||
skipLinting: args.includes('--skip-lint'),
|
||||
production: args.includes('--production')
|
||||
};
|
||||
|
||||
// Create and run build
|
||||
const buildManager = new BuildManager(options);
|
||||
buildManager.build().catch(error => {
|
||||
console.error('Unhandled build error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Copy Assets Script for SerpentRace Backend
|
||||
* Copies non-TypeScript files to the dist directory
|
||||
*/
|
||||
|
||||
const srcDir = path.join(__dirname, '..', 'src');
|
||||
const distDir = path.join(__dirname, '..', 'dist');
|
||||
|
||||
// File extensions to copy
|
||||
const assetExtensions = ['.json', '.html', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'];
|
||||
|
||||
// Directories to exclude from copying
|
||||
const excludeDirs = ['node_modules', '.git', 'tests', '__tests__'];
|
||||
|
||||
function copyAssets(srcPath, distPath) {
|
||||
if (!fs.existsSync(srcPath)) {
|
||||
console.log(`Source directory ${srcPath} does not exist`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(distPath)) {
|
||||
fs.mkdirSync(distPath, { recursive: true });
|
||||
}
|
||||
|
||||
const items = fs.readdirSync(srcPath);
|
||||
|
||||
items.forEach(item => {
|
||||
const srcItemPath = path.join(srcPath, item);
|
||||
const distItemPath = path.join(distPath, item);
|
||||
const stat = fs.statSync(srcItemPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// Skip excluded directories
|
||||
if (excludeDirs.includes(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recursively copy subdirectories
|
||||
copyAssets(srcItemPath, distItemPath);
|
||||
} else {
|
||||
const ext = path.extname(item).toLowerCase();
|
||||
|
||||
// Copy asset files
|
||||
if (assetExtensions.includes(ext)) {
|
||||
console.log(`Copying asset: ${srcItemPath} -> ${distItemPath}`);
|
||||
fs.copyFileSync(srcItemPath, distItemPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Copying assets from src to dist...');
|
||||
copyAssets(srcDir, distDir);
|
||||
console.log('Asset copying completed successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error copying assets:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
@echo off
|
||||
REM SerpentRace Backend Production Deployment Script for Windows
|
||||
REM This script handles the complete deployment process
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set "SCRIPT_START=%TIME%"
|
||||
|
||||
REM Colors simulation for Windows (using echo with different prefixes)
|
||||
set "LOG_PREFIX=[INFO]"
|
||||
set "ERROR_PREFIX=[ERROR]"
|
||||
set "WARN_PREFIX=[WARN]"
|
||||
|
||||
:log
|
||||
echo %LOG_PREFIX% [%DATE% %TIME%] %~1
|
||||
goto :eof
|
||||
|
||||
:error
|
||||
echo %ERROR_PREFIX% [%DATE% %TIME%] %~1
|
||||
goto :eof
|
||||
|
||||
:warn
|
||||
echo %WARN_PREFIX% [%DATE% %TIME%] %~1
|
||||
goto :eof
|
||||
|
||||
:check_env
|
||||
call :log "Checking environment variables..."
|
||||
|
||||
set "required_vars=DB_HOST DB_PORT DB_USERNAME DB_PASSWORD DB_NAME JWT_SECRET REDIS_HOST REDIS_PORT"
|
||||
set "missing_vars="
|
||||
|
||||
for %%v in (%required_vars%) do (
|
||||
call set "var_value=%%!%%v!%%"
|
||||
if "!var_value!"=="" (
|
||||
set "missing_vars=!missing_vars! %%v"
|
||||
)
|
||||
)
|
||||
|
||||
if not "!missing_vars!"==" " (
|
||||
call :error "Missing required environment variables:!missing_vars!"
|
||||
call :error "Please set these variables before running the deployment"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
call :log "All required environment variables are set"
|
||||
goto :eof
|
||||
|
||||
:install_dependencies
|
||||
call :log "Installing production dependencies..."
|
||||
npm ci --only=production
|
||||
if !errorlevel! neq 0 (
|
||||
call :error "Failed to install dependencies"
|
||||
exit /b 1
|
||||
)
|
||||
call :log "Dependencies installed successfully"
|
||||
goto :eof
|
||||
|
||||
:run_build
|
||||
call :log "Running production build..."
|
||||
npm run build:production
|
||||
if !errorlevel! neq 0 (
|
||||
call :error "Build failed"
|
||||
exit /b 1
|
||||
)
|
||||
call :log "Build completed successfully"
|
||||
goto :eof
|
||||
|
||||
:test_database
|
||||
call :log "Testing database connectivity..."
|
||||
|
||||
echo import { AppDataSource } from './src/Infrastructure/ormconfig'; > test-db-temp.ts
|
||||
echo. >> test-db-temp.ts
|
||||
echo async function testConnection() { >> test-db-temp.ts
|
||||
echo try { >> test-db-temp.ts
|
||||
echo await AppDataSource.initialize(); >> test-db-temp.ts
|
||||
echo console.log('✅ Database connection successful'^); >> test-db-temp.ts
|
||||
echo await AppDataSource.destroy(); >> test-db-temp.ts
|
||||
echo process.exit(0^); >> test-db-temp.ts
|
||||
echo } catch (error^) { >> test-db-temp.ts
|
||||
echo console.error('❌ Database connection failed:', error^); >> test-db-temp.ts
|
||||
echo process.exit(1^); >> test-db-temp.ts
|
||||
echo } >> test-db-temp.ts
|
||||
echo } >> test-db-temp.ts
|
||||
echo. >> test-db-temp.ts
|
||||
echo testConnection(); >> test-db-temp.ts
|
||||
|
||||
npx ts-node test-db-temp.ts
|
||||
set "db_test_result=!errorlevel!"
|
||||
del test-db-temp.ts 2>nul
|
||||
|
||||
if !db_test_result! neq 0 (
|
||||
call :error "Database connectivity test failed"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
call :log "Database connectivity test passed"
|
||||
goto :eof
|
||||
|
||||
:test_redis
|
||||
call :log "Testing Redis connectivity..."
|
||||
|
||||
echo import { createClient } from 'redis'; > test-redis-temp.ts
|
||||
echo. >> test-redis-temp.ts
|
||||
echo async function testRedis() { >> test-redis-temp.ts
|
||||
echo const client = createClient({ >> test-redis-temp.ts
|
||||
echo socket: { >> test-redis-temp.ts
|
||||
echo host: process.env.REDIS_HOST ^|^| 'localhost', >> test-redis-temp.ts
|
||||
echo port: parseInt(process.env.REDIS_PORT ^|^| '6379'^) >> test-redis-temp.ts
|
||||
echo } >> test-redis-temp.ts
|
||||
echo }^); >> test-redis-temp.ts
|
||||
echo. >> test-redis-temp.ts
|
||||
echo try { >> test-redis-temp.ts
|
||||
echo await client.connect(); >> test-redis-temp.ts
|
||||
echo await client.ping(); >> test-redis-temp.ts
|
||||
echo console.log('✅ Redis connection successful'^); >> test-redis-temp.ts
|
||||
echo await client.disconnect(); >> test-redis-temp.ts
|
||||
echo process.exit(0^); >> test-redis-temp.ts
|
||||
echo } catch (error^) { >> test-redis-temp.ts
|
||||
echo console.error('❌ Redis connection failed:', error^); >> test-redis-temp.ts
|
||||
echo process.exit(1^); >> test-redis-temp.ts
|
||||
echo } >> test-redis-temp.ts
|
||||
echo } >> test-redis-temp.ts
|
||||
echo. >> test-redis-temp.ts
|
||||
echo testRedis(); >> test-redis-temp.ts
|
||||
|
||||
npx ts-node test-redis-temp.ts
|
||||
set "redis_test_result=!errorlevel!"
|
||||
del test-redis-temp.ts 2>nul
|
||||
|
||||
if !redis_test_result! neq 0 (
|
||||
call :warn "Redis connectivity test failed - continuing anyway"
|
||||
) else (
|
||||
call :log "Redis connectivity test passed"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
:setup_directories
|
||||
call :log "Setting up required directories..."
|
||||
if not exist "logs" mkdir logs
|
||||
if not exist "uploads" mkdir uploads
|
||||
call :log "Directories created"
|
||||
goto :eof
|
||||
|
||||
:start_app
|
||||
call :log "Starting application for validation..."
|
||||
|
||||
REM Start the app in background
|
||||
start /B "" npm start
|
||||
|
||||
REM Wait for app to start
|
||||
timeout /t 10 /nobreak >nul
|
||||
|
||||
REM Test if the health endpoint responds (using curl if available)
|
||||
set "PORT_VAR=!PORT!"
|
||||
if "!PORT_VAR!"=="" set "PORT_VAR=3000"
|
||||
|
||||
curl -f http://localhost:!PORT_VAR!/health >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
call :log "Application health check passed"
|
||||
REM Try to stop the background process (this is tricky in batch)
|
||||
taskkill /F /IM node.exe /FI "WINDOWTITLE eq npm start*" >nul 2>&1
|
||||
) else (
|
||||
call :error "Application health check failed"
|
||||
taskkill /F /IM node.exe /FI "WINDOWTITLE eq npm start*" >nul 2>&1
|
||||
exit /b 1
|
||||
)
|
||||
goto :eof
|
||||
|
||||
:deploy
|
||||
call :log "🚀 Starting SerpentRace Backend production deployment..."
|
||||
|
||||
call :check_env
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
|
||||
call :install_dependencies
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
|
||||
call :run_build
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
|
||||
call :setup_directories
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
|
||||
call :test_database
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
|
||||
call :test_redis
|
||||
REM Redis test failure is not fatal
|
||||
|
||||
if not "%SKIP_APP_TEST%"=="true" (
|
||||
call :start_app
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
) else (
|
||||
call :warn "Skipping application startup test"
|
||||
)
|
||||
|
||||
call :log "🎉 Deployment completed successfully!"
|
||||
call :log "You can now start the application with: npm start"
|
||||
goto :eof
|
||||
|
||||
:build_only
|
||||
call :log "Running build-only deployment..."
|
||||
call :check_env
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
call :install_dependencies
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
call :run_build
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
call :setup_directories
|
||||
call :log "Build-only deployment completed"
|
||||
goto :eof
|
||||
|
||||
:test_connections
|
||||
call :log "Testing connections only..."
|
||||
call :check_env
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
call :test_database
|
||||
if !errorlevel! neq 0 exit /b 1
|
||||
call :test_redis
|
||||
call :log "Connection tests completed"
|
||||
goto :eof
|
||||
|
||||
REM Main script logic
|
||||
if "%1"=="" goto deploy
|
||||
if "%1"=="deploy" goto deploy
|
||||
if "%1"=="build-only" goto build_only
|
||||
if "%1"=="test-connections" goto test_connections
|
||||
|
||||
echo Usage: %0 [deploy^|build-only^|test-connections]
|
||||
echo deploy - Full deployment (default)
|
||||
echo build-only - Only build, skip tests
|
||||
echo test-connections - Test database and Redis connections
|
||||
exit /b 1
|
||||
@@ -0,0 +1,237 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SerpentRace Backend Production Deployment Script
|
||||
# This script handles the complete deployment process
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
|
||||
}
|
||||
|
||||
# Check if required environment variables are set
|
||||
check_env() {
|
||||
log "Checking environment variables..."
|
||||
|
||||
required_vars=(
|
||||
"DB_HOST"
|
||||
"DB_PORT"
|
||||
"DB_USERNAME"
|
||||
"DB_PASSWORD"
|
||||
"DB_NAME"
|
||||
"JWT_SECRET"
|
||||
"REDIS_HOST"
|
||||
"REDIS_PORT"
|
||||
)
|
||||
|
||||
missing_vars=()
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_vars[@]} -ne 0 ]; then
|
||||
error "Missing required environment variables: ${missing_vars[*]}"
|
||||
error "Please set these variables before running the deployment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "All required environment variables are set"
|
||||
}
|
||||
|
||||
# Install dependencies
|
||||
install_dependencies() {
|
||||
log "Installing production dependencies..."
|
||||
npm ci --only=production
|
||||
log "Dependencies installed successfully"
|
||||
}
|
||||
|
||||
# Run the comprehensive build process
|
||||
run_build() {
|
||||
log "Running production build..."
|
||||
npm run build:production
|
||||
log "Build completed successfully"
|
||||
}
|
||||
|
||||
# Test database connectivity
|
||||
test_database() {
|
||||
log "Testing database connectivity..."
|
||||
|
||||
# Use a simple TypeScript script to test connection
|
||||
cat > /tmp/test-db.ts << 'EOF'
|
||||
import { AppDataSource } from './src/Infrastructure/ormconfig';
|
||||
|
||||
async function testConnection() {
|
||||
try {
|
||||
await AppDataSource.initialize();
|
||||
console.log('✅ Database connection successful');
|
||||
await AppDataSource.destroy();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Database connection failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
testConnection();
|
||||
EOF
|
||||
|
||||
npx ts-node /tmp/test-db.ts || {
|
||||
error "Database connectivity test failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
rm -f /tmp/test-db.ts
|
||||
log "Database connectivity test passed"
|
||||
}
|
||||
|
||||
# Test Redis connectivity
|
||||
test_redis() {
|
||||
log "Testing Redis connectivity..."
|
||||
|
||||
# Use a simple script to test Redis connection
|
||||
cat > /tmp/test-redis.ts << 'EOF'
|
||||
import { createClient } from 'redis';
|
||||
|
||||
async function testRedis() {
|
||||
const client = createClient({
|
||||
socket: {
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379')
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
await client.ping();
|
||||
console.log('✅ Redis connection successful');
|
||||
await client.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Redis connection failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
testRedis();
|
||||
EOF
|
||||
|
||||
npx ts-node /tmp/test-redis.ts || {
|
||||
warn "Redis connectivity test failed - continuing anyway"
|
||||
}
|
||||
|
||||
rm -f /tmp/test-redis.ts
|
||||
log "Redis connectivity test completed"
|
||||
}
|
||||
|
||||
# Create required directories
|
||||
setup_directories() {
|
||||
log "Setting up required directories..."
|
||||
mkdir -p logs
|
||||
mkdir -p uploads
|
||||
log "Directories created"
|
||||
}
|
||||
|
||||
# Start the application (for testing)
|
||||
start_app() {
|
||||
log "Starting application for validation..."
|
||||
|
||||
# Start the app in background and test if it responds
|
||||
npm start &
|
||||
APP_PID=$!
|
||||
|
||||
# Wait for app to start
|
||||
sleep 10
|
||||
|
||||
# Test if the health endpoint responds
|
||||
if curl -f http://localhost:${PORT:-3000}/health > /dev/null 2>&1; then
|
||||
log "Application health check passed"
|
||||
kill $APP_PID
|
||||
wait $APP_PID 2>/dev/null
|
||||
else
|
||||
error "Application health check failed"
|
||||
kill $APP_PID 2>/dev/null || true
|
||||
wait $APP_PID 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main deployment function
|
||||
deploy() {
|
||||
log "🚀 Starting SerpentRace Backend production deployment..."
|
||||
|
||||
# Check environment
|
||||
check_env
|
||||
|
||||
# Install dependencies
|
||||
install_dependencies
|
||||
|
||||
# Run build process
|
||||
run_build
|
||||
|
||||
# Setup directories
|
||||
setup_directories
|
||||
|
||||
# Test connections
|
||||
test_database
|
||||
test_redis
|
||||
|
||||
# Test application startup
|
||||
if [ "${SKIP_APP_TEST}" != "true" ]; then
|
||||
start_app
|
||||
else
|
||||
warn "Skipping application startup test"
|
||||
fi
|
||||
|
||||
log "🎉 Deployment completed successfully!"
|
||||
info "You can now start the application with: npm start"
|
||||
}
|
||||
|
||||
# Handle script arguments
|
||||
case "${1:-deploy}" in
|
||||
"deploy")
|
||||
deploy
|
||||
;;
|
||||
"build-only")
|
||||
log "Running build-only deployment..."
|
||||
check_env
|
||||
install_dependencies
|
||||
run_build
|
||||
setup_directories
|
||||
log "Build-only deployment completed"
|
||||
;;
|
||||
"test-connections")
|
||||
log "Testing connections only..."
|
||||
check_env
|
||||
test_database
|
||||
test_redis
|
||||
log "Connection tests completed"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [deploy|build-only|test-connections]"
|
||||
echo " deploy - Full deployment (default)"
|
||||
echo " build-only - Only build, skip tests"
|
||||
echo " test-connections - Test database and Redis connections"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,28 @@
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const migrationName = process.argv[2];
|
||||
|
||||
if (!migrationName) {
|
||||
console.error('Please provide a migration name: npm run migration:full <migration_name>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Creating migration: ${migrationName}`);
|
||||
execSync(`npx ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli migration:create ./src/Infrastructure/Migrationsettings/${migrationName}`, { stdio: 'inherit' });
|
||||
|
||||
console.log(`Generating migration: ${migrationName}`);
|
||||
execSync(`npx ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli -d ./src/Infrastructure/ormconfig.ts migration:generate ./src/Infrastructure/Migrations/${migrationName}`, { stdio: 'inherit' });
|
||||
|
||||
console.log('Migration generated successfully!');
|
||||
|
||||
console.log('Running migration...');
|
||||
execSync(`npx ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli -d ./src/Infrastructure/ormconfig.ts migration:run`, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Migration failed:', error.message);
|
||||
} else {
|
||||
console.error('Migration failed:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# PowerShell script to start Redis and run tests
|
||||
Write-Host "Starting Redis with Docker Compose..." -ForegroundColor Green
|
||||
docker-compose up -d redis
|
||||
|
||||
# Wait for Redis to be ready
|
||||
Write-Host "Waiting for Redis to be ready..." -ForegroundColor Yellow
|
||||
do {
|
||||
Write-Host "Checking Redis connection..." -ForegroundColor Gray
|
||||
$result = docker-compose exec redis redis-cli ping 2>$null
|
||||
if ($result -ne "PONG") {
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
} while ($result -ne "PONG")
|
||||
|
||||
Write-Host "Redis is ready!" -ForegroundColor Green
|
||||
|
||||
# Run Redis tests
|
||||
Write-Host "Running Redis tests..." -ForegroundColor Cyan
|
||||
npm test -- --testNamePattern="RedisService"
|
||||
|
||||
Write-Host "Done!" -ForegroundColor Green
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to start Redis and run tests
|
||||
echo "Starting Redis with Docker Compose..."
|
||||
docker-compose up -d redis
|
||||
|
||||
# Wait for Redis to be ready
|
||||
echo "Waiting for Redis to be ready..."
|
||||
until docker-compose exec redis redis-cli ping; do
|
||||
echo "Waiting for Redis..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Redis is ready!"
|
||||
|
||||
# Run Redis tests
|
||||
echo "Running Redis tests..."
|
||||
npm test -- --testNamePattern="RedisService"
|
||||
|
||||
echo "Done!"
|
||||
Reference in New Issue
Block a user