Modernize collaborative pixel art platform to production-ready architecture

Major refactor from simple HTML/JS app to modern full-stack TypeScript application:

## Architecture Changes
- Migrated to monorepo structure with workspaces (backend, frontend, shared)
- Backend: Node.js + Express + TypeScript + Socket.IO
- Frontend: Next.js 15.5 + React 19 + TypeScript + Tailwind CSS
- Shared: Common types and utilities across packages

## Key Features Implemented
- Real-time WebSocket collaboration via Socket.IO
- Virtual canvas with chunked loading for performance
- Modern UI with dark mode and responsive design
- Mock database system for easy development (Redis/PostgreSQL compatible)
- Comprehensive error handling and rate limiting
- User presence and cursor tracking
- Infinite canvas support with zoom/pan controls

## Performance Optimizations
- Canvas virtualization - only renders visible viewport
- Chunked pixel data loading (64x64 pixel chunks)
- Optimized WebSocket protocol
- Memory-efficient state management with Zustand

## Development Experience
- Full TypeScript support across all packages
- Hot reload for both frontend and backend
- Docker support for production deployment
- Comprehensive linting and formatting
- Automated development server startup

## Fixed Issues
- Corrected start script paths
- Updated environment configuration
- Fixed ESLint configuration issues
- Ensured all dependencies are properly installed
- Verified build process works correctly
This commit is contained in:
martin 2025-08-22 19:28:05 +02:00
commit 3ce5a97422
69 changed files with 17771 additions and 1589 deletions

75
scripts/setup.js Normal file
View file

@ -0,0 +1,75 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
console.log('🎨 Setting up GaPlace...\n');
// Check if required files exist
const requiredFiles = [
'backend/.env',
'frontend/.env.local'
];
console.log('📋 Checking environment files...');
requiredFiles.forEach(file => {
if (!fs.existsSync(file)) {
console.log(`❌ Missing: ${file}`);
process.exit(1);
} else {
console.log(`✅ Found: ${file}`);
}
});
// Build shared package
console.log('\n📦 Building shared package...');
try {
execSync('npm run build', { cwd: 'shared', stdio: 'inherit' });
console.log('✅ Shared package built successfully');
} catch (error) {
console.error('❌ Failed to build shared package');
process.exit(1);
}
// Install dependencies for all workspaces
console.log('\n📦 Installing dependencies...');
try {
execSync('npm install', { stdio: 'inherit' });
console.log('✅ Dependencies installed successfully');
} catch (error) {
console.error('❌ Failed to install dependencies');
process.exit(1);
}
// Build backend
console.log('\n🔧 Building backend...');
try {
execSync('npm run build', { cwd: 'backend', stdio: 'inherit' });
console.log('✅ Backend built successfully');
} catch (error) {
console.error('❌ Failed to build backend');
process.exit(1);
}
// Build frontend
console.log('\n🎨 Building frontend...');
try {
execSync('npm run build', { cwd: 'frontend', stdio: 'inherit' });
console.log('✅ Frontend built successfully');
} catch (error) {
console.error('❌ Failed to build frontend');
process.exit(1);
}
console.log('\n🚀 Setup complete! To start development:');
console.log('');
console.log('1. Start databases:');
console.log(' docker-compose up redis postgres -d');
console.log('');
console.log('2. Start development servers:');
console.log(' npm run dev');
console.log('');
console.log('3. Open http://localhost:3000 in your browser');
console.log('');
console.log('🎨 Welcome to GaPlace! ✨');

128
scripts/start-dev.js Normal file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env node
const { spawn, exec } = require('child_process');
const path = require('path');
const util = require('util');
const execPromise = util.promisify(exec);
console.log('🎨 Starting GaPlace Development Environment...\n');
// Function to check if port is in use
async function isPortInUse(port) {
try {
const { stdout } = await execPromise(`netstat -ano | findstr :${port}`);
return stdout.trim().length > 0;
} catch (error) {
return false;
}
}
// Function to kill process on port
async function killPort(port) {
try {
await execPromise(`npx kill-port ${port}`);
console.log(`✅ Cleared port ${port}`);
} catch (error) {
console.log(` Port ${port} was already free`);
}
}
// Function to start a process and handle output
function startProcess(name, command, args, cwd, color) {
console.log(`${color}[${name}]${'\x1b[0m'} Starting: ${command} ${args.join(' ')}`);
const isWindows = process.platform === 'win32';
const proc = spawn(command, args, {
cwd,
stdio: 'pipe',
shell: true,
windowsHide: true
});
proc.stdout.on('data', (data) => {
const lines = data.toString().split('\n').filter(line => line.trim());
lines.forEach(line => {
console.log(`${color}[${name}]${'\x1b[0m'} ${line}`);
});
});
proc.stderr.on('data', (data) => {
const lines = data.toString().split('\n').filter(line => line.trim());
lines.forEach(line => {
console.log(`${color}[${name}]${'\x1b[0m'} ${line}`);
});
});
proc.on('close', (code) => {
if (code !== 0) {
console.log(`${color}[${name}]${'\x1b[0m'} ❌ Process exited with code ${code}`);
}
});
proc.on('error', (error) => {
console.log(`${color}[${name}]${'\x1b[0m'} ❌ Error: ${error.message}`);
});
return proc;
}
async function startDevelopment() {
try {
// Clear ports first
console.log('🧹 Clearing ports...');
await killPort(3001);
await killPort(3000);
// Wait a moment for ports to be fully cleared
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('🔧 Starting backend server...');
const backend = startProcess(
'Backend',
'npm',
['run', 'dev'],
path.join(__dirname, '..', 'backend'),
'\x1b[34m' // Blue
);
// Wait for backend to start
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('🎨 Starting frontend server...');
const frontend = startProcess(
'Frontend',
'npm',
['run', 'dev'],
path.join(__dirname, '..', 'frontend'),
'\x1b[32m' // Green
);
console.log('\n📱 Frontend: http://localhost:3000');
console.log('🔌 Backend: http://localhost:3001');
console.log('🩺 Health Check: http://localhost:3001/health');
console.log('💡 Press Ctrl+C to stop all servers\n');
// Handle Ctrl+C
process.on('SIGINT', async () => {
console.log('\n🛑 Shutting down development servers...');
backend.kill('SIGTERM');
frontend.kill('SIGTERM');
// Wait a moment then force kill if needed
setTimeout(() => {
backend.kill('SIGKILL');
frontend.kill('SIGKILL');
process.exit(0);
}, 2000);
});
// Keep the script running
setInterval(() => {}, 1000);
} catch (error) {
console.error('❌ Failed to start development environment:', error.message);
process.exit(1);
}
}
startDevelopment();