gym-website / server.js
ChineseAI21's picture
Upload server.js with huggingface_hub
270ba1a verified
Raw
History Blame Contribute Delete
9.37 kB
Here's a complete production-ready Node.js Express server for a gym website:
```javascript
// server.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { Pool } = require('pg');
const path = require('path');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Database connection
const pool = new Pool({
user: process.env.DB_USER || 'postgres',
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'gym_db',
password: process.env.DB_PASSWORD || 'password',
port: process.env.DB_PORT || 5432,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Test database connection
pool.on('connect', () => {
console.log('Connected to PostgreSQL database');
});
pool.on('error', (err) => {
console.error('Database connection error:', err);
});
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
},
},
}));
// CORS configuration
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? [process.env.FRONTEND_URL || 'https://yourgym.com']
: ['http://localhost:3000', 'http://localhost:3001'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests from this IP, please try again later.',
},
standardHeaders: true,
legacyHeaders: false,
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Limit auth attempts
message: {
error: 'Too many authentication attempts, please try again later.',
},
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);
// Logging
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
// Body parser
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Static files
app.use(express.static(path.join(__dirname, 'public')));
// JWT verification middleware
const verifyToken = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'gym_secret_key');
// Get user from database
const result = await pool.query(
'SELECT id, email, first_name, last_name, role FROM users WHERE id = $1',
[decoded.userId]
);
if (result.rows.length === 0) {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = result.rows[0];
next();
} catch (error) {
console.error('Token verification error:', error);
res.status(401).json({ error: 'Invalid token' });
}
};
// Health check endpoint
app.get('/health', async (req, res) => {
try {
await pool.query('SELECT 1');
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
database: 'Connected',
environment: process.env.NODE_ENV || 'development'
});
} catch (error) {
res.status(503).json({
status: 'ERROR',
timestamp: new Date().toISOString(),
database: 'Disconnected',
error: error.message
});
}
});
// Authentication routes
app.post('/api/auth/register', async (req, res) => {
try {
const { email, password, firstName, lastName, phone } = req.body;
// Validation
if (!email || !password || !firstName || !lastName) {
return res.status(400).json({ error: 'All fields are required' });
}
// Check if user exists
const existingUser = await pool.query(
'SELECT id FROM users WHERE email = $1',
[email]
);
if (existingUser.rows.length > 0) {
return res.status(409).json({ error: 'User already exists' });
}
// Hash password
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Create user
const result = await pool.query(
`INSERT INTO users (email, password, first_name, last_name, phone, role, created_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING id, email, first_name, last_name, role`,
[email, hashedPassword, firstName, lastName, phone, 'member']
);
const user = result.rows[0];
// Generate JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET || 'gym_secret_key',
{ expiresIn: '7d' }
);
res.status(201).json({
message: 'User registered successfully',
user: {
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
role: user.role
},
token
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
// Get user
const result = await pool.query(
'SELECT id, email, password, first_name, last_name, role FROM users WHERE email = $1',
[email]
);
if (result.rows.length === 0) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const user = result.rows[0];
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET || 'gym_secret_key',
{ expiresIn: '7d' }
);
res.json({
message: 'Login successful',
user: {
id: user.id,
email: user.email,
firstName: user.first_name,
lastName: user.last_name,
role: user.role
},
token
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Booking routes
app.get('/api/booking/classes', async (req, res) => {
try {
const result = await pool.query(`
SELECT
c.id, c.name, c.description, c.duration, c.capacity, c.price,
cs.id as schedule_id, cs.class_date, cs.start_time, cs.end_time,
cs.available_spots,
t.first_name as trainer_first_name, t.last_name as trainer_last_name,
t.specialization, t.image_url as trainer_image
FROM classes c
JOIN class_schedules cs ON c.id = cs.class_id
JOIN trainers t ON cs.trainer_id = t.id
WHERE cs.class_date >= CURRENT_DATE
ORDER BY cs.class_date, cs.start_time
`);
const classes = result.rows.map(row => ({
id: row.id,
name: row.name,
description: row.description,
duration: row.duration,
capacity: row.capacity,
price: parseFloat(row.price),
scheduleId: row.schedule_id,
date: row.class_date,
startTime: row.start_time,
endTime: row.end_time,
availableSpots: row.available_spots,
trainer: {
name: `${row.trainer_first_name} ${row.trainer_last_name}`,
specialization: row.specialization,
image: row.trainer_image
}
}));
res.json({ classes });
} catch (error) {
console.error('Get classes error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/booking/book', verifyToken, async (req, res) => {
const client = await pool.connect();
try {
await client.query('BEGIN');
const { scheduleId } = req.body;
const userId = req.user.id;
// Check if user already booked this class
const existingBooking = await client.query(
'SELECT id FROM bookings WHERE user_id = $1 AND schedule_id = $2',
[userId, scheduleId]
);
if (existingBooking.rows.length > 0) {
await client.query('ROLLBACK');
return res.status(409).json({ error: 'You have already booked this class' });
}
// Check available spots
const scheduleResult = await client.query(
'SELECT available_spots FROM class_schedules WHERE id = $1',
[scheduleId]
);
if (scheduleResult.rows.length === 0) {
await client.query('ROLLBACK');
return res.status(404).json({ error: 'Class not found' });
}
if (scheduleResult.rows[0].available_spots <= 0) {
await client.query('ROLLBACK');
return res.status(400).json({ error: 'No available spots' });
}
// Create booking