diff --git a/package.json b/package.json index 8abc8e8..204268f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,13 @@ "test:watch": "jest --watch", "test:coverage": "jest --coverage" }, - "keywords": ["qa", "forum", "stackoverflow", "questions", "answers"], + "keywords": [ + "qa", + "forum", + "stackoverflow", + "questions", + "answers" + ], "author": "StackIt Team", "license": "MIT", "devDependencies": { @@ -22,5 +28,16 @@ "jest": "^29.7.0", "supertest": "^6.3.3", "mongoose": "^8.0.0" - } -} \ No newline at end of file + }, + "directories": { + "test": "tests" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Neeraj-code-beep/StackIt.git" + }, + "bugs": { + "url": "https://github.com/Neeraj-code-beep/StackIt/issues" + }, + "homepage": "https://github.com/Neeraj-code-beep/StackIt#readme" +} diff --git a/server/index.js b/server/index.js index 3d89760..71006bd 100644 --- a/server/index.js +++ b/server/index.js @@ -1,72 +1,77 @@ -const express = require('express') -const cors = require('cors') -const helmet = require('helmet') -const morgan = require('morgan') -const rateLimit = require('express-rate-limit') -const { createServer } = require('http') -const { Server } = require('socket.io') -const path = require('path') +const express = require('express'); +const cors = require('cors'); +const helmet = require('helmet'); +const morgan = require('morgan'); +const rateLimit = require('express-rate-limit'); +const { createServer } = require('http'); +const { Server } = require('socket.io'); +const path = require('path'); // Ensure .env is loaded from the correct path -require('dotenv').config({ path: path.resolve(__dirname, '.env') }) +require('dotenv').config({ path: path.resolve(__dirname, '.env') }); // Fallback: manually set JWT_SECRET if .env loading failed if (!process.env.JWT_SECRET) { - console.log('🔧 JWT_SECRET not loaded from .env, setting manually') - process.env.JWT_SECRET = 'stackit-super-secret-jwt-key-2024-change-in-production' - process.env.PORT = process.env.PORT || '5000' - process.env.NODE_ENV = process.env.NODE_ENV || 'development' - process.env.MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/stackit' - process.env.CLIENT_URL = process.env.CLIENT_URL || 'http://localhost:3000' + console.log('🔧 JWT_SECRET not loaded from .env, setting manually'); + process.env.JWT_SECRET = + 'stackit-super-secret-jwt-key-2024-change-in-production'; + process.env.PORT = process.env.PORT || '5000'; + process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + process.env.MONGODB_URI = + process.env.MONGODB_URI || 'mongodb://localhost:27017/stackit'; + process.env.CLIENT_URL = process.env.CLIENT_URL || 'http://localhost:3000'; } -console.log('✅ JWT_SECRET loaded:', !!process.env.JWT_SECRET) -console.log('✅ JWT_SECRET length:', process.env.JWT_SECRET ? process.env.JWT_SECRET.length : 0) +console.log('✅ JWT_SECRET loaded:', !!process.env.JWT_SECRET); +console.log( + '✅ JWT_SECRET length:', + process.env.JWT_SECRET ? process.env.JWT_SECRET.length : 0, +); -const connectDB = require('./config/db') -const authRoutes = require('./routes/auth') -const questionRoutes = require('./routes/questions') +const connectDB = require('./config/db'); +const authRoutes = require('./routes/auth'); +const questionRoutes = require('./routes/questions'); const answersRoutes = require('./routes/answers'); -const userRoutes = require('./routes/users') -const tagRoutes = require('./routes/tags') -const adminRoutes = require('./routes/admin') -const commentRoutes = require('./routes/comments') -const notificationRoutes = require('./routes/notifications') -const { authenticateSocket } = require('./middleware/auth') +const userRoutes = require('./routes/users'); +const tagRoutes = require('./routes/tags'); +const adminRoutes = require('./routes/admin'); +const commentRoutes = require('./routes/comments'); +const notificationRoutes = require('./routes/notifications'); +const { authenticateSocket } = require('./middleware/auth'); -const app = express() -const server = createServer(app) +const app = express(); +const server = createServer(app); // Socket.io setup const io = new Server(server, { cors: { - origin: process.env.CLIENT_URL || "http://localhost:3000", - methods: ["GET", "POST"] - } -}) + origin: process.env.CLIENT_URL || 'http://localhost:3000', + methods: ['GET', 'POST'], + }, +}); // Rate limiting - Development-friendly configuration const shouldEnableRateLimit = () => { // Environment variable override takes precedence if (process.env.RATE_LIMIT_ENABLED !== undefined) { - return process.env.RATE_LIMIT_ENABLED === 'true' + return process.env.RATE_LIMIT_ENABLED === 'true'; } - + // Production: always enabled // Development: disabled by default - return process.env.NODE_ENV === 'production' -} + return process.env.NODE_ENV === 'production'; +}; const getRateLimitMax = () => { // Environment variable override takes precedence if (process.env.RATE_LIMIT_MAX) { - return parseInt(process.env.RATE_LIMIT_MAX) + return parseInt(process.env.RATE_LIMIT_MAX); } - + // Production: 100 requests per 15 minutes // Development: 1,000,000 requests (effectively unlimited) - return process.env.NODE_ENV === 'production' ? 100 : 1000000 -} + return process.env.NODE_ENV === 'production' ? 100 : 1000000; +}; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes @@ -74,60 +79,64 @@ const limiter = rateLimit({ message: 'Too many requests from this IP, please try again later.', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers -}) +}); // Log rate limiting configuration if (shouldEnableRateLimit()) { - console.log(`🔒 Rate limiting: ${getRateLimitMax()} requests per 15 minutes`) + console.log(`🔒 Rate limiting: ${getRateLimitMax()} requests per 15 minutes`); } else { - console.log('🔓 Rate limiting: DISABLED') + console.log('🔓 Rate limiting: DISABLED'); } // Middleware -app.use(helmet()) -app.use(cors({ - origin: process.env.CLIENT_URL || "http://localhost:3000", - credentials: true -})) -app.use(morgan('combined')) +app.use(helmet()); +app.use( + cors({ + origin: process.env.CLIENT_URL || 'http://localhost:3000', + credentials: true, + }), +); +app.use(morgan('combined')); // Apply rate limiting conditionally if (shouldEnableRateLimit()) { - app.use(limiter) + app.use(limiter); } -app.use(express.json({ limit: '10mb' })) -app.use(express.urlencoded({ extended: true })) +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true })); // Debug middleware to log all requests app.use((req, res, next) => { - console.log(`🔍 ${req.method} ${req.originalUrl} - ${new Date().toISOString()}`); + console.log( + `🔍 ${req.method} ${req.originalUrl} - ${new Date().toISOString()}`, + ); next(); }); // Routes -app.use('/api/auth', authRoutes) -app.use('/api/questions', questionRoutes) +app.use('/api/auth', authRoutes); +app.use('/api/questions', questionRoutes); app.use('/api/answers', answersRoutes); -app.use('/api/users', userRoutes) -app.use('/api/tags', tagRoutes) -app.use('/api/admin', adminRoutes) -app.use('/api/comments', commentRoutes) -app.use('/api/notifications', notificationRoutes) +app.use('/api/users', userRoutes); +app.use('/api/tags', tagRoutes); +app.use('/api/admin', adminRoutes); +app.use('/api/comments', commentRoutes); +app.use('/api/notifications', notificationRoutes); // Health check app.get('/api/health', (req, res) => { - res.json({ status: 'OK', timestamp: new Date().toISOString() }) -}) + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); // Socket.io connection handling -io.use(authenticateSocket) +io.use(authenticateSocket); io.on('connection', (socket) => { - console.log('User connected:', socket.userId) + console.log('User connected:', socket.userId); // Join user to their personal room - socket.join(`user:${socket.userId}`) + socket.join(`user:${socket.userId}`); // Handle notifications socket.on('send-notification', async (data) => { @@ -139,72 +148,83 @@ io.on('connection', (socket) => { content: data.content, questionId: data.questionId, answerId: data.answerId, - sender: socket.userId - }) + sender: socket.userId, + }); // Send to recipient - io.to(`user:${data.recipientId}`).emit('notification', notification) + io.to(`user:${data.recipientId}`).emit('notification', notification); } catch (error) { - console.error('Error sending notification:', error) + console.error('Error sending notification:', error); } - }) + }); // Handle real-time updates socket.on('join-question', (questionId) => { - socket.join(`question:${questionId}`) - }) + socket.join(`question:${questionId}`); + }); socket.on('leave-question', (questionId) => { - socket.leave(`question:${questionId}`) - }) + socket.leave(`question:${questionId}`); + }); socket.on('disconnect', () => { - console.log('User disconnected:', socket.userId) - }) -}) + console.log('User disconnected:', socket.userId); + }); +}); // Global error handling middleware app.use((err, req, res, next) => { - console.error('❌ Global error handler caught:', err) - console.error('❌ Error stack:', err.stack) - res.status(500).json({ - message: 'Internal Server Error', - error: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong' - }) -}) + console.error('❌ Global error handler caught:', err); + console.error('❌ Error stack:', err.stack); + res.status(500).json({ + message: 'Internal Server Error', + error: + process.env.NODE_ENV === 'development' + ? err.message + : 'Something went wrong', + }); +}); // 404 handler app.use('*', (req, res) => { - res.status(404).json({ message: 'Route not found' }) -}) + res.status(404).json({ message: 'Route not found' }); +}); -const PORT = process.env.PORT || 5000 +const PORT = process.env.PORT || 5000; const startServer = async () => { try { - await connectDB() - + await connectDB(); + // Debug environment variables - console.log('🔍 Server Debug - JWT_SECRET exists:', !!process.env.JWT_SECRET) - console.log('🔍 Server Debug - JWT_SECRET length:', process.env.JWT_SECRET ? process.env.JWT_SECRET.length : 0) - console.log('🔍 Server Debug - NODE_ENV:', process.env.NODE_ENV) - console.log('🔍 Server Debug - PORT:', process.env.PORT || 5000) - + console.log( + '🔍 Server Debug - JWT_SECRET exists:', + !!process.env.JWT_SECRET, + ); + console.log( + '🔍 Server Debug - JWT_SECRET length:', + process.env.JWT_SECRET ? process.env.JWT_SECRET.length : 0, + ); + console.log('🔍 Server Debug - NODE_ENV:', process.env.NODE_ENV); + console.log('🔍 Server Debug - PORT:', process.env.PORT || 5000); + server.listen(PORT, () => { - console.log(`✅ Server running at http://localhost:${PORT}`) - console.log('✅ Ensure Vite proxy forwards /api calls correctly to the server.') - console.log('✅ Available routes:') - console.log(' - POST /api/answers/:questionId (create answer)') - console.log(' - GET /api/answers/question/:questionId (get answers)') - console.log(' - PUT /api/answers/:answerId (update answer)') - console.log(' - DELETE /api/answers/:answerId (delete answer)') - }) + console.log(`✅ Server running at http://localhost:${PORT}`); + console.log( + '✅ Ensure Vite proxy forwards /api calls correctly to the server.', + ); + console.log('✅ Available routes:'); + console.log(' - POST /api/answers/:questionId (create answer)'); + console.log(' - GET /api/answers/question/:questionId (get answers)'); + console.log(' - PUT /api/answers/:answerId (update answer)'); + console.log(' - DELETE /api/answers/:answerId (delete answer)'); + }); } catch (error) { - console.error('Failed to start server:', error) - process.exit(1) + console.error('Failed to start server:', error); + process.exit(1); } -} +}; -startServer() +startServer(); -module.exports = { app, io } \ No newline at end of file +module.exports = { app, io }; diff --git a/server/package-lock.json b/server/package-lock.json index 6afc5c6..8327a17 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,7 +13,7 @@ "cloudinary": "^1.41.0", "cors": "^2.8.5", "dotenv": "^16.3.1", - "express": "^4.18.2", + "express": "^4.22.2", "express-rate-limit": "^7.1.5", "express-validator": "^7.0.1", "helmet": "^7.1.0", @@ -1407,29 +1407,58 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2279,39 +2308,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -4703,12 +4732,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -4727,20 +4756,49 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -4985,13 +5043,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" diff --git a/server/package.json b/server/package.json index f83b9f3..31a6f1a 100644 --- a/server/package.json +++ b/server/package.json @@ -14,7 +14,7 @@ "cloudinary": "^1.41.0", "cors": "^2.8.5", "dotenv": "^16.3.1", - "express": "^4.18.2", + "express": "^4.22.2", "express-rate-limit": "^7.1.5", "express-validator": "^7.0.1", "helmet": "^7.1.0",