fail safe
This commit is contained in:
@@ -1,297 +0,0 @@
|
||||
# SerpentRace Backend Build System
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the comprehensive build system for the SerpentRace backend application. The build system handles TypeScript compilation, database migrations, asset management, testing, and deployment.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Development build
|
||||
npm run build
|
||||
|
||||
# Production build with full validation
|
||||
npm run build:production
|
||||
|
||||
# Advanced build with migrations and tests
|
||||
npm run build:advanced:prod
|
||||
|
||||
# Development server with hot reload
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Build Scripts
|
||||
|
||||
### Basic Build Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `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
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `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) |
|
||||
|
||||
### Development Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run dev` | Start development server with hot reload |
|
||||
| `npm run watch` | Watch mode TypeScript compilation |
|
||||
| `npm run typecheck` | Type checking without code generation |
|
||||
|
||||
### Database Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `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
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `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 Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run deploy:prod` | Build for production deployment |
|
||||
| `scripts/deploy.sh` | Full Linux/Mac deployment script |
|
||||
| `scripts/deploy.bat` | Full Windows deployment script |
|
||||
|
||||
## Advanced Build Script
|
||||
|
||||
The advanced build script (`scripts/build.ts`) supports various options:
|
||||
|
||||
```bash
|
||||
# Basic advanced build
|
||||
npm run build:advanced
|
||||
|
||||
# Production build with migrations and tests
|
||||
npm run build:advanced:prod
|
||||
|
||||
# CI/CD build (skips linting, includes tests and migrations)
|
||||
npm run build:advanced:ci
|
||||
```
|
||||
|
||||
### Build Options
|
||||
|
||||
- `--migrations`: Run database migrations during build
|
||||
- `--test`: Run tests during build
|
||||
- `--skip-lint`: Skip linting step
|
||||
- `--production`: Enable production mode (strict validation)
|
||||
|
||||
## Deployment Scripts
|
||||
|
||||
### Linux/Mac Deployment
|
||||
|
||||
```bash
|
||||
./scripts/deploy.sh [deploy|build-only|test-connections]
|
||||
```
|
||||
|
||||
Options:
|
||||
- `deploy` (default): Full deployment with validation
|
||||
- `build-only`: Build without connection testing
|
||||
- `test-connections`: Test database and Redis connections only
|
||||
|
||||
### Windows Deployment
|
||||
|
||||
```cmd
|
||||
scripts\deploy.bat [deploy|build-only|test-connections]
|
||||
```
|
||||
|
||||
Same options as Linux/Mac version.
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
The deployment scripts require these environment variables:
|
||||
|
||||
```bash
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=your_password
|
||||
DB_NAME=serpentrace
|
||||
JWT_SECRET=your_jwt_secret
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
## Build Process Flow
|
||||
|
||||
### Standard Build (`npm run build`)
|
||||
|
||||
1. **Clean** - Remove previous build artifacts
|
||||
2. **Lint** - Code quality checks (if configured)
|
||||
3. **Compile** - TypeScript compilation
|
||||
4. **Copy Assets** - Copy non-TS files to dist
|
||||
5. **Post-build** - Validation and cleanup
|
||||
|
||||
### Production Build (`npm run build:production`)
|
||||
|
||||
1. **Clean** - Remove previous build artifacts
|
||||
2. **Lint** - Code quality checks
|
||||
3. **Test** - Run test suite
|
||||
4. **Migrations** - Apply database migrations
|
||||
5. **Compile** - TypeScript compilation
|
||||
6. **Copy Assets** - Copy non-TS files to dist
|
||||
7. **Validate** - Ensure build integrity
|
||||
|
||||
### Advanced Build (`npm run build:advanced`)
|
||||
|
||||
Provides fine-grained control over the build process with comprehensive logging and error handling.
|
||||
|
||||
## Asset Management
|
||||
|
||||
The build system automatically copies these file types to the dist directory:
|
||||
|
||||
- `.json` files (configuration, data)
|
||||
- `.html` files (templates)
|
||||
- `.css` files (stylesheets)
|
||||
- Image files (`.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.ico`)
|
||||
- Font files (`.woff`, `.woff2`, `.ttf`, `.eot`)
|
||||
|
||||
Excluded directories:
|
||||
- `node_modules`
|
||||
- `.git`
|
||||
- `tests`
|
||||
- `__tests__`
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
The build system uses the following TypeScript settings:
|
||||
|
||||
- **Target**: ES2020
|
||||
- **Module**: CommonJS
|
||||
- **Output Directory**: `./dist`
|
||||
- **Source Maps**: Enabled
|
||||
- **Declarations**: Enabled for type definitions
|
||||
- **Strict Mode**: Enabled for type safety
|
||||
|
||||
## Migration Management
|
||||
|
||||
### Creating Migrations
|
||||
|
||||
```bash
|
||||
# Create empty migration
|
||||
npm run migration:create AddNewTable
|
||||
|
||||
# Generate migration from entity changes
|
||||
npm run migration:generate AddNewTable
|
||||
|
||||
# Full migration workflow (create + generate + run)
|
||||
npm run migration:full AddNewTable
|
||||
```
|
||||
|
||||
### Migration Best Practices
|
||||
|
||||
1. Always backup database before running migrations in production
|
||||
2. Test migrations in development environment first
|
||||
3. Use descriptive migration names
|
||||
4. Review generated migrations before running them
|
||||
|
||||
## Docker Integration
|
||||
|
||||
The build system is optimized for Docker deployments:
|
||||
|
||||
```dockerfile
|
||||
# Use build:docker for container builds
|
||||
RUN npm run build:docker
|
||||
|
||||
# Or use production build for full validation
|
||||
RUN npm run build:production
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Build fails with "Cannot find module"**
|
||||
- Run `npm ci` to ensure all dependencies are installed
|
||||
- Check TypeScript paths configuration
|
||||
|
||||
2. **Migration errors during build**
|
||||
- Verify database connection parameters
|
||||
- Ensure database exists and is accessible
|
||||
- Check migration files for syntax errors
|
||||
|
||||
3. **Asset copying fails**
|
||||
- Verify file permissions
|
||||
- Check disk space availability
|
||||
- Ensure source files exist
|
||||
|
||||
4. **TypeScript compilation errors**
|
||||
- Run `npm run typecheck` for detailed error messages
|
||||
- Check tsconfig.json configuration
|
||||
- Verify all type definitions are installed
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose logging by setting the environment variable:
|
||||
|
||||
```bash
|
||||
export DEBUG=serpentrace:*
|
||||
npm run build:advanced
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Build Performance Tips
|
||||
|
||||
1. Use `npm ci` instead of `npm install` in CI/CD
|
||||
2. Enable TypeScript incremental compilation for development
|
||||
3. Use `--skip-lint` in CI if linting is handled separately
|
||||
4. Cache node_modules in CI/CD pipelines
|
||||
|
||||
### Runtime Performance
|
||||
|
||||
The build system optimizes the output for production:
|
||||
|
||||
- Source maps for debugging (can be disabled in production)
|
||||
- Type declarations for library usage
|
||||
- Compressed and optimized JavaScript output
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
Build logs include:
|
||||
- Timestamps for each build step
|
||||
- Error details with stack traces
|
||||
- Performance metrics (build duration)
|
||||
- Validation results
|
||||
|
||||
Production builds create detailed logs in the `logs/` directory.
|
||||
|
||||
## Contributing
|
||||
|
||||
When modifying the build system:
|
||||
|
||||
1. Test changes with both development and production builds
|
||||
2. Update this documentation for any new scripts or options
|
||||
3. Ensure backward compatibility
|
||||
4. Add appropriate error handling and logging
|
||||
|
||||
## Support
|
||||
|
||||
For build system issues:
|
||||
1. Check this documentation
|
||||
2. Review error logs in the console
|
||||
3. Verify environment variables are set correctly
|
||||
4. Test with a clean `node_modules` installation
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,392 +0,0 @@
|
||||
# 🗄️ SerpentRace Database Management Guide
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This guide provides comprehensive information about managing all database services in the SerpentRace project, including PostgreSQL, Redis, MinIO, and administration tools.
|
||||
|
||||
## 📊 Quick Status Check
|
||||
|
||||
### Check All Services
|
||||
```bash
|
||||
npm run db:status
|
||||
```
|
||||
|
||||
### Check Individual Services
|
||||
```bash
|
||||
npm run db:status:pg # PostgreSQL only
|
||||
npm run db:status:redis # Redis only
|
||||
npm run db:status:docker # Docker containers only
|
||||
```
|
||||
|
||||
### Simple Connection Test
|
||||
```bash
|
||||
npm run test:connections
|
||||
```
|
||||
|
||||
## 🐘 PostgreSQL Database
|
||||
|
||||
### Connection Details
|
||||
- **Host**: localhost:5432
|
||||
- **Database**: serpentrace
|
||||
- **Username**: postgres
|
||||
- **Password**: postgres
|
||||
- **Admin Tool**: pgAdmin at http://localhost:8080
|
||||
|
||||
### Database Operations
|
||||
|
||||
#### Run Migrations
|
||||
```bash
|
||||
npm run migration:run
|
||||
```
|
||||
|
||||
#### Create New Migration
|
||||
```bash
|
||||
npm run migration:create src/migrations/YourMigrationName
|
||||
```
|
||||
|
||||
#### Generate Migration from Entity Changes
|
||||
```bash
|
||||
npm run migration:generate src/migrations/YourMigrationName
|
||||
```
|
||||
|
||||
#### Check Migration Status
|
||||
```bash
|
||||
npm run migration:show
|
||||
```
|
||||
|
||||
#### Rollback Last Migration
|
||||
```bash
|
||||
npm run migration:revert
|
||||
```
|
||||
|
||||
### Direct Database Access
|
||||
|
||||
#### Using psql (if installed)
|
||||
```bash
|
||||
psql -h localhost -p 5432 -U postgres -d serpentrace
|
||||
```
|
||||
|
||||
#### Using pgAdmin
|
||||
1. Open http://localhost:8080
|
||||
2. Login with: admin@serpentrace.dev / admin
|
||||
3. Server should be pre-configured as "SerpentRace"
|
||||
|
||||
### Common SQL Queries
|
||||
|
||||
#### Check Database Size
|
||||
```sql
|
||||
SELECT pg_size_pretty(pg_database_size('serpentrace')) as size;
|
||||
```
|
||||
|
||||
#### List All Tables
|
||||
```sql
|
||||
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
|
||||
```
|
||||
|
||||
#### Check Active Connections
|
||||
```sql
|
||||
SELECT count(*) FROM pg_stat_activity WHERE datname = 'serpentrace';
|
||||
```
|
||||
|
||||
## 🔴 Redis Cache
|
||||
|
||||
### Connection Details
|
||||
- **Host**: localhost:6379
|
||||
- **No Authentication**: Default Redis setup
|
||||
- **Admin Tool**: Redis Commander at http://localhost:8081
|
||||
|
||||
### Redis Operations
|
||||
|
||||
#### Direct Redis Access (if redis-cli installed)
|
||||
```bash
|
||||
redis-cli -h localhost -p 6379
|
||||
```
|
||||
|
||||
#### Common Redis Commands
|
||||
```bash
|
||||
# Get all keys
|
||||
KEYS *
|
||||
|
||||
# Get key count
|
||||
DBSIZE
|
||||
|
||||
# Check memory usage
|
||||
INFO memory
|
||||
|
||||
# Flush all data (careful!)
|
||||
FLUSHALL
|
||||
```
|
||||
|
||||
### Using Redis Commander
|
||||
1. Open http://localhost:8081
|
||||
2. Browse keys, view data, execute commands
|
||||
|
||||
## 🗄️ MinIO Object Storage
|
||||
|
||||
### Connection Details
|
||||
- **Endpoint**: localhost:9000
|
||||
- **Console**: http://localhost:9001
|
||||
- **Access Key**: serpentrace
|
||||
- **Secret Key**: serpentrace123
|
||||
- **Default Bucket**: serpentrace
|
||||
|
||||
### MinIO Operations
|
||||
|
||||
#### Access MinIO Console
|
||||
1. Open http://localhost:9001
|
||||
2. Login with: serpentrace / serpentrace123
|
||||
3. Create buckets, upload files, manage storage
|
||||
|
||||
#### Health Check
|
||||
```bash
|
||||
curl http://localhost:9000/minio/health/live
|
||||
```
|
||||
|
||||
### File Upload Example (Node.js)
|
||||
```javascript
|
||||
const Minio = require('minio');
|
||||
|
||||
const minioClient = new Minio.Client({
|
||||
endPoint: 'localhost',
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
accessKey: 'serpentrace',
|
||||
secretKey: 'serpentrace123'
|
||||
});
|
||||
|
||||
// Upload file
|
||||
minioClient.fPutObject('serpentrace', 'test-file.txt', './file.txt');
|
||||
```
|
||||
|
||||
## 🐳 Docker Container Management
|
||||
|
||||
### View All Containers
|
||||
```bash
|
||||
docker ps -a
|
||||
```
|
||||
|
||||
### View SerpentRace Containers Only
|
||||
```bash
|
||||
docker ps -a --filter "name=serpentrace"
|
||||
```
|
||||
|
||||
### Container Operations
|
||||
|
||||
#### Restart All Services
|
||||
```bash
|
||||
cd d:\munka\SzeSnake\SerpentRace_Docker
|
||||
docker-compose -f docker-compose.dev.yml restart
|
||||
```
|
||||
|
||||
#### Restart Individual Service
|
||||
```bash
|
||||
docker restart serpentrace-postgres-dev # PostgreSQL
|
||||
docker restart serpentrace-redis-dev # Redis
|
||||
docker restart serpentrace-minio-dev # MinIO
|
||||
docker restart serpentrace-pgadmin-dev # pgAdmin
|
||||
```
|
||||
|
||||
#### View Container Logs
|
||||
```bash
|
||||
docker logs serpentrace-postgres-dev
|
||||
docker logs serpentrace-redis-dev -f # Follow logs
|
||||
```
|
||||
|
||||
#### Stop All Services
|
||||
```bash
|
||||
cd d:\munka\SzeSnake\SerpentRace_Docker
|
||||
docker-compose -f docker-compose.dev.yml down
|
||||
```
|
||||
|
||||
#### Start All Services
|
||||
```bash
|
||||
cd d:\munka\SzeSnake\SerpentRace_Docker
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
|
||||
### PostgreSQL Issues
|
||||
|
||||
#### Connection Refused
|
||||
```bash
|
||||
# Check if container is running
|
||||
docker ps | grep postgres
|
||||
|
||||
# Check container logs
|
||||
docker logs serpentrace-postgres-dev
|
||||
|
||||
# Restart if needed
|
||||
docker restart serpentrace-postgres-dev
|
||||
```
|
||||
|
||||
#### Migration Errors
|
||||
```bash
|
||||
# Check migration status
|
||||
npm run migration:show
|
||||
|
||||
# Revert last migration if problematic
|
||||
npm run migration:revert
|
||||
|
||||
# Re-run migrations
|
||||
npm run migration:run
|
||||
```
|
||||
|
||||
### Redis Issues
|
||||
|
||||
#### Cannot Connect
|
||||
```bash
|
||||
# Check Redis container
|
||||
docker ps | grep redis
|
||||
|
||||
# Test connection
|
||||
redis-cli -h localhost -p 6379 ping
|
||||
# Expected response: PONG
|
||||
```
|
||||
|
||||
### MinIO Issues
|
||||
|
||||
#### Health Check Failed
|
||||
```bash
|
||||
# Check MinIO container
|
||||
docker ps | grep minio
|
||||
|
||||
# Test health endpoint
|
||||
curl http://localhost:9000/minio/health/live
|
||||
# Expected response: 200 OK
|
||||
```
|
||||
|
||||
### pgAdmin Issues
|
||||
|
||||
#### Cannot Login
|
||||
- Default credentials: admin@serpentrace.dev / admin
|
||||
- If issues persist, restart container:
|
||||
```bash
|
||||
docker restart serpentrace-pgladmin-dev
|
||||
```
|
||||
|
||||
#### Server Not Found
|
||||
- pgAdmin should auto-configure the PostgreSQL server
|
||||
- If not visible, add manually:
|
||||
- Host: postgres
|
||||
- Port: 5432
|
||||
- Database: serpentrace
|
||||
- Username: postgres
|
||||
- Password: postgres
|
||||
|
||||
## 🔧 Environment Variables
|
||||
|
||||
### Default Development Settings
|
||||
```bash
|
||||
# PostgreSQL
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=serpentrace
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
|
||||
# MinIO
|
||||
MINIO_ENDPOINT=localhost
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=serpentrace
|
||||
MINIO_SECRET_KEY=serpentrace123
|
||||
```
|
||||
|
||||
### Production Configuration
|
||||
Create `.env.production` with secure values:
|
||||
```bash
|
||||
DB_HOST=your-production-host
|
||||
DB_PASSWORD=secure-password
|
||||
REDIS_PASSWORD=secure-redis-password
|
||||
MINIO_SECRET_KEY=secure-minio-secret
|
||||
```
|
||||
|
||||
## 📈 Monitoring & Maintenance
|
||||
|
||||
### Daily Health Check
|
||||
```bash
|
||||
npm run db:status
|
||||
```
|
||||
|
||||
### Weekly Maintenance
|
||||
```bash
|
||||
# Check database size growth
|
||||
npm run db:status:pg
|
||||
|
||||
# Review Redis memory usage
|
||||
npm run db:status:redis
|
||||
|
||||
# Clean up old Docker logs
|
||||
docker system prune
|
||||
```
|
||||
|
||||
### Backup Procedures
|
||||
|
||||
#### PostgreSQL Backup
|
||||
```bash
|
||||
docker exec serpentrace-postgres-dev pg_dump -U postgres serpentrace > backup.sql
|
||||
```
|
||||
|
||||
#### Redis Backup
|
||||
```bash
|
||||
docker exec serpentrace-redis-dev redis-cli BGSAVE
|
||||
```
|
||||
|
||||
#### MinIO Backup
|
||||
Use MinIO Console or mc client to backup buckets.
|
||||
|
||||
## 🎯 Performance Optimization
|
||||
|
||||
### PostgreSQL
|
||||
- Monitor active connections with `npm run db:status:pg`
|
||||
- Use connection pooling in production
|
||||
- Regular VACUUM and ANALYZE operations
|
||||
|
||||
### Redis
|
||||
- Monitor memory usage
|
||||
- Configure appropriate eviction policies
|
||||
- Use Redis persistence (RDB/AOF) in production
|
||||
|
||||
### MinIO
|
||||
- Configure appropriate bucket policies
|
||||
- Use lifecycle management for old files
|
||||
- Monitor storage usage through console
|
||||
|
||||
## 🚀 Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# Status and Health
|
||||
npm run db:status # Full system status
|
||||
npm run test:connections # Quick connection test
|
||||
|
||||
# Database Operations
|
||||
npm run migration:run # Apply migrations
|
||||
npm run migration:show # Check migration status
|
||||
|
||||
# Docker Management
|
||||
docker ps # Show running containers
|
||||
docker logs <container> # View logs
|
||||
docker restart <container> # Restart service
|
||||
|
||||
# Direct Access
|
||||
psql -h localhost -U postgres -d serpentrace # PostgreSQL CLI
|
||||
redis-cli -h localhost # Redis CLI
|
||||
```
|
||||
|
||||
## 🌐 Web Interfaces Summary
|
||||
|
||||
| Service | URL | Credentials |
|
||||
|---------|-----|------------|
|
||||
| pgAdmin | http://localhost:8080 | admin@serpentrace.dev / admin |
|
||||
| Redis Commander | http://localhost:8081 | No auth required |
|
||||
| MinIO Console | http://localhost:9001 | serpentrace / serpentrace123 |
|
||||
| Backend API | http://localhost:3000 | When running |
|
||||
| Frontend | http://localhost:5173 | When running |
|
||||
|
||||
---
|
||||
|
||||
*This guide is automatically updated when database configurations change. Last updated: 2025-08-23*
|
||||
@@ -1,235 +0,0 @@
|
||||
# Docker Watcher Implementation Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document explains the Docker watcher implementation for the SerpentRace project, which automatically synchronizes local file changes with Docker containers and rebuilds images when necessary.
|
||||
|
||||
## What's Implemented
|
||||
|
||||
### Docker Compose Watch Configuration
|
||||
|
||||
The development Docker Compose configuration now includes `develop.watch` sections for both frontend and backend services that provide:
|
||||
|
||||
1. **File Synchronization**: Automatically sync source code changes to running containers
|
||||
2. **Selective Rebuilding**: Rebuild containers when critical configuration files change
|
||||
3. **Intelligent Ignore Patterns**: Exclude unnecessary files like `node_modules`
|
||||
|
||||
### Backend Watcher Configuration
|
||||
|
||||
```yaml
|
||||
develop:
|
||||
watch:
|
||||
- action: sync
|
||||
path: ../SerpentRace_Backend/src
|
||||
target: /app/src
|
||||
ignore:
|
||||
- node_modules/
|
||||
- action: sync
|
||||
path: ../SerpentRace_Backend/package.json
|
||||
target: /app/package.json
|
||||
- action: rebuild
|
||||
path: ../SerpentRace_Backend/package-lock.json
|
||||
- action: rebuild
|
||||
path: ../SerpentRace_Docker/Dockerfile_backend.dev
|
||||
```
|
||||
|
||||
### Frontend Watcher Configuration
|
||||
|
||||
```yaml
|
||||
develop:
|
||||
watch:
|
||||
- action: sync
|
||||
path: ../SerpentRace_Frontend/src
|
||||
target: /app/src
|
||||
ignore:
|
||||
- node_modules/
|
||||
- action: sync
|
||||
path: ../SerpentRace_Frontend/public
|
||||
target: /app/public
|
||||
- action: sync
|
||||
path: ../SerpentRace_Frontend/package.json
|
||||
target: /app/package.json
|
||||
- action: rebuild
|
||||
path: ../SerpentRace_Frontend/package-lock.json
|
||||
- action: rebuild
|
||||
path: ../SerpentRace_Frontend/vite.config.js
|
||||
- action: rebuild
|
||||
path: ../SerpentRace_Docker/Dockerfile_frontend.dev
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Sync Actions
|
||||
- **Purpose**: Instantly copy changed files from host to container
|
||||
- **Use Cases**: Source code files, static assets, configuration files that don't require rebuild
|
||||
- **Performance**: Near-instant updates, no container restart needed
|
||||
|
||||
### Rebuild Actions
|
||||
- **Purpose**: Trigger full container rebuild when critical files change
|
||||
- **Use Cases**: Package files, Docker configuration, build configuration
|
||||
- **Performance**: Takes longer but ensures consistency
|
||||
|
||||
## Usage
|
||||
|
||||
### New Commands Added
|
||||
|
||||
#### Windows (docker-manage.bat)
|
||||
```bash
|
||||
# Start with file watchers
|
||||
.\docker-manage.bat dev:watch
|
||||
|
||||
# Traditional start (without watchers)
|
||||
.\docker-manage.bat dev:start
|
||||
```
|
||||
|
||||
#### Linux/Mac (docker-manage.sh)
|
||||
```bash
|
||||
# Start with file watchers
|
||||
./docker-manage.sh dev:watch
|
||||
|
||||
# Traditional start (without watchers)
|
||||
./docker-manage.sh dev:start
|
||||
```
|
||||
|
||||
### Command Differences
|
||||
|
||||
| Command | Mode | File Watching | Container Rebuild | Use Case |
|
||||
|---------|------|---------------|-------------------|----------|
|
||||
| `dev:start` | Background (-d) | No | Manual only | Traditional development |
|
||||
| `dev:watch` | Foreground | Yes | Automatic | Modern development with live sync |
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. Instant File Synchronization
|
||||
- Source code changes are immediately available in containers
|
||||
- No manual rebuild or restart required for code changes
|
||||
- Maintains all existing hot-reload functionality (nodemon, Vite HMR)
|
||||
|
||||
### 2. Smart Rebuilding
|
||||
- Automatically rebuilds when package.json or Dockerfile changes
|
||||
- Ensures containers stay consistent with dependency updates
|
||||
- Prevents common issues with stale dependencies
|
||||
|
||||
### 3. Development Efficiency
|
||||
- Combines Docker's isolation with native-like development speed
|
||||
- Reduces context switching between local and containerized development
|
||||
- Maintains consistent environment across team members
|
||||
|
||||
## File Patterns Watched
|
||||
|
||||
### Backend
|
||||
- **Synced Files**:
|
||||
- `src/` directory (all TypeScript source files)
|
||||
- `package.json` (for runtime reference)
|
||||
- **Rebuild Triggers**:
|
||||
- `package-lock.json` (dependency changes)
|
||||
- `Dockerfile_backend.dev` (container configuration)
|
||||
|
||||
### Frontend
|
||||
- **Synced Files**:
|
||||
- `src/` directory (React components, styles, etc.)
|
||||
- `public/` directory (static assets)
|
||||
- `package.json` (for runtime reference)
|
||||
- **Rebuild Triggers**:
|
||||
- `package-lock.json` (dependency changes)
|
||||
- `vite.config.js` (build configuration)
|
||||
- `Dockerfile_frontend.dev` (container configuration)
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Sync Performance
|
||||
- File synchronization is near-instantaneous
|
||||
- Uses Docker's built-in file watching mechanisms
|
||||
- Optimized for development workloads
|
||||
|
||||
### Rebuild Performance
|
||||
- Rebuilds only occur when necessary
|
||||
- Docker layer caching reduces rebuild times
|
||||
- Can be resource-intensive for large dependency changes
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **File Changes Not Reflected**
|
||||
- Ensure you're using `dev:watch` command
|
||||
- Check that files are not in ignore patterns
|
||||
- Verify file paths are correct
|
||||
|
||||
2. **Excessive Rebuilds**
|
||||
- Check for unnecessary changes to rebuild trigger files
|
||||
- Consider moving files to sync-only patterns if appropriate
|
||||
|
||||
3. **Performance Issues**
|
||||
- Monitor Docker resource usage
|
||||
- Consider excluding large directories from watching
|
||||
- Use `.dockerignore` for files that should never be synced
|
||||
|
||||
### Debugging Commands
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose -f SerpentRace_Docker/docker-compose.dev.yml ps
|
||||
|
||||
# View watcher logs
|
||||
docker-compose -f SerpentRace_Docker/docker-compose.dev.yml logs -f backend
|
||||
docker-compose -f SerpentRace_Docker/docker-compose.dev.yml logs -f frontend
|
||||
|
||||
# Check file synchronization
|
||||
docker exec -it serpentrace-backend-dev ls -la /app/src
|
||||
docker exec -it serpentrace-frontend-dev ls -la /app/src
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### Docker Compose Version
|
||||
- Requires Docker Compose v2.22+ for `develop.watch` support
|
||||
- Check version: `docker-compose version`
|
||||
|
||||
### File System
|
||||
- Works on Windows, Linux, and macOS
|
||||
- Performance may vary based on file system type
|
||||
- WSL2 recommended for Windows users
|
||||
|
||||
## Migration from Traditional Setup
|
||||
|
||||
### No Breaking Changes
|
||||
- Existing `dev:start` command continues to work
|
||||
- All volume mounts remain functional
|
||||
- Hot reload functionality preserved
|
||||
|
||||
### Gradual Adoption
|
||||
1. Try `dev:watch` for active development
|
||||
2. Use `dev:start` for background services
|
||||
3. Gradually migrate team to new workflow
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Development Workflow
|
||||
1. Use `dev:watch` during active development
|
||||
2. Make code changes normally
|
||||
3. Watch for automatic synchronization
|
||||
4. Monitor logs for any sync issues
|
||||
|
||||
### File Organization
|
||||
- Keep frequently changed files in sync patterns
|
||||
- Place build configuration in rebuild patterns
|
||||
- Use `.dockerignore` for files that should never sync
|
||||
|
||||
### Team Collaboration
|
||||
- Document which command team members should use
|
||||
- Ensure consistent Docker Compose version across team
|
||||
- Share troubleshooting steps for common issues
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Potential Improvements
|
||||
1. **Selective Service Watching**: Watch only specific services
|
||||
2. **Custom Ignore Patterns**: Per-developer ignore configurations
|
||||
3. **Performance Monitoring**: Built-in sync performance metrics
|
||||
4. **Integration with IDEs**: Better editor integration for sync status
|
||||
|
||||
### Configuration Expansion
|
||||
- Additional file patterns as needed
|
||||
- Service-specific watch configurations
|
||||
- Environment-based watch rules
|
||||
@@ -1,570 +0,0 @@
|
||||
# Frontend Kódolási Útmutató - SerpentRace
|
||||
|
||||
## Tartalomjegyzék
|
||||
1. [Navigáció és Routing](#navigáció-és-routing)
|
||||
2. [Fájl és Mappa Struktúra](#fájl-és-mappa-struktúra)
|
||||
3. [Komponens Konvenciók](#komponens-konvenciók)
|
||||
4. [State Management](#state-management)
|
||||
5. [API Hívások](#api-hívások)
|
||||
6. [Hibakezelés](#hibakezelés)
|
||||
7. [Elnevezési Konvenciók](#elnevezési-konvenciók)
|
||||
|
||||
---
|
||||
|
||||
## Navigáció és Routing
|
||||
|
||||
### ✅ Helyes gyakorlat: HandleNavigate használata
|
||||
|
||||
**MINDIG használd a központosított HandleNavigate hook-ot navigációhoz:**
|
||||
|
||||
```jsx
|
||||
import HandleNavigate from "../../utils/HandleNavigate/HandleNavigate"
|
||||
|
||||
const MyComponent = () => {
|
||||
const { goHome, goLogin, goDeckDetails } = HandleNavigate()
|
||||
|
||||
const handleClick = () => {
|
||||
goHome() // Egyszerű navigáció
|
||||
}
|
||||
|
||||
const handleDeckView = (deckId) => {
|
||||
goDeckDetails(deckId) // Dinamikus route paraméterrel
|
||||
}
|
||||
|
||||
const handleLobby = (gameCode) => {
|
||||
goLobby({ gameCode }) // State passzolással
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Kerülendő: Direkt useNavigate használat
|
||||
|
||||
```jsx
|
||||
// SOHA NE HASZNÁLD EZT!
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
const MyComponent = () => {
|
||||
const navigate = useNavigate()
|
||||
navigate("/home") // ❌ NEM JÓ!
|
||||
}
|
||||
```
|
||||
|
||||
### Elérhető Navigációs Függvények
|
||||
|
||||
**HandleNavigate által biztosított függvények:**
|
||||
|
||||
```jsx
|
||||
const {
|
||||
// Általános
|
||||
goTo, // goTo('/any-path', { state: {...} })
|
||||
goBack, // Vissza az előző oldalra
|
||||
|
||||
// Authentikáció
|
||||
goHome, // → /home
|
||||
goLogin, // → /login, state: { success, message }
|
||||
goRegister, // → /register (alias: goAuth)
|
||||
goLanding, // → / (landing page)
|
||||
|
||||
// Deck Management
|
||||
goDecks, // → /decks
|
||||
goDeckDetails, // goDeckDetails(deckId) → /deck/:deckId
|
||||
goDeckCreator, // → /deck-creator
|
||||
goDeckCreatorEdit, // goDeckCreatorEdit(deckId) → /deck-creator/:deckId
|
||||
|
||||
// Game Flow
|
||||
goLobby, // goLobby({ gameCode }) → /lobby
|
||||
goChooseDeck, // goChooseDeck({ username, deckIds }) → /choosedeck
|
||||
goPlayerSetup, // goPlayerSetup({ deckIds }) → /player-setup
|
||||
goGame, // goGame({ players, gameState }) → /game
|
||||
|
||||
// Egyéb
|
||||
goContacts // → /contacts (alias: goCompanies)
|
||||
} = HandleNavigate()
|
||||
```
|
||||
|
||||
### Route Konstansok
|
||||
|
||||
**Használd a centralizált route konstansokat:**
|
||||
|
||||
```jsx
|
||||
// src/utils/routes.js
|
||||
import { ROUTES } from '../../utils/routes'
|
||||
|
||||
// App.jsx-ben
|
||||
<Route path={ROUTES.HOME} element={<Home />} />
|
||||
<Route path={ROUTES.DECK_DETAILS} element={<DeckDetails />} />
|
||||
|
||||
// ❌ NE használj string literálokat:
|
||||
<Route path="/home" element={<Home />} /> // NEM JÓ!
|
||||
```
|
||||
|
||||
### State Passzolás
|
||||
|
||||
**Így adj át adatokat navigáció során:**
|
||||
|
||||
```jsx
|
||||
// Régi mód (useNavigate) - ❌ NE!
|
||||
navigate('/lobby', { state: { gameCode: 'ABC123' } })
|
||||
|
||||
// Új mód (HandleNavigate) - ✅ JÓ!
|
||||
goLobby({ gameCode: 'ABC123' })
|
||||
|
||||
// Fogadó oldalon:
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
const Lobby = () => {
|
||||
const location = useLocation()
|
||||
const gameCode = location.state?.gameCode
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fájl és Mappa Struktúra
|
||||
|
||||
### Mappa Szervezés
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # API hívások
|
||||
│ ├── userApi.js
|
||||
│ ├── deckApi.js
|
||||
│ └── gameApi.js
|
||||
├── assets/ # Statikus fájlok
|
||||
│ ├── backgrounds/
|
||||
│ ├── images/
|
||||
│ └── icons/
|
||||
├── components/ # Újrahasználható komponensek
|
||||
│ ├── Buttons/
|
||||
│ ├── Inputs/
|
||||
│ ├── Navbar/
|
||||
│ └── PopUp/
|
||||
├── hooks/ # Custom Hooks
|
||||
│ └── useRequireAuth.jsx
|
||||
├── pages/ # Oldal komponensek
|
||||
│ ├── Auth/
|
||||
│ ├── Game/
|
||||
│ ├── Decks/
|
||||
│ └── Landing/
|
||||
├── utils/ # Utility függvények
|
||||
│ ├── HandleNavigate/
|
||||
│ └── routes.js
|
||||
└── App.jsx # Fő alkalmazás komponens
|
||||
```
|
||||
|
||||
### Fájl Elnevezési Konvenciók
|
||||
|
||||
- **Komponensek**: PascalCase
|
||||
- `LoginForm.jsx`, `DeckCreator.jsx`, `ButtonGreen.jsx`
|
||||
|
||||
- **Utility fájlok**: camelCase
|
||||
- `routes.js`, `randomUtils.js`, `userApi.js`
|
||||
|
||||
- **Hook fájlok**: camelCase, "use" prefix
|
||||
- `useRequireAuth.jsx`, `useLocalStorage.jsx`
|
||||
|
||||
---
|
||||
|
||||
## Komponens Konvenciók
|
||||
|
||||
### Funkcionális Komponens Sablon
|
||||
|
||||
```jsx
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import HandleNavigate from '../../utils/HandleNavigate/HandleNavigate'
|
||||
|
||||
/**
|
||||
* Komponens rövid leírása
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const MyComponent = () => {
|
||||
// 1. Hooks (HandleNavigate, useState, useEffect, stb.)
|
||||
const { goHome } = HandleNavigate()
|
||||
const [data, setData] = useState(null)
|
||||
|
||||
// 2. Effect hooks
|
||||
useEffect(() => {
|
||||
// Component mount logic
|
||||
}, [])
|
||||
|
||||
// 3. Event handlers
|
||||
const handleClick = () => {
|
||||
// Logic
|
||||
}
|
||||
|
||||
// 4. Render
|
||||
return (
|
||||
<div>
|
||||
{/* JSX */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyComponent
|
||||
```
|
||||
|
||||
### Import Sorrend
|
||||
|
||||
```jsx
|
||||
// 1. React és third-party library-k
|
||||
import React, { useState } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
// 2. React Router hooks (useLocation, useParams - NEM useNavigate!)
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
// 3. Custom hooks és utils
|
||||
import HandleNavigate from '../../utils/HandleNavigate/HandleNavigate'
|
||||
import useRequireAuth from '../../hooks/useRequireAuth'
|
||||
|
||||
// 4. API
|
||||
import { getUserData } from '../../api/userApi'
|
||||
|
||||
// 5. Komponensek
|
||||
import Button from '../../components/Buttons/Button'
|
||||
import Navbar from '../../components/Navbar/Navbar'
|
||||
|
||||
// 6. Assets
|
||||
import Background from '../../assets/backgrounds/Background'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State
|
||||
|
||||
```jsx
|
||||
// Egyszerű state
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Object state
|
||||
const [user, setUser] = useState({
|
||||
name: '',
|
||||
email: ''
|
||||
})
|
||||
|
||||
// Array state
|
||||
const [items, setItems] = useState([])
|
||||
```
|
||||
|
||||
### LocalStorage Használat
|
||||
|
||||
**useRequireAuth hook használata auth kezeléshez:**
|
||||
|
||||
```jsx
|
||||
import useRequireAuth from '../../hooks/useRequireAuth'
|
||||
|
||||
const MyComponent = () => {
|
||||
const [username] = useRequireAuth({
|
||||
key: 'username',
|
||||
redirectTo: '/login'
|
||||
})
|
||||
|
||||
// username automatikusan szinkronizálva van localStorage-el
|
||||
// Ha nincs username, automatikus redirect /login-re
|
||||
}
|
||||
```
|
||||
|
||||
**Manuális localStorage:**
|
||||
|
||||
```jsx
|
||||
// Írás
|
||||
localStorage.setItem('gameToken', token)
|
||||
|
||||
// Olvasás
|
||||
const token = localStorage.getItem('gameToken')
|
||||
|
||||
// Törlés
|
||||
localStorage.removeItem('gameToken')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Hívások
|
||||
|
||||
### API File Struktúra
|
||||
|
||||
**Minden API endpoint egy külön file-ban (`userApi.js`, `deckApi.js`, `gameApi.js`):**
|
||||
|
||||
```jsx
|
||||
// src/api/userApi.js
|
||||
import axiosInstance from './axiosInstance'
|
||||
|
||||
export const getUserData = async (userId) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/users/${userId}`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('Error fetching user data:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const updateUser = async (userId, userData) => {
|
||||
try {
|
||||
const response = await axiosInstance.put(`/users/${userId}`, userData)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('Error updating user:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Hívás Komponensben
|
||||
|
||||
```jsx
|
||||
import { getUserData } from '../../api/userApi'
|
||||
|
||||
const MyComponent = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const [data, setData] = useState(null)
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const result = await getUserData(userId)
|
||||
setData(result)
|
||||
} catch (err) {
|
||||
setError(err.message || 'Hiba történt')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [userId])
|
||||
|
||||
if (loading) return <div>Betöltés...</div>
|
||||
if (error) return <div>Hiba: {error}</div>
|
||||
|
||||
return <div>{/* data megjelenítése */}</div>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hibakezelés
|
||||
|
||||
### Try-Catch Blokkok
|
||||
|
||||
```jsx
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const response = await createDeck(deckData)
|
||||
// Siker kezelése
|
||||
notifySuccess('Deck sikeresen létrehozva!')
|
||||
goDecks()
|
||||
} catch (error) {
|
||||
// Hiba kezelése
|
||||
const errorMessage = error.response?.data?.message || 'Ismeretlen hiba'
|
||||
setError(errorMessage)
|
||||
notifyError(errorMessage)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Toast Notifications
|
||||
|
||||
```jsx
|
||||
import { notifySuccess, notifyError } from '../../components/Toastify/toastifyServices'
|
||||
|
||||
// Siker üzenet
|
||||
notifySuccess('✅ Művelet sikeres!')
|
||||
|
||||
// Hiba üzenet
|
||||
notifyError('❌ Hiba történt!')
|
||||
|
||||
// Egyedi konfiguráció
|
||||
notifySuccess('Mentve!', { autoClose: 2000 })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Elnevezési Konvenciók
|
||||
|
||||
### JavaScript/React
|
||||
|
||||
| Típus | Konvenció | Példa |
|
||||
|-------|-----------|-------|
|
||||
| Komponensek | PascalCase | `LoginForm`, `DeckCreator` |
|
||||
| Függvények | camelCase | `handleClick`, `fetchUserData` |
|
||||
| Változók | camelCase | `userName`, `isLoading` |
|
||||
| Konstansok | UPPER_SNAKE_CASE | `API_BASE_URL`, `MAX_PLAYERS` |
|
||||
| Private változók | _camelCase | `_internalState` |
|
||||
| Event handlers | handle + PascalCase | `handleSubmit`, `handleInputChange` |
|
||||
| Boolean változók | is/has/can prefix | `isLoading`, `hasError`, `canEdit` |
|
||||
|
||||
### CSS Classes (Tailwind)
|
||||
|
||||
```jsx
|
||||
// Használj explicit class neveket
|
||||
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
|
||||
|
||||
// Kerüld a túl hosszú class stringeket - bontsd több sorra
|
||||
<div
|
||||
className="
|
||||
flex items-center justify-between
|
||||
p-4 bg-white rounded-lg shadow-md
|
||||
hover:shadow-lg transition-shadow duration-200
|
||||
"
|
||||
>
|
||||
```
|
||||
|
||||
### Fájl Nevek
|
||||
|
||||
- **Egyedi komponens**: `LoginForm.jsx` (nem `login-form.jsx`)
|
||||
- **Index fájlok**: `index.jsx` (ha könyvtárban több file van)
|
||||
- **Utility fájlok**: `randomUtils.js` (camelCase)
|
||||
- **API fájlok**: `userApi.js` (camelCase + Api postfix)
|
||||
|
||||
---
|
||||
|
||||
## Teljes Példa - Best Practices
|
||||
|
||||
```jsx
|
||||
// src/pages/Example/ExamplePage.jsx
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
import HandleNavigate from '../../utils/HandleNavigate/HandleNavigate'
|
||||
import useRequireAuth from '../../hooks/useRequireAuth'
|
||||
import { fetchExampleData, updateExampleData } from '../../api/exampleApi'
|
||||
import { notifySuccess, notifyError } from '../../components/Toastify/toastifyServices'
|
||||
|
||||
import Navbar from '../../components/Navbar/Navbar'
|
||||
import Button from '../../components/Buttons/Button'
|
||||
import Background from '../../assets/backgrounds/Background'
|
||||
|
||||
/**
|
||||
* Example Page - Komponens rövid leírása
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const ExamplePage = () => {
|
||||
// 1. Auth & Navigation
|
||||
const [username] = useRequireAuth({ key: 'username', redirectTo: '/login' })
|
||||
const { goHome, goBack } = HandleNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
// 2. State
|
||||
const [data, setData] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
// 3. Effects
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
// 4. Functions
|
||||
const loadData = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const result = await fetchExampleData()
|
||||
setData(result)
|
||||
} catch (err) {
|
||||
const errorMsg = err.response?.data?.message || 'Hiba történt'
|
||||
setError(errorMsg)
|
||||
notifyError(errorMsg)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await updateExampleData(data)
|
||||
notifySuccess('✅ Sikeresen mentve!')
|
||||
goHome()
|
||||
} catch (err) {
|
||||
notifyError('❌ Mentés sikertelen')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
goBack()
|
||||
}
|
||||
|
||||
// 5. Conditional Rendering
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div>Betöltés...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-red-500">Hiba: {error}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 6. Main Render
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<Background />
|
||||
<Navbar />
|
||||
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<h1 className="text-3xl font-bold mb-6">Example Page</h1>
|
||||
|
||||
{/* Content */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
{data && (
|
||||
<div>
|
||||
{/* Render data */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-4 mt-6">
|
||||
<Button onClick={handleSave} text="Mentés" />
|
||||
<Button onClick={handleCancel} text="Mégse" variant="secondary" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExamplePage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Összefoglalás - Legfontosabb Szabályok
|
||||
|
||||
1. ✅ **MINDIG használd HandleNavigate-et** navigációhoz
|
||||
2. ✅ **Használd a ROUTES konstansokat** az App.jsx-ben
|
||||
3. ✅ **API hívások külön file-okban** (userApi.js, deckApi.js, stb.)
|
||||
4. ✅ **Try-catch minden async műveletnél**
|
||||
5. ✅ **Toast notifications** a felhasználói visszajelzéshez
|
||||
6. ✅ **useRequireAuth hook** auth védett oldalaknál
|
||||
7. ✅ **Konzisztens import sorrend**
|
||||
8. ✅ **PascalCase komponenseknek, camelCase változóknak**
|
||||
9. ❌ **SOHA ne használj useNavigate közvetlen**
|
||||
10. ❌ **Ne használj string literal route-okat**
|
||||
|
||||
---
|
||||
|
||||
**Verzió:** 1.0
|
||||
**Utolsó frissítés:** 2025-01-17
|
||||
**Készítette:** SerpentRace Development Team
|
||||
@@ -1,476 +0,0 @@
|
||||
# Frontend Game Completion Implementation
|
||||
|
||||
**Date:** November 19, 2025
|
||||
**Status:** ✅ Core gameplay event handlers and action methods implemented
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document details the completion of missing WebSocket event handlers and action methods in the frontend to enable full gameplay functionality. The implementation ensures that GameScreen can properly receive and respond to all game events from the backend.
|
||||
|
||||
---
|
||||
|
||||
## Changes Implemented
|
||||
|
||||
### 1. GameWebSocketContext.jsx - Added Missing Event Handlers
|
||||
|
||||
**Location:** `SerpentRace_Frontend/src/contexts/GameWebSocketContext.jsx`
|
||||
|
||||
Added 9 critical gameplay event handlers that were missing from the context:
|
||||
|
||||
#### ✅ game:your-turn
|
||||
- **Purpose:** Notifies player when it's their turn
|
||||
- **Action:** Updates currentTurn state, emits custom event for GameScreen
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:your-turn', (data) => {
|
||||
log('🎯 Your turn!', data);
|
||||
setCurrentTurn(data.currentPlayer);
|
||||
window.dispatchEvent(new CustomEvent('game:your-turn', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:dice-rolled
|
||||
- **Purpose:** Broadcasts dice roll results to all players
|
||||
- **Action:** Emits custom event for UI to display dice animation
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:dice-rolled', (data) => {
|
||||
log('🎲 Dice rolled:', data.diceValue, 'by', data.playerName);
|
||||
window.dispatchEvent(new CustomEvent('game:dice-rolled', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:guess-result
|
||||
- **Purpose:** Receives position guess validation result
|
||||
- **Action:** Updates player position if guess was correct, emits event
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:guess-result', (data) => {
|
||||
log('🎯 Guess result:', data);
|
||||
if (data.correct && data.newPosition !== undefined) {
|
||||
setBoardData(prev => {
|
||||
if (!prev) return prev;
|
||||
const updatedPlayers = { ...prev.playerPositions };
|
||||
updatedPlayers[data.playerName] = data.newPosition;
|
||||
return { ...prev, playerPositions: updatedPlayers };
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('game:guess-result', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:joker-complete
|
||||
- **Purpose:** Receives joker card approval/rejection result
|
||||
- **Action:** Updates player position if joker was approved, emits event
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:joker-complete', (data) => {
|
||||
log('🃏 Joker complete:', data);
|
||||
if (data.approved && data.newPosition !== undefined) {
|
||||
setBoardData(prev => {
|
||||
if (!prev) return prev;
|
||||
const updatedPlayers = { ...prev.playerPositions };
|
||||
updatedPlayers[data.playerName] = data.newPosition;
|
||||
return { ...prev, playerPositions: updatedPlayers };
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('game:joker-complete', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:luck-consequence
|
||||
- **Purpose:** Receives luck card consequence (extra turns, lost turns, position changes)
|
||||
- **Action:** Updates player position if consequence includes movement, emits event
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:luck-consequence', (data) => {
|
||||
log('🍀 Luck consequence:', data);
|
||||
if (data.newPosition !== undefined && data.playerName) {
|
||||
setBoardData(prev => {
|
||||
if (!prev) return prev;
|
||||
const updatedPlayers = { ...prev.playerPositions };
|
||||
updatedPlayers[data.playerName] = data.newPosition;
|
||||
return { ...prev, playerPositions: updatedPlayers };
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('game:luck-consequence', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:ended
|
||||
- **Purpose:** Announces game end with winner and final scores
|
||||
- **Action:** Updates gameState with winner and final scores, emits event for winner modal
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:ended', (data) => {
|
||||
log('🏁 Game ended! Winner:', data.winner);
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
status: 'finished',
|
||||
winner: data.winner,
|
||||
finalScores: data.scores
|
||||
}));
|
||||
window.dispatchEvent(new CustomEvent('game:ended', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:extra-turn-remaining
|
||||
- **Purpose:** Notifies player they have extra turn(s) from luck consequences
|
||||
- **Action:** Emits event for UI notification
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:extra-turn-remaining', (data) => {
|
||||
log('⭐ Extra turn remaining:', data);
|
||||
window.dispatchEvent(new CustomEvent('game:extra-turn-remaining', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:players-skipped
|
||||
- **Purpose:** Broadcasts when players are skipped due to lost turn consequences
|
||||
- **Action:** Emits event for UI notification
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:players-skipped', (data) => {
|
||||
log('⏭️ Players skipped:', data.skippedPlayers);
|
||||
window.dispatchEvent(new CustomEvent('game:players-skipped', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
#### ✅ game:cleanup-complete
|
||||
- **Purpose:** Confirms cleanup after game end
|
||||
- **Action:** Emits event for final UI state reset
|
||||
- **Implementation:**
|
||||
```javascript
|
||||
socket.on('game:cleanup-complete', (data) => {
|
||||
log('🧹 Cleanup complete:', data);
|
||||
window.dispatchEvent(new CustomEvent('game:cleanup-complete', { detail: data }));
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. GameWebSocketContext.jsx - Added Missing Action Methods
|
||||
|
||||
Added 4 critical action methods that players and gamemaster need:
|
||||
|
||||
#### ✅ submitAnswer(answer)
|
||||
- **Purpose:** Submit answer to question card
|
||||
- **Parameters:** `answer` - Player's answer (type depends on card type: string for QUIZ/OWN_ANSWER, array for SENTENCE_PAIRING, boolean for TRUE_FALSE, number for CLOSER)
|
||||
- **Emits:** `game:card-answer` with gameCode and answer
|
||||
- **Returns:** Boolean (success/failure)
|
||||
|
||||
```javascript
|
||||
const submitAnswer = useCallback((answer) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot submit answer: not connected');
|
||||
return false;
|
||||
}
|
||||
log('📝 Submitting answer:', answer);
|
||||
socket.emit('game:card-answer', { gameCode: gameState?.gameCode, answer });
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
```
|
||||
|
||||
#### ✅ submitPositionGuess(guessedPosition)
|
||||
- **Purpose:** Submit position guess after correct answer
|
||||
- **Parameters:** `guessedPosition` - Number (0-99) representing guessed board position
|
||||
- **Emits:** `game:position-guess` with gameCode and guessedPosition
|
||||
- **Returns:** Boolean (success/failure)
|
||||
|
||||
```javascript
|
||||
const submitPositionGuess = useCallback((guessedPosition) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected) {
|
||||
warn('⚠️ Cannot submit position guess: not connected');
|
||||
return false;
|
||||
}
|
||||
log('🎯 Submitting position guess:', guessedPosition);
|
||||
socket.emit('game:position-guess', { gameCode: gameState?.gameCode, guessedPosition });
|
||||
return true;
|
||||
}, [isConnected, gameState?.gameCode]);
|
||||
```
|
||||
|
||||
#### ✅ approveJoker(requestId)
|
||||
- **Purpose:** Gamemaster approves joker card
|
||||
- **Parameters:** `requestId` - Unique identifier for joker decision request
|
||||
- **Emits:** `game:gamemaster-decision` with gameCode, requestId, decision: 'approve'
|
||||
- **Returns:** Boolean (success/failure)
|
||||
- **Authorization:** Requires isGamemaster = true
|
||||
|
||||
```javascript
|
||||
const approveJoker = useCallback((requestId) => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected || !isGamemaster) {
|
||||
warn('⚠️ Cannot approve joker: not gamemaster or not connected');
|
||||
return false;
|
||||
}
|
||||
log('✅ Approving joker request:', requestId);
|
||||
socket.emit('game:gamemaster-decision', {
|
||||
gameCode: gameState?.gameCode,
|
||||
requestId,
|
||||
decision: 'approve'
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, isGamemaster, gameState?.gameCode]);
|
||||
```
|
||||
|
||||
#### ✅ rejectJoker(requestId, reason?)
|
||||
- **Purpose:** Gamemaster rejects joker card
|
||||
- **Parameters:**
|
||||
- `requestId` - Unique identifier for joker decision request
|
||||
- `reason` - Optional rejection reason (default: 'Joker answer rejected')
|
||||
- **Emits:** `game:gamemaster-decision` with gameCode, requestId, decision: 'reject', reason
|
||||
- **Returns:** Boolean (success/failure)
|
||||
- **Authorization:** Requires isGamemaster = true
|
||||
|
||||
```javascript
|
||||
const rejectJoker = useCallback((requestId, reason = 'Joker answer rejected') => {
|
||||
const socket = socketRef.current;
|
||||
if (!socket || !isConnected || !isGamemaster) {
|
||||
warn('⚠️ Cannot reject joker: not gamemaster or not connected');
|
||||
return false;
|
||||
}
|
||||
log('❌ Rejecting joker request:', requestId, 'Reason:', reason);
|
||||
socket.emit('game:gamemaster-decision', {
|
||||
gameCode: gameState?.gameCode,
|
||||
requestId,
|
||||
decision: 'reject',
|
||||
reason
|
||||
});
|
||||
return true;
|
||||
}, [isConnected, isGamemaster, gameState?.gameCode]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. GameWebSocketContext.jsx - Updated Context Value Export
|
||||
|
||||
Updated the context value to export all new methods:
|
||||
|
||||
```javascript
|
||||
const value = {
|
||||
socket: socketRef.current,
|
||||
isConnected,
|
||||
gameState,
|
||||
players,
|
||||
boardData,
|
||||
currentTurn,
|
||||
error,
|
||||
isGamemaster,
|
||||
gameStarted,
|
||||
pendingPlayers,
|
||||
approvalStatus,
|
||||
// Connection management
|
||||
connect,
|
||||
disconnect,
|
||||
// Methods
|
||||
rollDice,
|
||||
sendMessage,
|
||||
setReady,
|
||||
leaveGame,
|
||||
approvePlayer,
|
||||
rejectPlayer,
|
||||
submitAnswer, // ✅ NEW
|
||||
submitPositionGuess, // ✅ NEW
|
||||
approveJoker, // ✅ NEW
|
||||
rejectJoker, // ✅ NEW
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. GameScreen.jsx - Fixed Action Method Calls
|
||||
|
||||
**Location:** `SerpentRace_Frontend/src/pages/Game/GameScreen.jsx`
|
||||
|
||||
#### Fixed handleSubmitAnswer
|
||||
**Before:**
|
||||
```javascript
|
||||
const handleSubmitAnswer = useCallback((answer) => {
|
||||
if (currentCard?.id) {
|
||||
submitAnswer(currentCard.id, answer) // ❌ Wrong parameters
|
||||
}
|
||||
}, [currentCard?.id, submitAnswer])
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
const handleSubmitAnswer = useCallback((answer) => {
|
||||
console.log('📝 Válasz beküldve:', answer)
|
||||
submitAnswer(answer) // ✅ Correct - backend extracts gameCode from context
|
||||
}, [submitAnswer])
|
||||
```
|
||||
|
||||
#### Fixed handleApproveJoker
|
||||
**Before:**
|
||||
```javascript
|
||||
const handleApproveJoker = useCallback(async (jokerRequest) => {
|
||||
approveJoker(jokerRequest.playerId, jokerRequest.cardId, jokerRequest.requestId) // ❌ Wrong parameters
|
||||
setIsJokerModalOpen(false)
|
||||
}, [approveJoker])
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
const handleApproveJoker = useCallback(async (jokerRequest) => {
|
||||
console.log('✅ Joker feladat jóváhagyva:', jokerRequest)
|
||||
approveJoker(jokerRequest.requestId) // ✅ Correct - only requestId needed
|
||||
setIsJokerModalOpen(false)
|
||||
}, [approveJoker])
|
||||
```
|
||||
|
||||
#### Fixed handleRejectJoker
|
||||
**Before:**
|
||||
```javascript
|
||||
const handleRejectJoker = useCallback(async (jokerRequest) => {
|
||||
rejectJoker(jokerRequest.playerId, jokerRequest.cardId, jokerRequest.requestId) // ❌ Wrong parameters
|
||||
setIsJokerModalOpen(false)
|
||||
}, [rejectJoker])
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
const handleRejectJoker = useCallback(async (jokerRequest) => {
|
||||
console.log('❌ Joker feladat elutasítva:', jokerRequest)
|
||||
rejectJoker(jokerRequest.requestId, 'Joker rejected by gamemaster') // ✅ Correct
|
||||
setIsJokerModalOpen(false)
|
||||
}, [rejectJoker])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Benefits
|
||||
|
||||
### ✅ Centralized Event Handling
|
||||
All WebSocket events are handled in the context, ensuring:
|
||||
- Single source of truth for game state
|
||||
- Consistent state updates across all components
|
||||
- Easy debugging with centralized logging
|
||||
|
||||
### ✅ Custom Event Bridge
|
||||
Events are re-emitted as CustomEvents via `window.dispatchEvent()`, allowing:
|
||||
- GameScreen to add specific UI logic without modifying context
|
||||
- Separation of concerns (state management vs UI presentation)
|
||||
- Multiple components can listen to the same events independently
|
||||
|
||||
### ✅ Persistent Connection
|
||||
Socket connection persists across navigation (Lobby → GameScreen), ensuring:
|
||||
- No disconnections during page transitions
|
||||
- Gamemaster can start game without socket dropping
|
||||
- Real-time updates continue seamlessly
|
||||
|
||||
### ✅ Type Safety & Validation
|
||||
All action methods include:
|
||||
- Connection state checks (`isConnected`)
|
||||
- Authorization checks (`isGamemaster` for approval methods)
|
||||
- Error logging for debugging
|
||||
- Boolean return values for success/failure
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### ✅ Event Handler Tests
|
||||
- [ ] Test `game:your-turn` - Turn indicator updates
|
||||
- [ ] Test `game:dice-rolled` - Dice animation triggers
|
||||
- [ ] Test `game:guess-result` - Position updates on correct guess
|
||||
- [ ] Test `game:joker-complete` - Position updates on approved joker
|
||||
- [ ] Test `game:luck-consequence` - Position updates from luck cards
|
||||
- [ ] Test `game:ended` - Winner modal displays with final scores
|
||||
- [ ] Test `game:extra-turn-remaining` - Extra turn notification
|
||||
- [ ] Test `game:players-skipped` - Skip notification
|
||||
- [ ] Test `game:cleanup-complete` - Cleanup confirmation
|
||||
|
||||
### ✅ Action Method Tests
|
||||
- [ ] Test `submitAnswer()` - Answer submission for all card types (QUIZ, SENTENCE_PAIRING, TRUE_FALSE, CLOSER, OWN_ANSWER)
|
||||
- [ ] Test `submitPositionGuess()` - Position guess submission
|
||||
- [ ] Test `approveJoker()` - Gamemaster approval (requires isGamemaster)
|
||||
- [ ] Test `rejectJoker()` - Gamemaster rejection (requires isGamemaster)
|
||||
|
||||
### ✅ Integration Tests
|
||||
- [ ] Complete game flow: Start → Dice → Card → Answer → Position Guess → Next Turn
|
||||
- [ ] Joker flow: Joker drawn → Request sent → Gamemaster decision → Position update
|
||||
- [ ] Luck flow: Luck card → Consequence applied → Position/turn updated
|
||||
- [ ] End game flow: Player reaches finish → Winner announced → Scores displayed
|
||||
|
||||
---
|
||||
|
||||
## Remaining UI Enhancements
|
||||
|
||||
### 🎨 Turn Indicator Component
|
||||
**Status:** Not implemented
|
||||
**Description:** Visual indicator showing whose turn it is
|
||||
**Events:** `game:your-turn`, `game:turn-changed`
|
||||
**Location:** GameScreen.jsx header area
|
||||
|
||||
### ⏱️ Timer Component
|
||||
**Status:** Not implemented
|
||||
**Description:** Countdown timer for card answers (60s) and joker decisions (120s)
|
||||
**Events:** `game:card-drawn-self`, `game:gamemaster-decision-request`
|
||||
**Location:** CardDisplayModal, JokerApprovalModal
|
||||
|
||||
### 🏆 Winner Modal
|
||||
**Status:** Not implemented
|
||||
**Description:** Full-screen modal showing winner, final scores, and play again option
|
||||
**Events:** `game:ended`
|
||||
**Location:** GameScreen.jsx (new modal component)
|
||||
|
||||
### ✨ Position Update Animations
|
||||
**Status:** Not implemented
|
||||
**Description:** Smooth token movement animations for position changes
|
||||
**Events:** `game:player-moved`, `game:guess-result`, `game:joker-complete`, `game:luck-consequence`
|
||||
**Location:** GameScreen.jsx player token rendering
|
||||
|
||||
### 📊 Score Display
|
||||
**Status:** Not implemented
|
||||
**Description:** Live leaderboard showing player rankings
|
||||
**State:** `players` array with position data
|
||||
**Location:** GameScreen.jsx sidebar or header
|
||||
|
||||
---
|
||||
|
||||
## Known Issues & Future Work
|
||||
|
||||
### 🐛 Known Issues
|
||||
None currently - all core functionality implemented and error-free.
|
||||
|
||||
### 🚀 Future Enhancements
|
||||
1. **Notification System** - Toast/notification UI for game events
|
||||
2. **Sound Effects** - Audio feedback for dice, cards, turns
|
||||
3. **Animation Polish** - Smooth transitions for all state changes
|
||||
4. **Mobile Responsiveness** - Touch-friendly controls for mobile devices
|
||||
5. **Accessibility** - ARIA labels, keyboard navigation, screen reader support
|
||||
6. **Reconnection Logic** - Handle network interruptions gracefully
|
||||
7. **Spectator Mode** - Allow non-playing users to watch games
|
||||
8. **Chat System** - Player communication during game
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **9 critical event handlers** added to GameWebSocketContext
|
||||
✅ **4 essential action methods** added to GameWebSocketContext
|
||||
✅ **3 handler fixes** in GameScreen for correct parameter usage
|
||||
✅ **Zero compilation errors** - all changes validated
|
||||
✅ **Full gameplay flow** now supported by frontend
|
||||
|
||||
The frontend is now **functionally complete** for core gameplay. Players can:
|
||||
- Receive turn notifications
|
||||
- Roll dice and move
|
||||
- Draw and answer cards
|
||||
- Submit position guesses
|
||||
- Complete joker challenges (with gamemaster approval)
|
||||
- Experience luck consequences
|
||||
- See game end with winner announcement
|
||||
|
||||
Remaining work is **UI polish** (animations, timers, winner screen) rather than functional gaps.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** November 19, 2025
|
||||
**Next Steps:** Implement UI enhancements and run comprehensive integration tests.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,117 +0,0 @@
|
||||
# pgAdmin Database Administration Guide
|
||||
|
||||
## Access pgAdmin
|
||||
|
||||
- **URL**: http://localhost:8080
|
||||
- **Email**: admin@serpentrace.dev
|
||||
- **Password**: admin
|
||||
|
||||
## Pre-configured Server
|
||||
|
||||
The pgAdmin interface should have a pre-configured server named **"SerpentRace PostgreSQL Dev"** in the "Development" group.
|
||||
|
||||
## Manual Server Configuration (If Needed)
|
||||
|
||||
If the server is not automatically configured, add it manually:
|
||||
|
||||
### Server Details
|
||||
- **Name**: SerpentRace PostgreSQL Dev
|
||||
- **Host**: postgres (or localhost if connecting from outside Docker)
|
||||
- **Port**: 5432
|
||||
- **Database**: serpentrace
|
||||
- **Username**: postgres
|
||||
- **Password**: postgres
|
||||
|
||||
### Steps to Add Server Manually
|
||||
|
||||
1. Right-click on "Servers" in the left panel
|
||||
2. Select "Register" > "Server..."
|
||||
3. Fill in the "General" tab:
|
||||
- Name: `SerpentRace PostgreSQL Dev`
|
||||
- Server group: `Development`
|
||||
4. Fill in the "Connection" tab:
|
||||
- Host name/address: `postgres`
|
||||
- Port: `5432`
|
||||
- Maintenance database: `serpentrace`
|
||||
- Username: `postgres`
|
||||
- Password: `postgres`
|
||||
5. Click "Save"
|
||||
|
||||
## Common Database Operations
|
||||
|
||||
### View Tables
|
||||
1. Expand the server connection
|
||||
2. Expand "Databases" > "serpentrace"
|
||||
3. Expand "Schemas" > "public"
|
||||
4. Expand "Tables"
|
||||
|
||||
### Run SQL Queries
|
||||
1. Right-click on the database name
|
||||
2. Select "Query Tool"
|
||||
3. Write your SQL queries in the editor
|
||||
4. Click the "Execute" button or press F5
|
||||
|
||||
### View Data
|
||||
1. Right-click on any table
|
||||
2. Select "View/Edit Data" > "All Rows"
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
- Ensure Docker containers are running: `docker ps`
|
||||
- Check container logs: `docker logs serpentrace-postgres-dev`
|
||||
- Test connections: `npm run test:connections`
|
||||
|
||||
### Authentication Failed
|
||||
- Verify the password is correct: `postgres`
|
||||
- Check if you're using the correct hostname: `postgres` (inside Docker) vs `localhost` (outside Docker)
|
||||
|
||||
### Server Not Appearing
|
||||
- Restart pgAdmin container:
|
||||
```bash
|
||||
docker-compose -f docker-compose.dev.yml restart pgadmin
|
||||
```
|
||||
- Clear browser cache and reload
|
||||
|
||||
## Development Tips
|
||||
|
||||
### Useful SQL Queries
|
||||
|
||||
```sql
|
||||
-- List all tables
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public';
|
||||
|
||||
-- Check database size
|
||||
SELECT pg_size_pretty(pg_database_size('serpentrace'));
|
||||
|
||||
-- View active connections
|
||||
SELECT * FROM pg_stat_activity WHERE datname = 'serpentrace';
|
||||
|
||||
-- Check migration status (if using TypeORM)
|
||||
SELECT * FROM migrations ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
### Database Backup
|
||||
1. Right-click on database name
|
||||
2. Select "Backup..."
|
||||
3. Choose format (Custom recommended for pgAdmin restore)
|
||||
4. Set filename and location
|
||||
5. Click "Backup"
|
||||
|
||||
### Database Restore
|
||||
1. Right-click on "Databases"
|
||||
2. Select "Restore..."
|
||||
3. Choose the backup file
|
||||
4. Configure options as needed
|
||||
5. Click "Restore"
|
||||
|
||||
## Security Notes
|
||||
|
||||
⚠️ **Development Only**: The current configuration uses default credentials and is intended for development only. For production:
|
||||
|
||||
- Use strong, unique passwords
|
||||
- Enable SSL connections
|
||||
- Restrict network access
|
||||
- Use environment variables for credentials
|
||||
- Enable authentication and authorization features
|
||||
@@ -1,285 +0,0 @@
|
||||
import express from 'express';
|
||||
import { createServer } from 'http';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import helmet from 'helmet';
|
||||
import { AppDataSource } from '../Infrastructure/ormconfig';
|
||||
import userRouter from './routers/userRouter';
|
||||
import organizationRouter from './routers/organizationRouter';
|
||||
import deckRouter from './routers/deckRouter';
|
||||
import chatRouter from './routers/chatRouter';
|
||||
import contactRouter from './routers/contactRouter';
|
||||
import adminRouter from './routers/adminRouter';
|
||||
import deckImportExportRouter from './routers/deckImportExportRouter';
|
||||
import gameRouter from './routers/gameRouter';
|
||||
import { LoggingService, logStartup, logConnection, logError, logRequest } from '../Application/Services/Logger';
|
||||
import { WebSocketService } from '../Application/Services/WebSocketService';
|
||||
import { GameWebSocketService } from '../Application/Services/GameWebSocketService';
|
||||
import { container } from '../Application/Services/DIContainer';
|
||||
import { GameRepository } from '../Infrastructure/Repository/GameRepository';
|
||||
import { UserRepository } from '../Infrastructure/Repository/UserRepository';
|
||||
import { RedisService } from '../Application/Services/RedisService';
|
||||
import { setupSwagger } from './swagger/swaggerUiSetup';
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
const loggingService = LoggingService.getInstance();
|
||||
|
||||
logStartup('SerpentRace Backend starting up', {
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
port: PORT,
|
||||
nodeVersion: process.version,
|
||||
chatInactivityTimeout: process.env.CHAT_INACTIVITY_TIMEOUT_MINUTES || '30'
|
||||
});
|
||||
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: isDevelopment ? false : undefined
|
||||
}));
|
||||
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(loggingService.requestLoggingMiddleware());
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const origin = req.headers.origin;
|
||||
const allowedOrigins = ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:8080', process.env.FRONTEND_URL];
|
||||
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
||||
}
|
||||
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cookie');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(200).end();
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
if (isDevelopment) {
|
||||
app.use((req, res, next) => {
|
||||
logRequest(`${req.method} ${req.path}`, req, res);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Swagger documentation
|
||||
setupSwagger(app);
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
service: 'SerpentRace Backend API',
|
||||
status: 'running',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
swagger: '/api-docs',
|
||||
users: '/api/users',
|
||||
organizations: '/api/organizations',
|
||||
decks: '/api/decks',
|
||||
chats: '/api/chats',
|
||||
contacts: '/api/contacts',
|
||||
admin: '/api/admin',
|
||||
deckImportExport: '/api/deck-import-export',
|
||||
health: '/health'
|
||||
},
|
||||
websocket: {
|
||||
enabled: true,
|
||||
events: [
|
||||
'chat:join', 'chat:leave', 'message:send',
|
||||
'group:create', 'chat:direct', 'game:chat:create',
|
||||
'chat:history'
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', async (req, res) => {
|
||||
try {
|
||||
const isDbConnected = AppDataSource.isInitialized;
|
||||
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'SerpentRace Backend API',
|
||||
version: '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
database: {
|
||||
connected: isDbConnected,
|
||||
type: AppDataSource.options.type
|
||||
},
|
||||
websocket: {
|
||||
enabled: true
|
||||
},
|
||||
uptime: process.uptime()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(503).json({
|
||||
status: 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
error: 'Service health check failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// API Routes
|
||||
app.use('/api/users', userRouter);
|
||||
app.use('/api/organizations', organizationRouter);
|
||||
app.use('/api/decks', deckRouter);
|
||||
app.use('/api/chats', chatRouter);
|
||||
app.use('/api/contacts', contactRouter);
|
||||
app.use('/api/admin', adminRouter);
|
||||
app.use('/api/deck-import-export', deckImportExportRouter);
|
||||
app.use('/api/games', gameRouter);
|
||||
|
||||
// Global error handler (must be after routes)
|
||||
app.use(loggingService.errorLoggingMiddleware());
|
||||
app.use((error: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logError('Global error handler caught unhandled error', error, req, res);
|
||||
|
||||
// Don't expose internal error details in production
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
timestamp: new Date().toISOString(),
|
||||
...(isDevelopment && { details: error.message, stack: error.stack })
|
||||
});
|
||||
});
|
||||
|
||||
// Handle 404 routes
|
||||
app.use((req: express.Request, res: express.Response) => {
|
||||
res.status(404).json({
|
||||
error: 'Route not found',
|
||||
path: req.originalUrl,
|
||||
method: req.method,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize WebSocket service after database connection
|
||||
let webSocketService: WebSocketService;
|
||||
let gameWebSocketService: GameWebSocketService;
|
||||
let server: any; // Declare server variable
|
||||
|
||||
// Initialize database connection and start server
|
||||
AppDataSource.initialize()
|
||||
.then(() => {
|
||||
const dbOptions = AppDataSource.options as any;
|
||||
logConnection('Database connection established', 'postgresql', 'success', {
|
||||
type: dbOptions.type,
|
||||
host: dbOptions.host,
|
||||
database: dbOptions.database
|
||||
});
|
||||
|
||||
// Initialize WebSocket service after database is connected
|
||||
webSocketService = new WebSocketService(httpServer);
|
||||
logStartup('WebSocket service initialized', {
|
||||
chatInactivityTimeout: process.env.CHAT_INACTIVITY_TIMEOUT_MINUTES || '30'
|
||||
});
|
||||
|
||||
// Initialize Game WebSocket service for /game namespace via DIContainer
|
||||
container.setSocketIO(webSocketService['io']);
|
||||
gameWebSocketService = container.gameWebSocketService;
|
||||
logStartup('Game WebSocket service initialized for /game namespace');
|
||||
|
||||
// Restore active games from snapshots (if any exist)
|
||||
gameWebSocketService.restoreAllActiveGames()
|
||||
.then(restoredCount => {
|
||||
if (restoredCount > 0) {
|
||||
logStartup(`Restored ${restoredCount} active game(s) from snapshots`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logError('Failed to restore games from snapshots', error);
|
||||
});
|
||||
|
||||
// Start server with WebSocket support AFTER database is ready
|
||||
server = httpServer.listen(PORT, () => {
|
||||
logStartup('Server started successfully', {
|
||||
port: PORT,
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
timestamp: new Date().toISOString(),
|
||||
endpoints: {
|
||||
health: `/health`,
|
||||
swagger: `/api-docs`,
|
||||
users: `/api/users`,
|
||||
organizations: `/api/organizations`,
|
||||
decks: `/api/decks`,
|
||||
chats: `/api/chats`
|
||||
},
|
||||
websocket: {
|
||||
enabled: true,
|
||||
chatInactivityTimeout: `${process.env.CHAT_INACTIVITY_TIMEOUT_MINUTES || '30'} minutes`
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
const dbOptions = AppDataSource.options as any;
|
||||
logConnection('Database connection failed', 'postgresql', 'failure', {
|
||||
error: error.message,
|
||||
type: dbOptions.type,
|
||||
host: dbOptions.host,
|
||||
database: dbOptions.database
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const gracefulShutdown = async (signal: string) => {
|
||||
logStartup(`Received ${signal}. Shutting down gracefully...`);
|
||||
|
||||
// Snapshot all active games before shutdown
|
||||
if (gameWebSocketService) {
|
||||
try {
|
||||
const snapshotCount = await gameWebSocketService.snapshotAllActiveGames();
|
||||
logStartup(`Created ${snapshotCount} game snapshot(s) before shutdown`);
|
||||
} catch (error) {
|
||||
logError('Failed to snapshot games before shutdown', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
server.close(() => {
|
||||
logStartup('HTTP server closed');
|
||||
|
||||
if (AppDataSource.isInitialized) {
|
||||
AppDataSource.destroy()
|
||||
.then(() => {
|
||||
logConnection('Database connection closed', 'postgresql', 'success');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
logError('Error during database shutdown', error);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
|
||||
// Handle uncaught exceptions
|
||||
process.on('uncaughtException', (error) => {
|
||||
logError('Uncaught Exception - Server will shut down', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logError('Unhandled Rejection - Server will shut down', new Error(String(reason)), undefined, undefined);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Export WebSocket services for game integration
|
||||
export { webSocketService, gameWebSocketService };
|
||||
@@ -1,422 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import * as Minio from 'minio';
|
||||
|
||||
export enum LogLevel {
|
||||
REQUEST = 'REQUEST',
|
||||
ERROR = 'ERROR',
|
||||
WARNING = 'WARNING',
|
||||
AUTH = 'AUTH',
|
||||
DATABASE = 'DATABASE',
|
||||
STARTUP = 'STARTUP',
|
||||
CONNECTION = 'CONNECTION',
|
||||
OTHER = 'OTHER'
|
||||
}
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string;
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
metadata?: any;
|
||||
requestId?: string;
|
||||
userId?: string;
|
||||
ip?: string;
|
||||
userAgent?: string;
|
||||
method?: string;
|
||||
url?: string;
|
||||
statusCode?: number;
|
||||
responseTime?: number;
|
||||
}
|
||||
|
||||
export class LoggingService {
|
||||
private static instance: LoggingService;
|
||||
private minioClient: Minio.Client | null = null;
|
||||
private logBuffer: LogEntry[] = [];
|
||||
private currentLogFile: string | null = null;
|
||||
private logCount = 0;
|
||||
private readonly maxLogsPerFile = parseInt(process.env.MAX_LOGS_PER_FILE || '10000');
|
||||
private readonly logsDir = path.join(process.cwd(), 'logs');
|
||||
private readonly bucketName = process.env.MINIO_BUCKET_NAME || 'serpentrace-logs';
|
||||
private uploadInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
private constructor() {
|
||||
this.initializeLogsDirectory();
|
||||
this.initializeMinioClient();
|
||||
this.createNewLogFile();
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
this.startPeriodicUpload();
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => this.shutdown());
|
||||
process.on('SIGINT', () => this.shutdown());
|
||||
process.on('beforeExit', () => this.shutdown());
|
||||
}
|
||||
|
||||
static getInstance(): LoggingService {
|
||||
if (!LoggingService.instance) {
|
||||
LoggingService.instance = new LoggingService();
|
||||
}
|
||||
return LoggingService.instance;
|
||||
}
|
||||
|
||||
private initializeLogsDirectory(): void {
|
||||
try {
|
||||
if (!fs.existsSync(this.logsDir)) {
|
||||
fs.mkdirSync(this.logsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create monthly subdirectory
|
||||
const monthlyDir = this.getMonthlyDirectory();
|
||||
if (!fs.existsSync(monthlyDir)) {
|
||||
fs.mkdirSync(monthlyDir, { recursive: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize logs directory:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private initializeMinioClient(): void {
|
||||
try {
|
||||
// Check if in production or development
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY) {
|
||||
this.minioClient = new Minio.Client({
|
||||
endPoint: process.env.MINIO_ENDPOINT,
|
||||
port: parseInt(process.env.MINIO_PORT || '9000'),
|
||||
useSSL: process.env.MINIO_USE_SSL === 'true',
|
||||
accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
secretKey: process.env.MINIO_SECRET_KEY
|
||||
});
|
||||
|
||||
this.ensureBucketExists().catch(error => {
|
||||
console.warn('MinIO bucket initialization failed:', error.message);
|
||||
});
|
||||
} else {
|
||||
console.warn('Minio configuration not found. Logs will only be stored locally and in console.');
|
||||
}
|
||||
} else {
|
||||
// Development mode - only use MinIO if explicitly configured
|
||||
if (process.env.MINIO_ENDPOINT || process.env.ENABLE_MINIO === 'true') {
|
||||
this.minioClient = new Minio.Client({
|
||||
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
|
||||
port: parseInt(process.env.MINIO_PORT || '9000'),
|
||||
useSSL: false,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || 'serpentrace',
|
||||
secretKey: process.env.MINIO_SECRET_KEY || 'serpentrace123!'
|
||||
});
|
||||
|
||||
this.ensureBucketExists().catch(error => {
|
||||
console.warn('MinIO bucket initialization failed:', error.message);
|
||||
});
|
||||
} else {
|
||||
console.log('Development mode: MinIO disabled. Set ENABLE_MINIO=true to enable MinIO logging.');
|
||||
this.minioClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Minio client:', error);
|
||||
this.minioClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureBucketExists(): Promise<void> {
|
||||
if (!this.minioClient) return;
|
||||
|
||||
try {
|
||||
const exists = await this.minioClient.bucketExists(this.bucketName);
|
||||
if (!exists) {
|
||||
await this.minioClient.makeBucket(this.bucketName);
|
||||
this.log(LogLevel.STARTUP, `Created Minio bucket: ${this.bucketName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('MinIO connection failed - disabling MinIO logging:', (error as Error).message);
|
||||
// Disable MinIO client if connection fails
|
||||
this.minioClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
private startPeriodicUpload(): void {
|
||||
// Upload current log file to Minio every 2 minutes
|
||||
this.uploadInterval = setInterval(async () => {
|
||||
if (this.currentLogFile && this.minioClient) {
|
||||
await this.uploadToMinio(this.currentLogFile);
|
||||
}
|
||||
}, 2 * 60 * 1000); // 2 minutes
|
||||
}
|
||||
|
||||
private getMonthlyDirectory(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
return path.join(this.logsDir, `${year}-${month}`);
|
||||
}
|
||||
|
||||
private getMonthlyMinioPrefix(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
return `${year}-${month}/`;
|
||||
}
|
||||
|
||||
private createNewLogFile(): void {
|
||||
const now = new Date();
|
||||
const timestamp = now.toISOString().replace(/[:.]/g, '-');
|
||||
const fileName = `serpentrace-${timestamp}.log`;
|
||||
|
||||
this.currentLogFile = path.join(this.getMonthlyDirectory(), fileName);
|
||||
this.logCount = 0;
|
||||
|
||||
// Write log file header
|
||||
const header = `# SerpentRace Backend Logs\n# Started: ${now.toISOString()}\n# Max entries per file: ${this.maxLogsPerFile}\n\n`;
|
||||
try {
|
||||
fs.writeFileSync(this.currentLogFile, header);
|
||||
} catch (error) {
|
||||
console.error('Failed to create log file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatLogEntry(entry: LogEntry): string {
|
||||
const parts = [
|
||||
entry.timestamp,
|
||||
`[${entry.level}]`,
|
||||
entry.message
|
||||
];
|
||||
|
||||
if (entry.requestId) parts.push(`ReqId:${entry.requestId}`);
|
||||
if (entry.userId) parts.push(`UserId:${entry.userId}`);
|
||||
if (entry.ip) parts.push(`IP:${entry.ip}`);
|
||||
if (entry.method && entry.url) parts.push(`${entry.method} ${entry.url}`);
|
||||
if (entry.statusCode) parts.push(`Status:${entry.statusCode}`);
|
||||
if (entry.responseTime) parts.push(`Time:${entry.responseTime}ms`);
|
||||
if (entry.userAgent) parts.push(`UA:${entry.userAgent.substring(0, 50)}`);
|
||||
if (entry.metadata) parts.push(`Meta:${JSON.stringify(entry.metadata)}`);
|
||||
|
||||
return parts.join(' | ');
|
||||
}
|
||||
|
||||
private async writeToLocalFile(entry: LogEntry): Promise<void> {
|
||||
if (!this.currentLogFile) return;
|
||||
|
||||
try {
|
||||
const logLine = this.formatLogEntry(entry) + '\n';
|
||||
fs.appendFileSync(this.currentLogFile, logLine);
|
||||
|
||||
this.logCount++;
|
||||
|
||||
// Check if we need to rotate the log file
|
||||
if (this.logCount >= this.maxLogsPerFile) {
|
||||
await this.rotateLogFile();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to write to log file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async rotateLogFile(): Promise<void> {
|
||||
if (!this.currentLogFile) return;
|
||||
|
||||
try {
|
||||
// Upload current file to Minio before rotating
|
||||
await this.uploadToMinio(this.currentLogFile);
|
||||
|
||||
// Create new log file
|
||||
this.createNewLogFile();
|
||||
|
||||
this.log(LogLevel.OTHER, 'Log file rotated due to size limit');
|
||||
} catch (error) {
|
||||
console.error('Failed to rotate log file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadToMinio(filePath: string): Promise<void> {
|
||||
if (!this.minioClient) {
|
||||
console.warn('Minio client not initialized, skipping upload');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.warn(`Log file does not exist: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileName = path.basename(filePath);
|
||||
const objectName = this.getMonthlyMinioPrefix() + fileName;
|
||||
|
||||
console.log(`Attempting to upload log file to Minio: ${objectName}`);
|
||||
await this.minioClient.fPutObject(this.bucketName, objectName, filePath);
|
||||
console.log(`Successfully uploaded log file to Minio: ${objectName}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to upload to Minio:', error);
|
||||
console.error('Minio config:', {
|
||||
endpoint: this.minioClient ? 'configured' : 'not configured',
|
||||
bucket: this.bucketName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private logToConsole(entry: LogEntry): void {
|
||||
// In production, skip OTHER, CONNECTION, and REQUEST logs
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (entry.level === LogLevel.OTHER ||
|
||||
entry.level === LogLevel.REQUEST) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const formattedEntry = this.formatLogEntry(entry);
|
||||
|
||||
switch (entry.level) {
|
||||
case LogLevel.ERROR:
|
||||
console.error(formattedEntry);
|
||||
break;
|
||||
case LogLevel.WARNING:
|
||||
console.warn(formattedEntry);
|
||||
break;
|
||||
case LogLevel.REQUEST:
|
||||
case LogLevel.AUTH:
|
||||
case LogLevel.DATABASE:
|
||||
case LogLevel.CONNECTION:
|
||||
console.info(formattedEntry);
|
||||
break;
|
||||
case LogLevel.STARTUP:
|
||||
console.log(formattedEntry);
|
||||
break;
|
||||
default:
|
||||
console.log(formattedEntry);
|
||||
}
|
||||
}
|
||||
|
||||
public log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
metadata?: any,
|
||||
req?: Request,
|
||||
res?: Response,
|
||||
responseTime?: number
|
||||
): void {
|
||||
// In production, skip OTHER, CONNECTION, and REQUEST logs entirely
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (level === LogLevel.OTHER ||
|
||||
level === LogLevel.CONNECTION ||
|
||||
level === LogLevel.REQUEST) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const entry: LogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
message,
|
||||
metadata
|
||||
};
|
||||
|
||||
// Add request context if available
|
||||
if (req) {
|
||||
entry.requestId = (req as any).requestId || this.generateRequestId();
|
||||
entry.userId = (req as any).user?.userId;
|
||||
entry.ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
||||
entry.userAgent = req.get ? req.get('User-Agent') : 'unknown';
|
||||
entry.method = req.method;
|
||||
entry.url = req.originalUrl || req.url;
|
||||
}
|
||||
|
||||
if (res) {
|
||||
entry.statusCode = res.statusCode;
|
||||
}
|
||||
|
||||
if (responseTime !== undefined) {
|
||||
entry.responseTime = responseTime;
|
||||
}
|
||||
|
||||
// Log to all three destinations
|
||||
this.logToConsole(entry);
|
||||
this.writeToLocalFile(entry);
|
||||
|
||||
// Add to buffer for potential batch processing
|
||||
this.logBuffer.push(entry);
|
||||
|
||||
// Limit buffer size
|
||||
if (this.logBuffer.length > 1000) {
|
||||
this.logBuffer = this.logBuffer.slice(-500);
|
||||
}
|
||||
}
|
||||
|
||||
private generateRequestId(): string {
|
||||
return Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
public async shutdown(): Promise<void> {
|
||||
try {
|
||||
// Clear the upload interval
|
||||
if (this.uploadInterval) {
|
||||
clearInterval(this.uploadInterval);
|
||||
this.uploadInterval = null;
|
||||
}
|
||||
|
||||
// Upload current log file to Minio
|
||||
if (this.currentLogFile) {
|
||||
await this.uploadToMinio(this.currentLogFile);
|
||||
}
|
||||
|
||||
this.log(LogLevel.STARTUP, 'Logging service shutting down gracefully');
|
||||
|
||||
// Give time for final logs to be written
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
} catch (error) {
|
||||
console.error('Error during logging service shutdown:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware factory methods
|
||||
public requestLoggingMiddleware() {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Generate request ID
|
||||
(req as any).requestId = this.generateRequestId();
|
||||
|
||||
// Log request start
|
||||
this.log(LogLevel.REQUEST, `Incoming request`, undefined, req);
|
||||
|
||||
// Override res.end to log response
|
||||
const originalEnd = res.end.bind(res);
|
||||
res.end = (...args: any[]): Response => {
|
||||
const responseTime = Date.now() - startTime;
|
||||
LoggingService.getInstance().log(
|
||||
LogLevel.REQUEST,
|
||||
`Request completed`,
|
||||
undefined,
|
||||
req,
|
||||
res,
|
||||
responseTime
|
||||
);
|
||||
return originalEnd(...args);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
public errorLoggingMiddleware() {
|
||||
return (error: Error, req: Request, res: Response, next: NextFunction) => {
|
||||
this.log(
|
||||
LogLevel.ERROR,
|
||||
`Unhandled error: ${error.message}`,
|
||||
{
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
},
|
||||
req,
|
||||
res
|
||||
);
|
||||
next(error);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default LoggingService;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,120 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
# SerpentRace Production Server Environment Variables
|
||||
# IMPORTANT: Change all placeholder values before deployment!
|
||||
|
||||
# Production settings
|
||||
NODE_ENV=production
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST=postgres
|
||||
DB_PORT=5432
|
||||
DB_NAME=serpentrace
|
||||
DB_USERNAME=postgres
|
||||
# CHANGE THIS: Use a strong password
|
||||
POSTGRES_PASSWORD=CHANGE_THIS_STRONG_DATABASE_PASSWORD_123!
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://redis:6379
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
# CHANGE THIS: Set a Redis password for security
|
||||
REDIS_PASSWORD=CHANGE_THIS_REDIS_PASSWORD_123!
|
||||
|
||||
# JWT Configuration
|
||||
# CHANGE THIS: Use a strong secret key (minimum 32 characters)
|
||||
JWT_SECRET=CHANGE_THIS_JWT_SECRET_KEY_MINIMUM_32_CHARACTERS_FOR_PRODUCTION_SECURITY
|
||||
JWT_EXPIRY=86400
|
||||
JWT_EXPIRATION=24h
|
||||
JWT_REFRESH_EXPIRATION=7d
|
||||
|
||||
# Email Configuration (SMTP)
|
||||
# CHANGE THESE: Configure your email provider
|
||||
EMAIL_HOST=smtp.yourmailprovider.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_SECURE=false
|
||||
EMAIL_USER=your_email@yourdomain.com
|
||||
EMAIL_PASS=your_email_password
|
||||
EMAIL_FROM="SerpentRace <noreply@yourdomain.com>"
|
||||
|
||||
# MinIO Object Storage
|
||||
MINIO_ENDPOINT=minio
|
||||
MINIO_PORT=9000
|
||||
MINIO_USE_SSL=false
|
||||
# CHANGE THESE: Use strong credentials
|
||||
MINIO_ACCESS_KEY=serpentrace_admin
|
||||
MINIO_SECRET_KEY=CHANGE_THIS_MINIO_SECRET_KEY_123!
|
||||
MINIO_BUCKET_NAME=serpentrace-logs
|
||||
|
||||
# Application Settings
|
||||
APP_BASE_URL=http://your-domain.com
|
||||
PORT=3000
|
||||
|
||||
# Chat System Limits
|
||||
CHAT_INACTIVITY_TIMEOUT_MINUTES=30
|
||||
CHAT_MAX_MESSAGES_PER_USER=100
|
||||
CHAT_MESSAGE_CLEANUP_WEEKS=4
|
||||
|
||||
# Logging
|
||||
MAX_LOGS_PER_FILE=10000
|
||||
|
||||
# SSL/TLS Configuration (if using HTTPS)
|
||||
# Uncomment and configure if you have SSL certificates
|
||||
# SSL_CERT_PATH=/path/to/certificate.crt
|
||||
# SSL_KEY_PATH=/path/to/private.key
|
||||
# SSL_CA_PATH=/path/to/ca-bundle.crt
|
||||
|
||||
# Security Headers (already configured in nginx)
|
||||
# These are handled by the nginx configuration
|
||||
|
||||
# Backup Configuration (optional)
|
||||
# BACKUP_ENABLED=true
|
||||
# BACKUP_SCHEDULE=0 2 * * *
|
||||
# BACKUP_RETENTION_DAYS=30
|
||||
@@ -1,203 +0,0 @@
|
||||
# SerpentRace Production Deployment Guide
|
||||
|
||||
## Overview
|
||||
This package contains everything needed to deploy SerpentRace in a production environment using pre-built Docker images.
|
||||
|
||||
## Package Contents
|
||||
- `serpentRaceDocker.tar` - All Docker images packed for deployment
|
||||
- `docker-compose.deploy.yml` - Production Docker Compose configuration
|
||||
- `.env.server` - Environment variables template for production
|
||||
- `load-images.bat` - Automated deployment script for Windows servers
|
||||
- `README.md` - This deployment guide
|
||||
|
||||
## System Requirements
|
||||
- Windows Server 2016+ or Windows 10/11
|
||||
- Docker Desktop or Docker Engine
|
||||
- Docker Compose
|
||||
- Minimum 4GB RAM, 20GB free disk space
|
||||
- Network ports: 80, 443, 3000, 5432, 6379, 9000, 9001
|
||||
|
||||
## Pre-Deployment Configuration
|
||||
|
||||
### 1. Environment Variables
|
||||
Edit `.env.server` and update the following **REQUIRED** settings:
|
||||
|
||||
```bash
|
||||
# Database - Use a strong password
|
||||
POSTGRES_PASSWORD=your_strong_database_password
|
||||
|
||||
# JWT Security - Use a random 32+ character string
|
||||
JWT_SECRET=your_super_secret_jwt_key_32_chars_minimum
|
||||
|
||||
# Redis Security
|
||||
REDIS_PASSWORD=your_redis_password
|
||||
|
||||
# MinIO Storage
|
||||
MINIO_ACCESS_KEY=your_minio_admin_user
|
||||
MINIO_SECRET_KEY=your_minio_secret_key
|
||||
|
||||
# Email Configuration (for notifications)
|
||||
EMAIL_HOST=smtp.yourmailprovider.com
|
||||
EMAIL_USER=your_email@yourdomain.com
|
||||
EMAIL_PASS=your_email_password
|
||||
EMAIL_FROM="SerpentRace <noreply@yourdomain.com>"
|
||||
|
||||
# Application URL
|
||||
APP_BASE_URL=http://your-domain.com
|
||||
```
|
||||
|
||||
### 2. Security Checklist
|
||||
- [ ] Changed all default passwords
|
||||
- [ ] Generated strong JWT secret (32+ characters)
|
||||
- [ ] Configured email settings
|
||||
- [ ] Updated domain name in APP_BASE_URL
|
||||
- [ ] Configured firewall rules
|
||||
- [ ] Planned SSL certificate setup
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### Automatic Deployment (Recommended)
|
||||
1. Extract all files to your server directory
|
||||
2. Edit `.env.server` with your configuration
|
||||
3. Run `load-images.bat`
|
||||
4. Follow the prompts
|
||||
|
||||
### Manual Deployment
|
||||
1. Load Docker images:
|
||||
```cmd
|
||||
docker load -i serpentRaceDocker.tar
|
||||
```
|
||||
|
||||
2. Start services:
|
||||
```cmd
|
||||
docker-compose -f docker-compose.deploy.yml --env-file .env.server up -d
|
||||
```
|
||||
|
||||
## Post-Deployment
|
||||
|
||||
### Verify Services
|
||||
Check that all services are running:
|
||||
```cmd
|
||||
docker-compose -f docker-compose.deploy.yml ps
|
||||
```
|
||||
|
||||
### Access Points
|
||||
- **Frontend Application**: http://localhost (or your domain)
|
||||
- **Backend API**: http://localhost:3000
|
||||
- **MinIO Console**: http://localhost:9001
|
||||
- **Database**: localhost:5432 (internal access only)
|
||||
|
||||
### Initial Setup
|
||||
1. Access the frontend and verify it loads
|
||||
2. Test user registration and login
|
||||
3. Check backend API health: http://localhost:3000/health
|
||||
4. Access MinIO console to verify storage
|
||||
|
||||
### Security Hardening
|
||||
|
||||
#### Firewall Configuration
|
||||
Open only necessary ports:
|
||||
- Port 80 (HTTP) - Public
|
||||
- Port 443 (HTTPS) - Public (when SSL configured)
|
||||
- Ports 3000, 5432, 6379, 9000, 9001 - Internal/VPN only
|
||||
|
||||
#### SSL/TLS Setup
|
||||
1. Obtain SSL certificates (Let's Encrypt, commercial CA)
|
||||
2. Configure nginx for HTTPS in the frontend container
|
||||
3. Update APP_BASE_URL to use https://
|
||||
|
||||
#### Regular Maintenance
|
||||
- Monitor logs: `docker-compose -f docker-compose.deploy.yml logs -f`
|
||||
- Update images periodically
|
||||
- Backup database and MinIO data
|
||||
- Monitor disk space and performance
|
||||
|
||||
## Management Commands
|
||||
|
||||
### View Logs
|
||||
```cmd
|
||||
# All services
|
||||
docker-compose -f docker-compose.deploy.yml logs -f
|
||||
|
||||
# Specific service
|
||||
docker-compose -f docker-compose.deploy.yml logs -f backend
|
||||
```
|
||||
|
||||
### Restart Services
|
||||
```cmd
|
||||
# Restart all
|
||||
docker-compose -f docker-compose.deploy.yml restart
|
||||
|
||||
# Restart specific service
|
||||
docker-compose -f docker-compose.deploy.yml restart backend
|
||||
```
|
||||
|
||||
### Stop Services
|
||||
```cmd
|
||||
docker-compose -f docker-compose.deploy.yml down
|
||||
```
|
||||
|
||||
### Update Deployment
|
||||
1. Stop current services
|
||||
2. Load new images
|
||||
3. Start services with new configuration
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Database Backup
|
||||
```cmd
|
||||
docker exec serpentrace-postgres pg_dump -U postgres serpentrace > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### Complete Backup
|
||||
```cmd
|
||||
# Stop services
|
||||
docker-compose -f docker-compose.deploy.yml down
|
||||
|
||||
# Backup volumes
|
||||
docker run --rm -v postgres_data:/data -v %cd%:/backup ubuntu tar czf /backup/postgres_backup.tar.gz -C /data .
|
||||
docker run --rm -v minio_data:/data -v %cd%:/backup ubuntu tar czf /backup/minio_backup.tar.gz -C /data .
|
||||
|
||||
# Restart services
|
||||
docker-compose -f docker-compose.deploy.yml up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Services Not Starting
|
||||
1. Check Docker is running
|
||||
2. Verify port availability
|
||||
3. Check environment variables
|
||||
4. Review logs for specific errors
|
||||
|
||||
#### Database Connection Issues
|
||||
1. Verify POSTGRES_PASSWORD matches in .env.server
|
||||
2. Check database container is healthy
|
||||
3. Ensure network connectivity
|
||||
|
||||
#### Frontend Not Loading
|
||||
1. Check nginx container status
|
||||
2. Verify backend API is responding
|
||||
3. Check browser console for errors
|
||||
|
||||
#### Performance Issues
|
||||
1. Monitor resource usage: `docker stats`
|
||||
2. Check available disk space
|
||||
3. Review application logs
|
||||
4. Consider scaling if needed
|
||||
|
||||
### Getting Help
|
||||
- Check application logs for specific error messages
|
||||
- Verify all environment variables are set correctly
|
||||
- Ensure all required ports are available
|
||||
- Contact support with log files and configuration details
|
||||
|
||||
## Version Information
|
||||
- SerpentRace Backend: Latest
|
||||
- Frontend: Latest
|
||||
- PostgreSQL: 15-alpine
|
||||
- Redis: 7-alpine
|
||||
- MinIO: Latest
|
||||
- Nginx: Alpine
|
||||
@@ -1,149 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Backend service using pre-built image
|
||||
backend:
|
||||
image: serpentrace_docker-backend:latest
|
||||
container_name: serpentrace-backend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- DB_HOST=postgres
|
||||
- DB_PORT=5432
|
||||
- DB_NAME=serpentrace
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- JWT_EXPIRATION=${JWT_EXPIRATION:-24h}
|
||||
- JWT_REFRESH_EXPIRATION=${JWT_REFRESH_EXPIRATION:-7d}
|
||||
- MINIO_ENDPOINT=minio
|
||||
- MINIO_PORT=9000
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
- MINIO_USE_SSL=false
|
||||
- EMAIL_HOST=${EMAIL_HOST}
|
||||
- EMAIL_PORT=${EMAIL_PORT}
|
||||
- EMAIL_SECURE=${EMAIL_SECURE}
|
||||
- EMAIL_USER=${EMAIL_USER}
|
||||
- EMAIL_PASS=${EMAIL_PASS}
|
||||
- EMAIL_FROM=${EMAIL_FROM}
|
||||
volumes:
|
||||
- backend_logs:/app/logs
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# Frontend service using pre-built image
|
||||
frontend:
|
||||
image: serpentrace_docker-frontend:latest
|
||||
container_name: serpentrace-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# PostgreSQL Database
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: serpentrace-postgres
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: serpentrace
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./sql_schema_only.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis Cache
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: serpentrace-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# MinIO Object Storage
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: serpentrace-minio
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
minio_data:
|
||||
driver: local
|
||||
backend_logs:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
serpentrace-network:
|
||||
driver: bridge
|
||||
@@ -1,103 +0,0 @@
|
||||
@echo off
|
||||
REM SerpentRace Production Deployment Script
|
||||
REM This script loads Docker images and starts the production environment
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
echo ===============================================
|
||||
echo SerpentRace Production Deployment
|
||||
echo ===============================================
|
||||
echo.
|
||||
|
||||
REM Check if Docker is installed
|
||||
where docker >nul 2>nul
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Docker is not installed. Please install Docker first.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where docker-compose >nul 2>nul
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Docker Compose is not installed. Please install Docker Compose first.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if serpentRaceDocker.tar exists
|
||||
if not exist "serpentRaceDocker.tar" (
|
||||
echo [ERROR] serpentRaceDocker.tar not found!
|
||||
echo Please ensure the tar file is in the same directory as this script.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if environment file exists
|
||||
if not exist ".env.server" (
|
||||
echo [ERROR] .env.server file not found!
|
||||
echo Please ensure the environment file is configured.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [INFO] Loading Docker images from serpentRaceDocker.tar...
|
||||
docker load -i serpentRaceDocker.tar
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Failed to load Docker images!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [INFO] Images loaded successfully!
|
||||
echo.
|
||||
|
||||
REM Show loaded images
|
||||
echo [INFO] Loaded images:
|
||||
docker images | findstr serpentrace
|
||||
docker images | findstr postgres
|
||||
docker images | findstr redis
|
||||
docker images | findstr minio
|
||||
echo.
|
||||
|
||||
echo [WARNING] Before starting the services, please review and update .env.server:
|
||||
echo - Change all placeholder passwords
|
||||
echo - Configure email settings
|
||||
echo - Update domain names
|
||||
echo - Set strong JWT secret
|
||||
echo.
|
||||
echo Press any key to continue with deployment or Ctrl+C to exit...
|
||||
pause >nul
|
||||
|
||||
echo [INFO] Starting production services...
|
||||
docker-compose -f docker-compose.deploy.yml --env-file .env.server up -d
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Failed to start services!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ===============================================
|
||||
echo Deployment Complete!
|
||||
echo ===============================================
|
||||
echo.
|
||||
echo Services are starting up. Please wait a few moments for all services to be ready.
|
||||
echo.
|
||||
echo Available services:
|
||||
echo - Frontend: http://localhost (or your domain)
|
||||
echo - Backend API: http://localhost:3000
|
||||
echo - MinIO Console: http://localhost:9001
|
||||
echo.
|
||||
echo To check service status: docker-compose -f docker-compose.deploy.yml ps
|
||||
echo To view logs: docker-compose -f docker-compose.deploy.yml logs -f [service_name]
|
||||
echo To stop services: docker-compose -f docker-compose.deploy.yml down
|
||||
echo.
|
||||
echo IMPORTANT SECURITY NOTES:
|
||||
echo 1. Change all default passwords in .env.server
|
||||
echo 2. Configure firewall rules for your server
|
||||
echo 3. Set up SSL/TLS certificates for HTTPS
|
||||
echo 4. Configure regular backups
|
||||
echo 5. Monitor logs and system resources
|
||||
echo.
|
||||
pause
|
||||
@@ -1,81 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SerpentRace Production Deployment Script for Linux
|
||||
# This script loads Docker images and starts the production environment
|
||||
|
||||
set -e
|
||||
|
||||
echo "==============================================="
|
||||
echo "SerpentRace Production Deployment"
|
||||
echo "==============================================="
|
||||
echo
|
||||
|
||||
# Check if Docker is installed
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "[ERROR] Docker is not installed. Please install Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
echo "[ERROR] Docker Compose is not installed. Please install Docker Compose first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if serpentRaceDocker.tar exists
|
||||
if [ ! -f "serpentRaceDocker.tar" ]; then
|
||||
echo "[ERROR] serpentRaceDocker.tar not found!"
|
||||
echo "Please ensure the tar file is in the same directory as this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if environment file exists
|
||||
if [ ! -f ".env.server" ]; then
|
||||
echo "[ERROR] .env.server file not found!"
|
||||
echo "Please ensure the environment file is configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INFO] Loading Docker images from serpentRaceDocker.tar..."
|
||||
docker load -i serpentRaceDocker.tar
|
||||
|
||||
echo "[INFO] Images loaded successfully!"
|
||||
echo
|
||||
|
||||
# Show loaded images
|
||||
echo "[INFO] Loaded images:"
|
||||
docker images | grep -E "(serpentrace|postgres|redis|minio)"
|
||||
echo
|
||||
|
||||
echo "[WARNING] Before starting the services, please review and update .env.server:"
|
||||
echo " - Change all placeholder passwords"
|
||||
echo " - Configure email settings"
|
||||
echo " - Update domain names"
|
||||
echo " - Set strong JWT secret"
|
||||
echo
|
||||
read -p "Press Enter to continue with deployment or Ctrl+C to exit..."
|
||||
|
||||
echo "[INFO] Starting production services..."
|
||||
docker-compose -f docker-compose.deploy.yml --env-file .env.server up -d
|
||||
|
||||
echo
|
||||
echo "==============================================="
|
||||
echo "Deployment Complete!"
|
||||
echo "==============================================="
|
||||
echo
|
||||
echo "Services are starting up. Please wait a few moments for all services to be ready."
|
||||
echo
|
||||
echo "Available services:"
|
||||
echo " - Frontend: http://localhost (or your domain)"
|
||||
echo " - Backend API: http://localhost:3000"
|
||||
echo " - MinIO Console: http://localhost:9001"
|
||||
echo
|
||||
echo "To check service status: docker-compose -f docker-compose.deploy.yml ps"
|
||||
echo "To view logs: docker-compose -f docker-compose.deploy.yml logs -f [service_name]"
|
||||
echo "To stop services: docker-compose -f docker-compose.deploy.yml down"
|
||||
echo
|
||||
echo "IMPORTANT SECURITY NOTES:"
|
||||
echo "1. Change all default passwords in .env.server"
|
||||
echo "2. Configure firewall rules for your server"
|
||||
echo "3. Set up SSL/TLS certificates for HTTPS"
|
||||
echo "4. Configure regular backups"
|
||||
echo "5. Monitor logs and system resources"
|
||||
echo
|
||||
@@ -1,60 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
|
||||
# Enable gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Handle client routing
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API proxy to backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# WebSocket support
|
||||
location /socket.io/ {
|
||||
proxy_pass http://backend:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Static assets caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"Servers": {
|
||||
"1": {
|
||||
"Name": "SerpentRace Production",
|
||||
"Group": "Servers",
|
||||
"Host": "postgres",
|
||||
"Port": 5432,
|
||||
"MaintenanceDB": "serpentrace",
|
||||
"Username": "postgres",
|
||||
"SSLMode": "prefer",
|
||||
"Comment": "SerpentRace Production Database"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,236 +0,0 @@
|
||||
-- SerpentRace Database Schema
|
||||
-- Generated from TypeORM Entity Aggregates
|
||||
-- This file creates the complete database schema without initial data
|
||||
|
||||
-- Enable UUID extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Create Users table
|
||||
CREATE TABLE "Users" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"orgid" UUID NULL,
|
||||
"username" VARCHAR(100) UNIQUE NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"email" VARCHAR(255) UNIQUE NOT NULL,
|
||||
"fname" VARCHAR(100) NOT NULL,
|
||||
"lname" VARCHAR(100) NOT NULL,
|
||||
"token" VARCHAR(255) NULL,
|
||||
"TokenExpires" TIMESTAMP NULL,
|
||||
"phone" VARCHAR(20) NULL,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"regdate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"Orglogindate" TIMESTAMP NULL
|
||||
);
|
||||
|
||||
-- Create Organizations table
|
||||
CREATE TABLE "Organizations" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"contactfname" VARCHAR(100) NOT NULL,
|
||||
"contactlname" VARCHAR(100) NOT NULL,
|
||||
"contactphone" VARCHAR(20) NOT NULL,
|
||||
"contactemail" VARCHAR(255) NOT NULL,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"regdate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"url" VARCHAR(500) NULL,
|
||||
"userinorg" INTEGER NOT NULL DEFAULT 0,
|
||||
"maxOrganizationalDecks" INTEGER NULL
|
||||
);
|
||||
|
||||
-- Create Decks table
|
||||
CREATE TABLE "Decks" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"type" INTEGER NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"creation_date" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"cards" JSONB NOT NULL DEFAULT '[]',
|
||||
"played_number" INTEGER NOT NULL DEFAULT 0,
|
||||
"ctype" INTEGER NOT NULL DEFAULT 0,
|
||||
"update_date" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"organization_id" UUID NULL
|
||||
);
|
||||
|
||||
-- Create Chats table
|
||||
CREATE TABLE "Chats" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"type" VARCHAR(50) NOT NULL DEFAULT 'direct',
|
||||
"name" VARCHAR(255) NULL,
|
||||
"gameId" UUID NULL,
|
||||
"createdBy" UUID NULL,
|
||||
"users" UUID[] NOT NULL,
|
||||
"messages" JSONB NOT NULL DEFAULT '[]',
|
||||
"lastActivity" TIMESTAMP NULL,
|
||||
"createDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"archiveDate" TIMESTAMP NULL
|
||||
);
|
||||
|
||||
-- Create Contacts table
|
||||
CREATE TABLE "Contacts" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"email" VARCHAR(255) NOT NULL,
|
||||
"userid" UUID NULL,
|
||||
"type" INTEGER NOT NULL,
|
||||
"txt" TEXT NOT NULL,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"createDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"adminResponse" TEXT NULL,
|
||||
"responseDate" TIMESTAMP NULL,
|
||||
"respondedBy" UUID NULL
|
||||
);
|
||||
|
||||
-- Create Games table
|
||||
CREATE TABLE "Games" (
|
||||
"id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
"gamecode" VARCHAR(10) UNIQUE NOT NULL,
|
||||
"maxplayers" INTEGER NOT NULL,
|
||||
"logintype" INTEGER NOT NULL DEFAULT 0,
|
||||
"state" INTEGER NOT NULL DEFAULT 0,
|
||||
"playerids" UUID[] NOT NULL DEFAULT '{}',
|
||||
"decks" JSONB NOT NULL DEFAULT '[]',
|
||||
"boardsize" INTEGER NOT NULL DEFAULT 50,
|
||||
"createDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updateDate" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishDate" TIMESTAMP NULL,
|
||||
"winnerid" UUID NULL,
|
||||
"createdBy" UUID NOT NULL,
|
||||
"organizationid" UUID NULL
|
||||
);
|
||||
|
||||
-- Add Foreign Key Constraints
|
||||
ALTER TABLE "Users"
|
||||
ADD CONSTRAINT "FK_Users_Organizations"
|
||||
FOREIGN KEY ("orgid") REFERENCES "Organizations"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Decks"
|
||||
ADD CONSTRAINT "FK_Decks_Users"
|
||||
FOREIGN KEY ("user_id") REFERENCES "Users"("id") ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE "Decks"
|
||||
ADD CONSTRAINT "FK_Decks_Organizations"
|
||||
FOREIGN KEY ("organization_id") REFERENCES "Organizations"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Contacts"
|
||||
ADD CONSTRAINT "FK_Contacts_Users"
|
||||
FOREIGN KEY ("userid") REFERENCES "Users"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Contacts"
|
||||
ADD CONSTRAINT "FK_Contacts_RespondedBy"
|
||||
FOREIGN KEY ("respondedBy") REFERENCES "Users"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Chats"
|
||||
ADD CONSTRAINT "FK_Chats_CreatedBy"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "Users"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Chats"
|
||||
ADD CONSTRAINT "FK_Chats_Games"
|
||||
FOREIGN KEY ("gameId") REFERENCES "Games"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Games"
|
||||
ADD CONSTRAINT "FK_Games_CreatedBy"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "Users"("id") ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE "Games"
|
||||
ADD CONSTRAINT "FK_Games_Organizations"
|
||||
FOREIGN KEY ("organizationid") REFERENCES "Organizations"("id") ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE "Games"
|
||||
ADD CONSTRAINT "FK_Games_Winner"
|
||||
FOREIGN KEY ("winnerid") REFERENCES "Users"("id") ON DELETE SET NULL;
|
||||
|
||||
-- Create Indexes for Performance
|
||||
CREATE INDEX "IDX_Users_Username" ON "Users" ("username");
|
||||
CREATE INDEX "IDX_Users_Email" ON "Users" ("email");
|
||||
CREATE INDEX "IDX_Users_OrgId" ON "Users" ("orgid");
|
||||
CREATE INDEX "IDX_Users_State" ON "Users" ("state");
|
||||
|
||||
CREATE INDEX "IDX_Organizations_Name" ON "Organizations" ("name");
|
||||
CREATE INDEX "IDX_Organizations_State" ON "Organizations" ("state");
|
||||
|
||||
CREATE INDEX "IDX_Decks_UserId" ON "Decks" ("user_id");
|
||||
CREATE INDEX "IDX_Decks_Type" ON "Decks" ("type");
|
||||
CREATE INDEX "IDX_Decks_CType" ON "Decks" ("ctype");
|
||||
CREATE INDEX "IDX_Decks_State" ON "Decks" ("state");
|
||||
CREATE INDEX "IDX_Decks_OrganizationId" ON "Decks" ("organization_id");
|
||||
|
||||
CREATE INDEX "IDX_Chats_Type" ON "Chats" ("type");
|
||||
CREATE INDEX "IDX_Chats_State" ON "Chats" ("state");
|
||||
CREATE INDEX "IDX_Chats_GameId" ON "Chats" ("gameId");
|
||||
CREATE INDEX "IDX_Chats_CreatedBy" ON "Chats" ("createdBy");
|
||||
|
||||
CREATE INDEX "IDX_Contacts_Type" ON "Contacts" ("type");
|
||||
CREATE INDEX "IDX_Contacts_State" ON "Contacts" ("state");
|
||||
CREATE INDEX "IDX_Contacts_UserId" ON "Contacts" ("userid");
|
||||
|
||||
CREATE INDEX "IDX_Games_GameCode" ON "Games" ("gamecode");
|
||||
CREATE INDEX "IDX_Games_State" ON "Games" ("state");
|
||||
CREATE INDEX "IDX_Games_CreatedBy" ON "Games" ("createdBy");
|
||||
CREATE INDEX "IDX_Games_OrganizationId" ON "Games" ("organizationid");
|
||||
|
||||
-- Create update trigger for updatedate columns
|
||||
CREATE OR REPLACE FUNCTION update_updatedate_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updatedate = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Apply update triggers
|
||||
CREATE TRIGGER update_users_updatedate
|
||||
BEFORE UPDATE ON "Users"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
CREATE TRIGGER update_organizations_updatedate
|
||||
BEFORE UPDATE ON "Organizations"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
CREATE TRIGGER update_decks_updatedate
|
||||
BEFORE UPDATE ON "Decks"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
CREATE TRIGGER update_chats_updatedate
|
||||
BEFORE UPDATE ON "Chats"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
CREATE TRIGGER update_contacts_updatedate
|
||||
BEFORE UPDATE ON "Contacts"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
CREATE TRIGGER update_games_updatedate
|
||||
BEFORE UPDATE ON "Games"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updatedate_column();
|
||||
|
||||
-- Comments for documentation
|
||||
COMMENT ON TABLE "Users" IS 'User accounts with authentication and profile information';
|
||||
COMMENT ON TABLE "Organizations" IS 'Organizations that can have multiple users and premium features';
|
||||
COMMENT ON TABLE "Decks" IS 'Card decks for the game, can be public, private, or organizational';
|
||||
COMMENT ON TABLE "Chats" IS 'Chat system supporting direct messages, groups, and game chats';
|
||||
COMMENT ON TABLE "Contacts" IS 'Contact form submissions and support tickets';
|
||||
COMMENT ON TABLE "Games" IS 'Game sessions with players, decks, and game state';
|
||||
|
||||
-- Enum value comments
|
||||
COMMENT ON COLUMN "Users"."state" IS '0=REGISTERED_NOT_VERIFIED, 1=VERIFIED_REGULAR, 2=VERIFIED_PREMIUM, 3=SOFT_DELETE, 4=DEACTIVATED, 5=ADMIN';
|
||||
COMMENT ON COLUMN "Organizations"."state" IS '0=REGISTERED, 1=ACTIVE, 2=SOFT_DELETE';
|
||||
COMMENT ON COLUMN "Decks"."type" IS '0=LUCK, 1=JOKER, 2=QUESTION';
|
||||
COMMENT ON COLUMN "Decks"."ctype" IS '0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION';
|
||||
COMMENT ON COLUMN "Decks"."state" IS '0=ACTIVE, 1=SOFT_DELETE';
|
||||
COMMENT ON COLUMN "Chats"."type" IS 'direct, group, game';
|
||||
COMMENT ON COLUMN "Chats"."state" IS '0=ACTIVE, 1=ARCHIVE, 2=SOFT_DELETE';
|
||||
COMMENT ON COLUMN "Contacts"."type" IS '0=BUG, 1=PROBLEM, 2=QUESTION, 3=SALES, 4=OTHER';
|
||||
COMMENT ON COLUMN "Contacts"."state" IS '0=ACTIVE, 1=RESOLVED, 2=SOFT_DELETE';
|
||||
COMMENT ON COLUMN "Games"."state" IS '0=WAITING, 1=ACTIVE, 2=FINISHED, 3=CANCELLED';
|
||||
COMMENT ON COLUMN "Games"."logintype" IS '0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION';
|
||||
|
||||
-- Grant permissions for application user
|
||||
-- Note: Replace 'serpentrace_app' with your actual application database user
|
||||
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO serpentrace_app;
|
||||
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO serpentrace_app;
|
||||
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO serpentrace_app;
|
||||
@@ -3,36 +3,13 @@ version: '3.8'
|
||||
services:
|
||||
# Backend service using pre-built image
|
||||
backend:
|
||||
image: serpentrace_docker-backend:latest
|
||||
image: serpentrace-backend:latest
|
||||
container_name: serpentrace-backend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- DB_HOST=postgres
|
||||
- DB_PORT=5432
|
||||
- DB_NAME=serpentrace
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- JWT_EXPIRATION=${JWT_EXPIRATION:-24h}
|
||||
- JWT_REFRESH_EXPIRATION=${JWT_REFRESH_EXPIRATION:-7d}
|
||||
- MINIO_ENDPOINT=minio
|
||||
- MINIO_PORT=9000
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
- MINIO_USE_SSL=false
|
||||
- EMAIL_HOST=${EMAIL_HOST}
|
||||
- EMAIL_PORT=${EMAIL_PORT}
|
||||
- EMAIL_SECURE=${EMAIL_SECURE}
|
||||
- EMAIL_USER=${EMAIL_USER}
|
||||
- EMAIL_PASS=${EMAIL_PASS}
|
||||
- EMAIL_FROM=${EMAIL_FROM}
|
||||
env_file:
|
||||
- .env.server
|
||||
volumes:
|
||||
- backend_logs:/app/logs
|
||||
depends_on:
|
||||
@@ -44,20 +21,15 @@ services:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
tty: true
|
||||
|
||||
# Frontend service using pre-built image
|
||||
frontend:
|
||||
image: serpentrace_docker-frontend:latest
|
||||
image: serpentrace-frontend:latest
|
||||
container_name: serpentrace-frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
@@ -81,7 +53,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: serpentrace
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
@@ -103,7 +75,7 @@ services:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
|
||||
command: redis-server --appendonly yes
|
||||
networks:
|
||||
- serpentrace-network
|
||||
healthcheck:
|
||||
@@ -121,8 +93,8 @@ services:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
|
||||
MINIO_ROOT_USER: serpentrace
|
||||
MINIO_ROOT_PASSWORD: serpentrace123!
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user