Professional tournament management with real-time brackets
Tournament Manager is a comprehensive tournament management system built specifically for the Playtopia Dev Lab infrastructure. It provides complete tournament organization capabilities with support for multiple tournament formats, real-time bracket updates, and seamless integration with Authelia for authentication and user management.
Component | Technology | Purpose |
---|---|---|
Backend API | Express.js + TypeScript | Comprehensive REST API with validation and error handling |
Database | PostgreSQL 16 + Prisma ORM | Robust data management with type-safe database operations |
Frontend | React 18 + TypeScript + Material-UI | Modern responsive interface with component library |
Authentication | Authelia ForwardAuth Integration | Header-based user authentication with role mapping |
Real-time | Socket.io | Live tournament updates and bracket synchronization |
Deployment | Docker Multi-Container | Production containers with nginx and PostgreSQL |
SSL/Proxy | Traefik v3.0 | Automatic HTTPS with Let’s Encrypt certificate management |
# Tournament management interface
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# API proxy to backend
location /api {
proxy_pass http://backend:5000;
proxy_set_header Remote-User $http_remote_user;
proxy_set_header Remote-Email $http_remote_email;
proxy_set_header Remote-Name $http_remote_name;
proxy_set_header Remote-Groups $http_remote_groups;
}
# WebSocket support for real-time updates
location /socket.io/ {
proxy_pass http://backend:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Express.js API with Authelia integration
tournament-backend:
build: ./tournament-manager/backend
container_name: tournament-backend
environment:
- DATABASE_URL=postgresql://tournament_user:tournament_password@tournament-database:5432/tournament_db
- FRONTEND_URL=https://tournaments.playtopia.com.au
labels:
- traefik.http.routers.tournament-api.middlewares=authelia-with-headers
- traefik.http.routers.tournament-api.rule=Host(`tournaments.playtopia.com.au`) && PathPrefix(`/api`)
// Authelia ForwardAuth integration
interface AutheliaUser {
id: string;
name: string;
email: string;
username: string;
groups: string[];
role: 'ADMIN' | 'TOURNAMENT_ORGANIZER' | 'PARTICIPANT';
}
export const authenticateUser = async (req: Request, res: Response, next: NextFunction) => {
// Extract user from Authelia ForwardAuth headers
const remoteUser = req.headers['remote-user'] as string;
const remoteEmail = req.headers['remote-email'] as string;
const remoteName = req.headers['remote-name'] as string;
const remoteGroups = req.headers['remote-groups'] as string;
if (remoteUser && remoteEmail) {
// Map Authelia groups to tournament roles
const role = mapUserRole(remoteGroups ? remoteGroups.split(',') : ['users']);
const user: AutheliaUser = {
id: remoteUser,
name: remoteName || remoteEmail,
email: remoteEmail,
username: remoteUser,
groups: remoteGroups ? remoteGroups.split(',') : ['users'],
role
};
req.user = user;
return next();
}
res.status(401).json({ error: 'Authentication required' });
};
// Advanced tournament bracket generation
class TournamentService {
async generateMatches(tournament: Tournament) {
const { participants, tournamentType, bracketSize } = tournament;
switch (tournamentType) {
case 'SINGLE_ELIMINATION':
return this.generateSingleElimination(participants, bracketSize);
case 'DOUBLE_ELIMINATION':
return this.generateDoubleElimination(participants, bracketSize);
case 'ROUND_ROBIN':
return this.generateRoundRobin(participants);
case 'SWISS_SYSTEM':
return this.generateSwissSystem(participants);
}
}
async updateMatchResult(matchId: string, result: MatchResult) {
// Validate match result and update bracket
const match = await this.validateMatchResult(matchId, result);
const updatedMatch = await this.processMatchResult(match, result);
// Emit real-time update via Socket.io
this.io.to(`tournament_${match.tournamentId}`)
.emit('match_updated', {
matchId: updatedMatch.id,
match: updatedMatch,
timestamp: new Date().toISOString()
});
return updatedMatch;
}
}
model Tournament {
id String @id @default(cuid())
name String
description String?
tournamentType TournamentType
bracketSize Int
maxParticipants Int
status TournamentStatus @default(DRAFT)
registrationDeadline DateTime?
startDate DateTime?
endDate DateTime?
isPublic Boolean @default(true)
ruleset Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
creator User @relation("TournamentCreator", fields: [creatorId], references: [id])
creatorId String
participants Participant[]
matches Match[]
rules Rule[]
}
model Match {
id String @id @default(cuid())
roundNumber Int
matchNumber Int
bracketType BracketType @default(MAIN)
participant1Score Int?
participant2Score Int?
status MatchStatus @default(PENDING)
scheduledTime DateTime?
completedTime DateTime?
matchData Json?
tournament Tournament @relation(fields: [tournamentId], references: [id])
tournamentId String
participant1 Participant? @relation("Participant1", fields: [participant1Id], references: [id])
participant1Id String?
participant2 Participant? @relation("Participant2", fields: [participant2Id], references: [id])
participant2Id String?
winner Participant? @relation("MatchWinner", fields: [winnerId], references: [id])
winnerId String?
}
enum TournamentType {
SINGLE_ELIMINATION
DOUBLE_ELIMINATION
ROUND_ROBIN
SWISS_SYSTEM
}
/home/thrax/unified-services/tournament-manager/
├── backend/
│ ├── Dockerfile # Production Express.js container
│ ├── package.json # Node.js dependencies and scripts
│ ├── prisma/
│ │ ├── schema.prisma # Database schema definition
│ │ └── migrations/ # Database migration files
│ └── src/
│ ├── server.ts # Express server with Socket.io integration
│ ├── middleware/
│ │ └── auth.ts # Authelia ForwardAuth middleware
│ └── routes/
│ ├── auth.ts # Authentication endpoints
│ ├── tournaments.ts # Tournament CRUD operations
│ ├── participants.ts # Participant management
│ ├── matches.ts # Match results and bracket generation
│ ├── rules.ts # Tournament rules management
│ └── users.ts # User profiles and statistics
├── frontend/
│ ├── Dockerfile # Production React container (nginx)
│ ├── nginx.conf # API proxy and WebSocket configuration
│ ├── package.json # React dependencies
│ └── src/
│ ├── App.tsx # Main React application
│ ├── components/
│ │ └── Layout.tsx # Navigation and layout structure
│ └── pages/
│ ├── HomePage.tsx # Landing page with feature overview
│ ├── TournamentsPage.tsx # Tournament listing and management
│ ├── TournamentDetailPage.tsx # Individual tournament view
│ └── ProfilePage.tsx # User profile and statistics
└── README.md # Comprehensive project documentation
// Intelligent bracket size calculation
const calculateBracketSize = (participants: number, type: TournamentType): number => {
switch (type) {
case 'SINGLE_ELIMINATION':
case 'DOUBLE_ELIMINATION':
return Math.pow(2, Math.ceil(Math.log2(participants))); // Next power of 2
case 'ROUND_ROBIN':
return participants; // Everyone plays everyone
case 'SWISS_SYSTEM':
return participants; // Pairing-based system
default:
return participants;
}
};
// Comprehensive match result validation
const updateMatchResult = async (matchId: string, result: MatchResult) => {
// Validate permissions
if (!canUpdateMatch(user, match)) {
throw new Error('Not authorized to update this match');
}
// Process result and update bracket
const updatedMatch = await processMatchResult(match, result);
// Generate next round matches if bracket round completed
if (await isRoundComplete(match.tournamentId, match.roundNumber)) {
await generateNextRoundMatches(match.tournamentId);
}
return updatedMatch;
};
# Tournament Manager Services
tournament-frontend:
build: ./tournament-manager/frontend
container_name: tournament-frontend
networks:
- services-network
labels:
- traefik.http.routers.tournament-manager.rule=Host(`tournaments.playtopia.com.au`)
- traefik.http.routers.tournament-manager.middlewares=authelia-with-headers
- traefik.http.routers.tournament-manager.tls.certresolver=letsencrypt
tournament-backend:
build: ./tournament-manager/backend
container_name: tournament-backend
environment:
- DATABASE_URL=postgresql://tournament_user:tournament_password@tournament-database:5432/tournament_db
- FRONTEND_URL=https://tournaments.playtopia.com.au
depends_on:
- tournament-database
tournament-database:
image: postgres:16-alpine
container_name: tournament-database
environment:
- POSTGRES_DB=tournament_db
- POSTGRES_USER=tournament_user
- POSTGRES_PASSWORD=tournament_password
volumes:
- tournament_postgres_data:/var/lib/postgresql/data
# Deploy Tournament Manager services
cd /home/thrax/unified-services
docker-compose up -d tournament-frontend tournament-backend tournament-database
# Run database migrations
docker-compose exec tournament-backend npx prisma migrate deploy
# Generate Prisma client
docker-compose exec tournament-backend npx prisma generate
# View service logs
docker-compose logs -f tournament-backend
docker-compose logs -f tournament-frontend
# Access database for debugging
docker-compose exec tournament-database psql -U tournament_user -d tournament_db
GET /api/auth/me
- Get current user informationGET /api/auth/status
- Check authentication statusGET /api/tournaments
- List tournaments with filtering and paginationPOST /api/tournaments
- Create new tournamentGET /api/tournaments/:id
- Get tournament details with participants and matchesPUT /api/tournaments/:id
- Update tournament configurationDELETE /api/tournaments/:id
- Delete tournamentPOST /api/tournaments/:id/register
- Register current user for tournamentDELETE /api/tournaments/:id/register
- Unregister from tournamentGET /api/participants/:tournamentId
- Get tournament participantsPOST /api/participants/:tournamentId
- Add participant to tournamentPUT /api/participants/:id
- Update participant informationDELETE /api/participants/:id
- Remove participantPOST /api/participants/:tournamentId/seed
- Auto-seed participantsGET /api/matches/:tournamentId
- Get all tournament matchesGET /api/matches/single/:id
- Get specific match detailsPUT /api/matches/:id/result
- Update match resultPOST /api/matches/:tournamentId/generate
- Generate tournament bracketGET /api/rules/:tournamentId
- Get tournament rulesPOST /api/rules
- Create tournament rulePUT /api/rules/:id
- Update ruleDELETE /api/rules/:id
- Delete rulePOST /api/rules/:tournamentId/batch
- Create multiple rulesGET /api/users/profile
- Get current user profile with tournament historyPUT /api/users/profile
- Update user profileGET /api/users/:id/tournaments
- Get user tournament historyGET /api/users/:id/stats
- Get user performance statisticsTournament Manager delivers a complete tournament management platform with:
Frontend Enhancement (Ready for development):
Tournament Manager represents a comprehensive tournament management solution with professional-grade backend infrastructure, authentication integration, and real-time capabilities. The system is production-ready for API-based tournament management and prepared for continued frontend development.