express-autotemplates
Version:
CLI tool to generate Express backend projects with various templates
1,066 lines (876 loc) • 31.2 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const packageJson = (projectName) => ({
name: projectName,
version: "1.0.0",
description: "Real-time chat application backend",
main: "server.js",
scripts: {
start: "node server.js",
dev: "nodemon server.js",
test: "echo \"Error: no test specified\" && exit 1"
},
keywords: ["express", "socket.io", "chat", "realtime", "mongodb"],
author: "",
license: "MIT",
engines: {
node: ">=16.0.0"
},
dependencies: {
express: "^4.21.2",
"socket.io": "^4.8.1",
cors: "^2.8.5",
dotenv: "^17.2.1",
mongoose: "^8.16.5",
helmet: "^8.1.0",
morgan: "^1.10.0",
compression: "^1.7.4",
bcryptjs: "^3.0.2",
jsonwebtoken: "^9.0.2",
validator: "^13.15.15"
},
devDependencies: {
nodemon: "^3.1.10"
}
});
const serverJs = `const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const validator = require('validator');
require('dotenv').config();
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "*",
methods: ["GET", "POST"]
}
});
const PORT = process.env.PORT || 3000;
// Security middleware
app.use(helmet());
app.use(morgan('combined'));
app.use(compression());
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// MongoDB connection
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/chatapp')
.then(() => console.log('✅ Connected to MongoDB'))
.catch(err => console.error('❌ MongoDB connection error:', err));
// User Schema
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, validate: [validator.isEmail, 'Invalid email'] },
password: { type: String, required: true, minlength: 6 },
avatar: { type: String, default: '' },
isOnline: { type: Boolean, default: false },
lastSeen: { type: Date, default: Date.now }
}, { timestamps: true });
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
userSchema.methods.comparePassword = async function(password) {
return bcrypt.compare(password, this.password);
};
const User = mongoose.model('User', userSchema);
// Room Schema
const roomSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
description: String,
isPrivate: { type: Boolean, default: false },
members: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
admin: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
const Room = mongoose.model('Room', roomSchema);
// Message Schema
const messageSchema = new mongoose.Schema({
content: { type: String, required: true },
sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
room: { type: mongoose.Schema.Types.ObjectId, ref: 'Room', required: true },
messageType: { type: String, enum: ['text', 'image', 'file'], default: 'text' },
edited: { type: Boolean, default: false },
editedAt: Date
}, { timestamps: true });
const Message = mongoose.model('Message', messageSchema);
// Auth middleware
const authenticateToken = async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret');
const user = await User.findById(decoded.userId);
if (!user) {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = user;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
};
// Routes
app.get('/', (req, res) => {
res.json({
message: 'Chat App Backend Running!',
version: '1.0.0',
endpoints: ['/api/auth/register', '/api/auth/login', '/api/rooms', '/api/messages']
});
});
// Auth routes
app.post('/api/auth/register', async (req, res) => {
try {
const { username, email, password } = req.body;
if (!username || !email || !password) {
return res.status(400).json({ error: 'All fields are required' });
}
const user = new User({ username, email, password });
await user.save();
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET || 'fallback-secret');
res.status(201).json({
token,
user: { id: user._id, username: user.username, email: user.email }
});
} catch (error) {
if (error.code === 11000) {
return res.status(400).json({ error: 'Username or email already exists' });
}
res.status(400).json({ error: error.message });
}
});
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
user.isOnline = true;
await user.save();
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET || 'fallback-secret');
res.json({
token,
user: { id: user._id, username: user.username, email: user.email }
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Room routes
app.get('/api/rooms', authenticateToken, async (req, res) => {
try {
const rooms = await Room.find({
$or: [{ isPrivate: false }, { members: req.user._id }]
}).populate('admin', 'username');
res.json(rooms);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/api/rooms', authenticateToken, async (req, res) => {
try {
const { name, description, isPrivate } = req.body;
const room = new Room({
name,
description,
isPrivate: isPrivate || false,
admin: req.user._id,
members: [req.user._id]
});
await room.save();
res.status(201).json(room);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Message routes
app.get('/api/messages/:roomId', authenticateToken, async (req, res) => {
try {
const { roomId } = req.params;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(req.user._id))) {
return res.status(403).json({ error: 'Access denied' });
}
const messages = await Message.find({ room: roomId })
.populate('sender', 'username avatar')
.sort({ createdAt: -1 })
.limit(limit * 1)
.skip((page - 1) * limit);
res.json(messages.reverse());
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Socket.io connection handling
const connectedUsers = new Map();
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error'));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret');
const user = await User.findById(decoded.userId);
if (!user) {
return next(new Error('Authentication error'));
}
socket.userId = user._id.toString();
socket.username = user.username;
next();
} catch (err) {
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
console.log(\`✅ User connected: \${socket.username} (\${socket.id})\`);
connectedUsers.set(socket.userId, {
socketId: socket.id,
username: socket.username,
joinedAt: new Date()
});
socket.on('join-room', async (roomId) => {
try {
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(socket.userId))) {
socket.emit('error', { message: 'Access denied to room' });
return;
}
socket.join(roomId);
socket.currentRoom = roomId;
socket.to(roomId).emit('user-joined', {
username: socket.username,
message: \`\${socket.username} joined the room\`
});
console.log(\`👥 \${socket.username} joined room: \${room.name}\`);
} catch (error) {
socket.emit('error', { message: 'Failed to join room' });
}
});
socket.on('send-message', async (data) => {
try {
const { content, roomId } = data;
if (!content || !roomId) {
socket.emit('error', { message: 'Message content and room ID required' });
return;
}
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(socket.userId))) {
socket.emit('error', { message: 'Access denied' });
return;
}
const message = new Message({
content,
sender: socket.userId,
room: roomId
});
await message.save();
await message.populate('sender', 'username avatar');
io.to(roomId).emit('receive-message', {
id: message._id,
content: message.content,
sender: message.sender,
timestamp: message.createdAt,
messageType: message.messageType
});
} catch (error) {
console.error('❌ Error saving message:', error);
socket.emit('error', { message: 'Failed to send message' });
}
});
socket.on('typing', (data) => {
socket.to(data.roomId).emit('user-typing', {
username: socket.username,
isTyping: data.isTyping
});
});
socket.on('disconnect', async () => {
console.log(\`❌ User disconnected: \${socket.username}\`);
connectedUsers.delete(socket.userId);
if (socket.currentRoom) {
socket.to(socket.currentRoom).emit('user-left', {
username: socket.username,
message: \`\${socket.username} left the room\`
});
}
// Update user offline status
try {
await User.findByIdAndUpdate(socket.userId, {
isOnline: false,
lastSeen: new Date()
});
} catch (error) {
console.error('Error updating user status:', error);
}
});
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: process.env.NODE_ENV === 'production' ? 'Something went wrong!' : err.message
});
});
server.listen(PORT, () => {
console.log(\`🚀 Chat server running on port \${PORT}\`);
console.log(\`📍 Environment: \${process.env.NODE_ENV || 'development'}\`);
console.log(\`🌐 URL: http://localhost:\${PORT}\`);
});`;
const envFile = `PORT=3000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/chatapp
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
CLIENT_URL=http://localhost:3000`;
const readmeFile = (projectName) => `# ${projectName}
Professional real-time chat application backend with user authentication, room management, and Socket.io.
## Features
- 🔐 User authentication with JWT
- 💬 Real-time messaging with Socket.io
- 🏠 Room-based chat system (public/private rooms)
- 📝 Message history with pagination
- 👥 User online/offline status
- ⚡ Typing indicators
- 🔒 Secure password hashing
- 📊 Message validation and error handling
- 🛡️ Security middleware (Helmet, CORS)
- 📈 Request logging with Morgan
## Prerequisites
- MongoDB installed and running locally
- Node.js (v14 or higher)
## Getting Started
1. Install dependencies:
\`\`\`bash
npm install
\`\`\`
2. Make sure MongoDB is running on your system
3. Update the \`.env\` file with your configuration:
\`\`\`env
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
MONGODB_URI=mongodb://localhost:27017/chatapp
\`\`\`
4. Start the server:
\`\`\`bash
npm start
\`\`\`
For development with auto-reload:
\`\`\`bash
npm run dev
\`\`\`
## API Endpoints
### Authentication
- \`POST /api/auth/register\` - Register new user
\`\`\`json
{
"username": "john_doe",
"email": "john@example.com",
"password": "password123"
}
\`\`\`
- \`POST /api/auth/login\` - Login user
\`\`\`json
{
"email": "john@example.com",
"password": "password123"
}
\`\`\`
### Rooms
- \`GET /api/rooms\` - Get available rooms (requires auth)
- \`POST /api/rooms\` - Create new room (requires auth)
\`\`\`json
{
"name": "General Chat",
"description": "General discussion room",
"isPrivate": false
}
\`\`\`
### Messages
- \`GET /api/messages/:roomId?page=1&limit=50\` - Get room messages (requires auth)
## Socket Events
### Authentication
Connect with JWT token:
\`\`\`javascript
const socket = io('http://localhost:3000', {
auth: {
token: 'your-jwt-token'
}
});
\`\`\`
### Client to Server Events:
- \`join-room\` - Join a chat room
\`\`\`javascript
socket.emit('join-room', roomId);
\`\`\`
- \`send-message\` - Send message to room
\`\`\`javascript
socket.emit('send-message', {
content: 'Hello everyone!',
roomId: 'room-id-here'
});
\`\`\`
- \`typing\` - Send typing indicator
\`\`\`javascript
socket.emit('typing', {
roomId: 'room-id-here',
isTyping: true
});
\`\`\`
### Server to Client Events:
- \`receive-message\` - New message received
- \`user-joined\` - User joined room
- \`user-left\` - User left room
- \`user-typing\` - User typing indicator
- \`error\` - Error message
## Usage Example
### Frontend Integration:
\`\`\`javascript
// Connect to server
const socket = io('http://localhost:3000', {
auth: { token: localStorage.getItem('token') }
});
// Join a room
socket.emit('join-room', roomId);
// Send message
socket.emit('send-message', {
content: 'Hello!',
roomId: roomId
});
// Listen for messages
socket.on('receive-message', (message) => {
console.log('New message:', message);
});
// Handle typing
socket.on('user-typing', (data) => {
console.log(\`\${data.username} is typing...\`);
});
\`\`\`
## Database Schema
### User
- username (unique)
- email (unique, validated)
- password (hashed)
- avatar
- isOnline
- lastSeen
### Room
- name (unique)
- description
- isPrivate
- members (User references)
- admin (User reference)
### Message
- content
- sender (User reference)
- room (Room reference)
- messageType (text/image/file)
- edited
- editedAt
## Security Features
- Password hashing with bcrypt
- JWT token authentication
- Input validation
- Rate limiting ready
- CORS protection
- Helmet security headers
Server runs on http://localhost:3000
`;
async function generate(projectPath, projectName) {
// Create folder structure
await fs.ensureDir(path.join(projectPath, "models"));
await fs.ensureDir(path.join(projectPath, "routes"));
await fs.ensureDir(path.join(projectPath, "middleware"));
await fs.ensureDir(path.join(projectPath, "controllers"));
await fs.ensureDir(path.join(projectPath, "config"));
await fs.ensureDir(path.join(projectPath, "utils"));
// Create package.json
await fs.writeJson(path.join(projectPath, 'package.json'), packageJson(projectName), { spaces: 2 });
// Create main server file
const mainServerJs = `const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
const connectDB = require('./config/database');
require('dotenv').config();
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "*",
methods: ["GET", "POST"]
}
});
const PORT = process.env.PORT || 3000;
// Connect to MongoDB
connectDB();
// Security middleware
app.use(helmet());
app.use(morgan('combined'));
app.use(compression());
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Routes
const authRoutes = require('./routes/auth');
const roomRoutes = require('./routes/rooms');
const messageRoutes = require('./routes/messages');
app.get('/', (req, res) => {
res.json({
message: 'Chat App Backend Running!',
version: '1.0.0',
endpoints: ['/api/auth/register', '/api/auth/login', '/api/rooms', '/api/messages']
});
});
app.use('/api/auth', authRoutes);
app.use('/api/rooms', roomRoutes);
app.use('/api/messages', messageRoutes);
// Socket.io setup
require('./utils/socketHandler')(io);
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production' ? 'Something went wrong!' : err.message
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
server.listen(PORT, () => {
console.log(\`🚀 Chat server running on port \${PORT}\`);
console.log(\`📍 Environment: \${process.env.NODE_ENV || 'development'}\`);
console.log(\`🌐 URL: http://localhost:\${PORT}\`);
});`;
await fs.writeFile(path.join(projectPath, 'server.js'), mainServerJs);
// Create database config
const databaseConfig = `const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/chatapp');
console.log('✅ Connected to MongoDB');
} catch (error) {
console.error('❌ MongoDB connection error:', error);
process.exit(1);
}
};
module.exports = connectDB;`;
await fs.writeFile(path.join(projectPath, 'config/database.js'), databaseConfig);
// Create User model
const userModel = `const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const validator = require('validator');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, validate: [validator.isEmail, 'Invalid email'] },
password: { type: String, required: true, minlength: 6 },
avatar: { type: String, default: '' },
isOnline: { type: Boolean, default: false },
lastSeen: { type: Date, default: Date.now }
}, { timestamps: true });
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
userSchema.methods.comparePassword = async function(password) {
return bcrypt.compare(password, this.password);
};
module.exports = mongoose.model('User', userSchema);`;
await fs.writeFile(path.join(projectPath, 'models/User.js'), userModel);
// Create Room model
const roomModel = `const mongoose = require('mongoose');
const roomSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
description: String,
isPrivate: { type: Boolean, default: false },
members: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
admin: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
module.exports = mongoose.model('Room', roomSchema);`;
await fs.writeFile(path.join(projectPath, 'models/Room.js'), roomModel);
// Create Message model
const messageModel = `const mongoose = require('mongoose');
const messageSchema = new mongoose.Schema({
content: { type: String, required: true },
sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
room: { type: mongoose.Schema.Types.ObjectId, ref: 'Room', required: true },
messageType: { type: String, enum: ['text', 'image', 'file'], default: 'text' },
edited: { type: Boolean, default: false },
editedAt: Date
}, { timestamps: true });
module.exports = mongoose.model('Message', messageSchema);`;
await fs.writeFile(path.join(projectPath, 'models/Message.js'), messageModel);
// Create auth middleware
const authMiddleware = `const jwt = require('jsonwebtoken');
const User = require('../models/User');
const authenticateToken = async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret');
const user = await User.findById(decoded.userId);
if (!user) {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = user;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
};
module.exports = { authenticateToken };`;
await fs.writeFile(path.join(projectPath, 'middleware/auth.js'), authMiddleware);
// Create auth controller
const authController = `const jwt = require('jsonwebtoken');
const User = require('../models/User');
const register = async (req, res) => {
try {
const { username, email, password } = req.body;
if (!username || !email || !password) {
return res.status(400).json({ error: 'All fields are required' });
}
const user = new User({ username, email, password });
await user.save();
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET || 'fallback-secret');
res.status(201).json({
token,
user: { id: user._id, username: user.username, email: user.email }
});
} catch (error) {
if (error.code === 11000) {
return res.status(400).json({ error: 'Username or email already exists' });
}
res.status(400).json({ error: error.message });
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
user.isOnline = true;
await user.save();
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET || 'fallback-secret');
res.json({
token,
user: { id: user._id, username: user.username, email: user.email }
});
} catch (error) {
res.status(400).json({ error: error.message });
}
};
module.exports = { register, login };`;
await fs.writeFile(path.join(projectPath, 'controllers/authController.js'), authController);
// Create room controller
const roomController = `const Room = require('../models/Room');
const getRooms = async (req, res) => {
try {
const rooms = await Room.find({
$or: [{ isPrivate: false }, { members: req.user._id }]
}).populate('admin', 'username');
res.json(rooms);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
const createRoom = async (req, res) => {
try {
const { name, description, isPrivate } = req.body;
const room = new Room({
name,
description,
isPrivate: isPrivate || false,
admin: req.user._id,
members: [req.user._id]
});
await room.save();
res.status(201).json(room);
} catch (error) {
res.status(400).json({ error: error.message });
}
};
module.exports = { getRooms, createRoom };`;
await fs.writeFile(path.join(projectPath, 'controllers/roomController.js'), roomController);
// Create message controller
const messageController = `const Message = require('../models/Message');
const Room = require('../models/Room');
const getMessages = async (req, res) => {
try {
const { roomId } = req.params;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 50;
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(req.user._id))) {
return res.status(403).json({ error: 'Access denied' });
}
const messages = await Message.find({ room: roomId })
.populate('sender', 'username avatar')
.sort({ createdAt: -1 })
.limit(limit * 1)
.skip((page - 1) * limit);
res.json(messages.reverse());
} catch (error) {
res.status(500).json({ error: error.message });
}
};
module.exports = { getMessages };`;
await fs.writeFile(path.join(projectPath, 'controllers/messageController.js'), messageController);
// Create auth routes
const authRoutes = `const express = require('express');
const { register, login } = require('../controllers/authController');
const router = express.Router();
router.post('/register', register);
router.post('/login', login);
module.exports = router;`;
await fs.writeFile(path.join(projectPath, 'routes/auth.js'), authRoutes);
// Create room routes
const roomRoutes = `const express = require('express');
const { getRooms, createRoom } = require('../controllers/roomController');
const { authenticateToken } = require('../middleware/auth');
const router = express.Router();
router.get('/', authenticateToken, getRooms);
router.post('/', authenticateToken, createRoom);
module.exports = router;`;
await fs.writeFile(path.join(projectPath, 'routes/rooms.js'), roomRoutes);
// Create message routes
const messageRoutes = `const express = require('express');
const { getMessages } = require('../controllers/messageController');
const { authenticateToken } = require('../middleware/auth');
const router = express.Router();
router.get('/:roomId', authenticateToken, getMessages);
module.exports = router;`;
await fs.writeFile(path.join(projectPath, 'routes/messages.js'), messageRoutes);
// Create socket handler
const socketHandler = `const jwt = require('jsonwebtoken');
const User = require('../models/User');
const Room = require('../models/Room');
const Message = require('../models/Message');
const connectedUsers = new Map();
module.exports = (io) => {
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error'));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret');
const user = await User.findById(decoded.userId);
if (!user) {
return next(new Error('Authentication error'));
}
socket.userId = user._id.toString();
socket.username = user.username;
next();
} catch (err) {
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
console.log(\`✅ User connected: \${socket.username} (\${socket.id})\`);
connectedUsers.set(socket.userId, {
socketId: socket.id,
username: socket.username,
joinedAt: new Date()
});
socket.on('join-room', async (roomId) => {
try {
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(socket.userId))) {
socket.emit('error', { message: 'Access denied to room' });
return;
}
socket.join(roomId);
socket.currentRoom = roomId;
socket.to(roomId).emit('user-joined', {
username: socket.username,
message: \`\${socket.username} joined the room\`
});
console.log(\`👥 \${socket.username} joined room: \${room.name}\`);
} catch (error) {
socket.emit('error', { message: 'Failed to join room' });
}
});
socket.on('send-message', async (data) => {
try {
const { content, roomId } = data;
if (!content || !roomId) {
socket.emit('error', { message: 'Message content and room ID required' });
return;
}
const room = await Room.findById(roomId);
if (!room || (room.isPrivate && !room.members.includes(socket.userId))) {
socket.emit('error', { message: 'Access denied' });
return;
}
const message = new Message({
content,
sender: socket.userId,
room: roomId
});
await message.save();
await message.populate('sender', 'username avatar');
io.to(roomId).emit('receive-message', {
id: message._id,
content: message.content,
sender: message.sender,
timestamp: message.createdAt,
messageType: message.messageType
});
} catch (error) {
console.error('❌ Error saving message:', error);
socket.emit('error', { message: 'Failed to send message' });
}
});
socket.on('typing', (data) => {
socket.to(data.roomId).emit('user-typing', {
username: socket.username,
isTyping: data.isTyping
});
});
socket.on('disconnect', async () => {
console.log(\`❌ User disconnected: \${socket.username}\`);
connectedUsers.delete(socket.userId);
if (socket.currentRoom) {
socket.to(socket.currentRoom).emit('user-left', {
username: socket.username,
message: \`\${socket.username} left the room\`
});
}
try {
await User.findByIdAndUpdate(socket.userId, {
isOnline: false,
lastSeen: new Date()
});
} catch (error) {
console.error('Error updating user status:', error);
}
});
});
};`;
await fs.writeFile(path.join(projectPath, 'utils/socketHandler.js'), socketHandler);
// Create other files
await fs.writeFile(path.join(projectPath, '.env'), envFile);
await fs.writeFile(path.join(projectPath, 'README.md'), readmeFile(projectName));
await fs.writeFile(path.join(projectPath, '.gitignore'), 'node_modules/\n.env\n*.log\n');
}
module.exports = { generate };