Files
spotlightcam/backend/src/app.js
Radosław Gierwiało 4a3e32f3b6 feat: implement Phase 2 - Matches API with real-time notifications
Backend changes:
- Add matches API routes (POST, GET, PUT, DELETE)
- Create/accept/reject match requests
- Auto-create private chat rooms on match acceptance
- Socket.IO notifications for match events (received, accepted, cancelled)
- Users join personal rooms (user_{id}) for notifications

Frontend changes:
- Add MatchesPage component with inbox UI
- Matches navigation link with notification badge
- Real-time match request count updates
- Accept/reject match functionality
- Filter matches by status (all/pending/accepted)
- Integrate match requests in EventChatPage (UserPlus button)

Features:
- Send match requests to event participants
- Accept incoming match requests
- Real-time notifications via Socket.IO
- Automatic private chat room creation
- Match status tracking (pending/accepted/completed)
- Authorization checks (only participants can match)
- Duplicate match prevention
2025-11-14 19:22:23 +01:00

116 lines
3.0 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const securityConfig = require('./config/security');
const { apiLimiter } = require('./middleware/rateLimiter');
const app = express();
// Security Headers (helmet)
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://ui-avatars.com"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:", "https://ui-avatars.com"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
noSniff: true,
xssFilter: true,
hidePoweredBy: true,
}));
// CORS
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = securityConfig.cors.origin;
// Allow requests with no origin (mobile apps, curl, etc.)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: securityConfig.cors.credentials,
maxAge: 86400, // 24 hours
}));
// Body parsing with size limits
app.use(express.json({ limit: securityConfig.bodyLimit }));
app.use(express.urlencoded({ extended: true, limit: securityConfig.bodyLimit }));
// Request logging middleware
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
// Health check endpoint
app.get('/api/health', (req, res) => {
res.status(200).json({
status: 'ok',
message: 'Backend is running',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development'
});
});
// Apply rate limiting to all API routes
app.use('/api/', apiLimiter);
// API routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));
app.use('/api/events', require('./routes/events'));
app.use('/api/wsdc', require('./routes/wsdc'));
app.use('/api/divisions', require('./routes/divisions'));
app.use('/api/competition-types', require('./routes/competitionTypes'));
app.use('/api/matches', require('./routes/matches'));
// app.use('/api/ratings', require('./routes/ratings'));
// 404 handler
app.use((req, res) => {
res.status(404).json({
error: 'Not Found',
message: `Cannot ${req.method} ${req.path}`
});
});
// Error handler
app.use((err, req, res, next) => {
// Log full error for debugging
console.error('Error:', err);
// Determine if we should show detailed errors
const isDevelopment = process.env.NODE_ENV === 'development';
// Generic error response
const errorResponse = {
success: false,
error: isDevelopment ? err.message : 'Internal Server Error',
};
// Add stack trace only in development
if (isDevelopment && err.stack) {
errorResponse.stack = err.stack;
}
res.status(err.status || 500).json(errorResponse);
});
module.exports = app;