add changes

This commit is contained in:
AD2025
2025-12-26 23:56:32 +02:00
parent 410c3d725f
commit e7d26bc981
127 changed files with 36162 additions and 0 deletions

421
routes/admin.routes.js Normal file
View File

@@ -0,0 +1,421 @@
const express = require('express');
const router = express.Router();
const questionController = require('../controllers/question.controller');
const adminController = require('../controllers/admin.controller');
const { verifyToken, isAdmin } = require('../middleware/auth.middleware');
const { adminLimiter } = require('../middleware/rateLimiter');
const { cacheStatistics, cacheGuestAnalytics, cacheGuestSettings, invalidateCacheMiddleware, invalidateCache } = require('../middleware/cache');
/**
* @swagger
* /admin/statistics:
* get:
* summary: Get system-wide statistics for admin dashboard
* tags: [Admin]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Statistics retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* users:
* type: object
* properties:
* total:
* type: integer
* active:
* type: integer
* inactiveLast7Days:
* type: integer
* quizzes:
* type: object
* properties:
* totalSessions:
* type: integer
* averageScore:
* type: number
* passRate:
* type: number
* content:
* type: object
* properties:
* totalCategories:
* type: integer
* totalQuestions:
* type: integer
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /admin/guest-settings:
* get:
* summary: Get guest user settings
* tags: [Admin]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Guest settings retrieved successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* put:
* summary: Update guest user settings
* tags: [Admin]
* security:
* - bearerAuth: []
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* maxQuizzes:
* type: integer
* minimum: 1
* maximum: 100
* expiryHours:
* type: integer
* minimum: 1
* maximum: 168
* publicCategories:
* type: array
* items:
* type: integer
* featureRestrictions:
* type: object
* responses:
* 200:
* description: Settings updated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /admin/guest-analytics:
* get:
* summary: Get guest user analytics
* tags: [Admin]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Analytics retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* overview:
* type: object
* properties:
* totalGuestSessions:
* type: integer
* activeGuestSessions:
* type: integer
* convertedGuestSessions:
* type: integer
* conversionRate:
* type: number
* quizActivity:
* type: object
* behavior:
* type: object
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /admin/users:
* get:
* summary: Get all users with pagination and filtering
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* maximum: 100
* - in: query
* name: role
* schema:
* type: string
* enum: [user, admin]
* - in: query
* name: isActive
* schema:
* type: boolean
* - in: query
* name: sortBy
* schema:
* type: string
* enum: [createdAt, username, email]
* - in: query
* name: sortOrder
* schema:
* type: string
* enum: [asc, desc]
* responses:
* 200:
* description: Users retrieved successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /admin/users/{userId}:
* get:
* summary: Get user details by ID
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: User details retrieved successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
// Apply admin rate limiter to all routes
router.use(adminLimiter);
router.get('/statistics', verifyToken, isAdmin, cacheStatistics, adminController.getSystemStatistics);
router.get('/guest-settings', verifyToken, isAdmin, cacheGuestSettings, adminController.getGuestSettings);
router.put('/guest-settings', verifyToken, isAdmin, invalidateCacheMiddleware(() => invalidateCache.guestSettings()), adminController.updateGuestSettings);
router.get('/guest-analytics', verifyToken, isAdmin, cacheGuestAnalytics, adminController.getGuestAnalytics);
router.get('/users', verifyToken, isAdmin, adminController.getAllUsers);
router.get('/users/:userId', verifyToken, isAdmin, adminController.getUserById);
/**
* @swagger
* /admin/users/{userId}/role:
* put:
* summary: Update user role
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - role
* properties:
* role:
* type: string
* enum: [user, admin]
* responses:
* 200:
* description: User role updated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*
* /admin/users/{userId}/activate:
* put:
* summary: Reactivate deactivated user
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: User reactivated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*
* /admin/users/{userId}:
* delete:
* summary: Deactivate user (soft delete)
* tags: [Admin]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: User deactivated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*
* /admin/questions:
* post:
* summary: Create a new question
* tags: [Questions]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - questionText
* - questionType
* - correctAnswer
* - difficulty
* - categoryId
* properties:
* questionText:
* type: string
* questionType:
* type: string
* enum: [multiple_choice, trueFalse, short_answer]
* options:
* type: array
* items:
* type: string
* correctAnswer:
* type: string
* difficulty:
* type: string
* enum: [easy, medium, hard]
* points:
* type: integer
* explanation:
* type: string
* categoryId:
* type: integer
* tags:
* type: array
* items:
* type: string
* keywords:
* type: array
* items:
* type: string
* responses:
* 201:
* description: Question created successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /admin/questions/{id}:
* put:
* summary: Update a question
* tags: [Questions]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* questionText:
* type: string
* options:
* type: array
* items:
* type: string
* correctAnswer:
* type: string
* difficulty:
* type: string
* enum: [easy, medium, hard]
* isActive:
* type: boolean
* responses:
* 200:
* description: Question updated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
* delete:
* summary: Delete a question (soft delete)
* tags: [Questions]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Question deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
// Apply admin rate limiter to all routes
router.use(adminLimiter);
router.put('/users/:userId/role', verifyToken, isAdmin, adminController.updateUserRole);
router.put('/users/:userId/activate', verifyToken, isAdmin, adminController.reactivateUser);
router.delete('/users/:userId', verifyToken, isAdmin, adminController.deactivateUser);
router.get('/questions', verifyToken, isAdmin, questionController.getAllQuestions);
router.get('/questions/:id', verifyToken, isAdmin, questionController.getQuestionByIdAdmin);
router.post('/questions', verifyToken, isAdmin, questionController.createQuestion);
router.put('/questions/:id', verifyToken, isAdmin, questionController.updateQuestion);
router.delete('/questions/:id', verifyToken, isAdmin, questionController.deleteQuestion);
module.exports = router;

201
routes/auth.routes.js Normal file
View File

@@ -0,0 +1,201 @@
const express = require('express');
const router = express.Router();
const authController = require('../controllers/auth.controller');
const { validateRegistration, validateLogin } = require('../middleware/validation.middleware');
const { verifyToken } = require('../middleware/auth.middleware');
const { loginLimiter, registerLimiter, authLimiter } = require('../middleware/rateLimiter');
/**
* @swagger
* /auth/register:
* post:
* summary: Register a new user account
* tags: [Authentication]
* security: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* minLength: 3
* maxLength: 50
* description: Unique username (3-50 characters)
* example: johndoe
* email:
* type: string
* format: email
* description: Valid email address
* example: john@example.com
* password:
* type: string
* minLength: 6
* description: Password (minimum 6 characters)
* example: password123
* responses:
* 201:
* description: User registered successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: User registered successfully
* user:
* type: object
* properties:
* id:
* type: integer
* example: 1
* username:
* type: string
* example: johndoe
* email:
* type: string
* example: john@example.com
* role:
* type: string
* example: user
* token:
* type: string
* description: JWT authentication token
* example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
* 400:
* $ref: '#/components/responses/ValidationError'
* 409:
* description: Username or email already exists
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* message: Username already exists
* 500:
* description: Server error
*/
router.post('/register', registerLimiter, validateRegistration, authController.register);
/**
* @swagger
* /auth/login:
* post:
* summary: Login to user account
* tags: [Authentication]
* security: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* description: Email or username
* example: john@example.com
* password:
* type: string
* description: Account password
* example: password123
* responses:
* 200:
* description: Login successful
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Login successful
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* description: JWT authentication token
* example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
* 400:
* $ref: '#/components/responses/ValidationError'
* 401:
* description: Invalid credentials
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* message: Invalid credentials
* 403:
* description: Account is deactivated
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* message: Account is deactivated
* 500:
* description: Server error
*/
router.post('/login', loginLimiter, validateLogin, authController.login);
/**
* @swagger
* /auth/logout:
* post:
* summary: Logout user (client-side token removal)
* tags: [Authentication]
* security: []
* responses:
* 200:
* description: Logout successful
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Logout successful
*/
router.post('/logout', authLimiter, authController.logout);
/**
* @swagger
* /auth/verify:
* get:
* summary: Verify JWT token and return user information
* tags: [Authentication]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Token is valid
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Token is valid
* user:
* $ref: '#/components/schemas/User'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 500:
* description: Server error
*/
router.get('/verify', authLimiter, verifyToken, authController.verifyToken);
module.exports = router;

142
routes/category.routes.js Normal file
View File

@@ -0,0 +1,142 @@
const express = require('express');
const router = express.Router();
const categoryController = require('../controllers/category.controller');
const authMiddleware = require('../middleware/auth.middleware');
const { cacheCategories, cacheSingleCategory, invalidateCacheMiddleware, invalidateCache } = require('../middleware/cache');
/**
* @swagger
* /categories:
* get:
* summary: Get all active categories
* description: Guest users see only guest-accessible categories, authenticated users see all
* tags: [Categories]
* security: []
* responses:
* 200:
* description: Categories retrieved successfully
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/Category'
* 500:
* description: Server error
* post:
* summary: Create new category
* tags: [Categories]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* properties:
* name:
* type: string
* example: JavaScript Fundamentals
* description:
* type: string
* example: Core JavaScript concepts and syntax
* requiresAuth:
* type: boolean
* default: false
* isActive:
* type: boolean
* default: true
* responses:
* 201:
* description: Category created successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
*
* /categories/{id}:
* get:
* summary: Get category details with question preview and stats
* tags: [Categories]
* security: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Category ID
* responses:
* 200:
* description: Category details retrieved successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Category'
* 404:
* $ref: '#/components/responses/NotFoundError'
* put:
* summary: Update category
* tags: [Categories]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description:
* type: string
* requiresAuth:
* type: boolean
* isActive:
* type: boolean
* responses:
* 200:
* description: Category updated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
* delete:
* summary: Delete category (soft delete)
* tags: [Categories]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Category deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* $ref: '#/components/responses/ForbiddenError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
router.get('/', authMiddleware.optionalAuth, cacheCategories, categoryController.getAllCategories);
router.get('/:id', authMiddleware.optionalAuth, cacheSingleCategory, categoryController.getCategoryById);
router.post('/', authMiddleware.verifyToken, authMiddleware.isAdmin, invalidateCacheMiddleware(() => invalidateCache.category()), categoryController.createCategory);
router.put('/:id', authMiddleware.verifyToken, authMiddleware.isAdmin, invalidateCacheMiddleware((req) => invalidateCache.category(req.params.id)), categoryController.updateCategory);
router.delete('/:id', authMiddleware.verifyToken, authMiddleware.isAdmin, invalidateCacheMiddleware((req) => invalidateCache.category(req.params.id)), categoryController.deleteCategory);
module.exports = router;

175
routes/guest.routes.js Normal file
View File

@@ -0,0 +1,175 @@
const express = require('express');
const router = express.Router();
const guestController = require('../controllers/guest.controller');
const guestMiddleware = require('../middleware/guest.middleware');
const { guestSessionLimiter } = require('../middleware/rateLimiter');
/**
* @swagger
* /guest/start-session:
* post:
* summary: Start a new guest session
* description: Creates a temporary guest session allowing users to try quizzes without registration
* tags: [Guest]
* security: []
* responses:
* 201:
* description: Guest session created successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Guest session created successfully
* guestSession:
* $ref: '#/components/schemas/GuestSession'
* token:
* type: string
* description: Guest session token for subsequent requests
* example: 550e8400-e29b-41d4-a716-446655440000
* settings:
* type: object
* properties:
* maxQuizzes:
* type: integer
* example: 3
* expiryHours:
* type: integer
* example: 24
* 500:
* description: Server error
*
* /guest/session/{guestId}:
* get:
* summary: Get guest session details
* tags: [Guest]
* security: []
* parameters:
* - in: path
* name: guestId
* required: true
* schema:
* type: string
* format: uuid
* description: Guest session ID
* responses:
* 200:
* description: Guest session retrieved successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/GuestSession'
* 404:
* $ref: '#/components/responses/NotFoundError'
*
* /guest/quiz-limit:
* get:
* summary: Check guest quiz limit and remaining quizzes
* tags: [Guest]
* security: []
* parameters:
* - in: header
* name: x-guest-token
* required: true
* schema:
* type: string
* format: uuid
* description: Guest session token
* responses:
* 200:
* description: Quiz limit information retrieved
* content:
* application/json:
* schema:
* type: object
* properties:
* maxQuizzes:
* type: integer
* example: 3
* quizzesCompleted:
* type: integer
* example: 1
* remainingQuizzes:
* type: integer
* example: 2
* limitReached:
* type: boolean
* example: false
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Guest session not found or expired
*
* /guest/convert:
* post:
* summary: Convert guest session to registered user account
* description: Converts guest progress to a new user account, preserving quiz history
* tags: [Guest]
* security: []
* parameters:
* - in: header
* name: x-guest-token
* required: true
* schema:
* type: string
* format: uuid
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - username
* - email
* - password
* properties:
* username:
* type: string
* minLength: 3
* maxLength: 50
* example: johndoe
* email:
* type: string
* format: email
* example: john@example.com
* password:
* type: string
* minLength: 6
* example: password123
* responses:
* 201:
* description: Guest converted to user successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Guest account converted successfully
* user:
* $ref: '#/components/schemas/User'
* token:
* type: string
* description: JWT authentication token
* sessionsTransferred:
* type: integer
* example: 2
* 400:
* $ref: '#/components/responses/ValidationError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Guest session not found or expired
* 409:
* description: Username or email already exists
*/
router.post('/start-session', guestSessionLimiter, guestController.startGuestSession);
router.get('/session/:guestId', guestController.getGuestSession);
router.get('/quiz-limit', guestMiddleware.verifyGuestToken, guestController.checkQuizLimit);
router.post('/convert', guestSessionLimiter, guestMiddleware.verifyGuestToken, guestController.convertGuestToUser);
module.exports = router;

35
routes/question.routes.js Normal file
View File

@@ -0,0 +1,35 @@
const express = require('express');
const router = express.Router();
const questionController = require('../controllers/question.controller');
const { optionalAuth } = require('../middleware/auth.middleware');
/**
* @route GET /api/questions/search
* @desc Search questions using full-text search
* @access Public (with optional auth for more questions)
* @query q - Search query (required)
* @query category - Filter by category UUID (optional)
* @query difficulty - Filter by difficulty (easy, medium, hard) (optional)
* @query limit - Number of results per page (default: 20, max: 100)
* @query page - Page number (default: 1)
*/
router.get('/search', optionalAuth, questionController.searchQuestions);
/**
* @route GET /api/questions/category/:categoryId
* @desc Get questions by category with filtering
* @access Public (with optional auth for more questions)
* @query difficulty - Filter by difficulty (easy, medium, hard)
* @query limit - Number of questions to return (default: 10, max: 50)
* @query random - Boolean to randomize questions (default: false)
*/
router.get('/category/:categoryId', optionalAuth, questionController.getQuestionsByCategory);
/**
* @route GET /api/questions/:id
* @desc Get single question by ID
* @access Public (with optional auth for auth-only questions)
*/
router.get('/:id', optionalAuth, questionController.getQuestionById);
module.exports = router;

251
routes/quiz.routes.js Normal file
View File

@@ -0,0 +1,251 @@
const express = require('express');
const router = express.Router();
const quizController = require('../controllers/quiz.controller');
const { verifyToken } = require('../middleware/auth.middleware');
const { verifyGuestToken } = require('../middleware/guest.middleware');
const { quizLimiter } = require('../middleware/rateLimiter');
/**
* Middleware to handle both authenticated users and guests
* Tries user auth first, then guest auth
*/
const authenticateUserOrGuest = async (req, res, next) => {
// Try to verify user token first
const authHeader = req.headers['authorization'];
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
await new Promise((resolve, reject) => {
verifyToken(req, res, (err) => {
if (err) reject(err);
else resolve();
});
});
if (req.user) {
return next();
}
} catch (error) {
// User auth failed, continue to guest auth
}
}
// Try to verify guest token
const guestToken = req.headers['x-guest-token'];
console.log(guestToken);
if (guestToken) {
try {
await new Promise((resolve, reject) => {
verifyGuestToken(req, res, (err) => {
if (err) reject(err);
else resolve();
});
});
if (req.guestId) {
return next();
}
} catch (error) {
// Guest auth also failed
}
}
// Neither authentication method worked
return res.status(401).json({
success: false,
message: 'Authentication required. Please login or start a guest session.'
});
};
/**
* @swagger
* /quiz/start:
* post:
* summary: Start a new quiz session
* description: Can be used by authenticated users or guest users
* tags: [Quiz]
* security:
* - bearerAuth: []
* parameters:
* - in: header
* name: x-guest-token
* schema:
* type: string
* format: uuid
* description: Guest session token (for guest users)
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - categoryId
* properties:
* categoryId:
* type: integer
* description: Category ID for the quiz
* example: 1
* questionCount:
* type: integer
* minimum: 1
* maximum: 50
* default: 10
* description: Number of questions in quiz
* difficulty:
* type: string
* enum: [easy, medium, hard, mixed]
* default: mixed
* quizType:
* type: string
* enum: [practice, timed, exam]
* default: practice
* responses:
* 201:
* description: Quiz session started successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* session:
* $ref: '#/components/schemas/QuizSession'
* currentQuestion:
* $ref: '#/components/schemas/Question'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Category not found
*
* /quiz/submit:
* post:
* summary: Submit an answer for a quiz question
* tags: [Quiz]
* security:
* - bearerAuth: []
* parameters:
* - in: header
* name: x-guest-token
* schema:
* type: string
* format: uuid
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - quizSessionId
* - questionId
* - userAnswer
* properties:
* quizSessionId:
* type: integer
* questionId:
* type: integer
* userAnswer:
* type: string
* timeSpent:
* type: integer
* description: Time spent on question in seconds
* responses:
* 200:
* description: Answer submitted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Session or question not found
*
* /quiz/complete:
* post:
* summary: Complete a quiz session and get final results
* tags: [Quiz]
* security:
* - bearerAuth: []
* parameters:
* - in: header
* name: x-guest-token
* schema:
* type: string
* format: uuid
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - sessionId
* properties:
* sessionId:
* type: integer
* responses:
* 200:
* description: Quiz completed successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Session not found
*
* /quiz/session/{sessionId}:
* get:
* summary: Get quiz session details with questions and answers
* tags: [Quiz]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: sessionId
* required: true
* schema:
* type: integer
* - in: header
* name: x-guest-token
* schema:
* type: string
* format: uuid
* responses:
* 200:
* description: Session details retrieved successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/QuizSession'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*
* /quiz/review/{sessionId}:
* get:
* summary: Review completed quiz with all answers and explanations
* tags: [Quiz]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: sessionId
* required: true
* schema:
* type: integer
* - in: header
* name: x-guest-token
* schema:
* type: string
* format: uuid
* responses:
* 200:
* description: Quiz review retrieved successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
router.post('/start', quizLimiter, authenticateUserOrGuest, quizController.startQuizSession);
router.post('/submit', authenticateUserOrGuest, quizController.submitAnswer);
router.post('/complete', authenticateUserOrGuest, quizController.completeQuizSession);
router.get('/session/:sessionId', authenticateUserOrGuest, quizController.getSessionDetails);
router.get('/review/:sessionId', authenticateUserOrGuest, quizController.reviewQuizSession);
module.exports = router;

336
routes/user.routes.js Normal file
View File

@@ -0,0 +1,336 @@
const express = require('express');
const router = express.Router();
const userController = require('../controllers/user.controller');
const { verifyToken } = require('../middleware/auth.middleware');
/**
* @swagger
* /users/{userId}/dashboard:
* get:
* summary: Get user dashboard with statistics and recent activity
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* description: User ID
* responses:
* 200:
* description: Dashboard data retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* stats:
* type: object
* properties:
* totalQuizzes:
* type: integer
* example: 25
* completedQuizzes:
* type: integer
* example: 20
* averageScore:
* type: number
* example: 85.5
* totalTimeSpent:
* type: integer
* description: Total time in minutes
* example: 120
* recentSessions:
* type: array
* items:
* $ref: '#/components/schemas/QuizSession'
* categoryPerformance:
* type: array
* items:
* type: object
* properties:
* categoryName:
* type: string
* quizCount:
* type: integer
* averageScore:
* type: number
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* description: Can only access own dashboard
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
router.get('/:userId/dashboard', verifyToken, userController.getUserDashboard);
/**
* @swagger
* /users/{userId}/history:
* get:
* summary: Get user quiz history with pagination and filtering
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* description: User ID
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* maximum: 50
* description: Items per page
* - in: query
* name: category
* schema:
* type: integer
* description: Filter by category ID
* - in: query
* name: status
* schema:
* type: string
* enum: [completed, timeout, abandoned]
* description: Filter by quiz status
* - in: query
* name: startDate
* schema:
* type: string
* format: date-time
* description: Filter by start date (ISO 8601)
* - in: query
* name: endDate
* schema:
* type: string
* format: date-time
* description: Filter by end date (ISO 8601)
* - in: query
* name: sortBy
* schema:
* type: string
* enum: [date, score]
* default: date
* description: Sort by field
* - in: query
* name: sortOrder
* schema:
* type: string
* enum: [asc, desc]
* default: desc
* description: Sort order
* responses:
* 200:
* description: Quiz history retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* quizzes:
* type: array
* items:
* $ref: '#/components/schemas/QuizSession'
* pagination:
* type: object
* properties:
* currentPage:
* type: integer
* totalPages:
* type: integer
* totalItems:
* type: integer
* itemsPerPage:
* type: integer
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* description: Can only access own history
*/
router.get('/:userId/history', verifyToken, userController.getQuizHistory);
/**
* @swagger
* /users/{userId}:
* put:
* summary: Update user profile
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* description: User ID
* requestBody:
* content:
* application/json:
* schema:
* type: object
* properties:
* username:
* type: string
* minLength: 3
* maxLength: 50
* email:
* type: string
* format: email
* currentPassword:
* type: string
* description: Required if changing password
* newPassword:
* type: string
* minLength: 6
* profileImage:
* type: string
* responses:
* 200:
* description: Profile updated successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 403:
* description: Can only update own profile
*/
router.put('/:userId', verifyToken, userController.updateUserProfile);
/**
* @swagger
* /users/{userId}/bookmarks:
* get:
* summary: Get user's bookmarked questions
* tags: [Bookmarks]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* maximum: 50
* - in: query
* name: category
* schema:
* type: integer
* description: Filter by category ID
* - in: query
* name: difficulty
* schema:
* type: string
* enum: [easy, medium, hard]
* - in: query
* name: sortBy
* schema:
* type: string
* enum: [date, difficulty]
* default: date
* - in: query
* name: sortOrder
* schema:
* type: string
* enum: [asc, desc]
* default: desc
* responses:
* 200:
* description: Bookmarks retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* bookmarks:
* type: array
* items:
* $ref: '#/components/schemas/Bookmark'
* pagination:
* type: object
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* post:
* summary: Add a question to bookmarks
* tags: [Bookmarks]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - questionId
* properties:
* questionId:
* type: integer
* description: Question ID to bookmark
* notes:
* type: string
* description: Optional notes about the bookmark
* responses:
* 201:
* description: Bookmark added successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 409:
* description: Question already bookmarked
*
* /users/{userId}/bookmarks/{questionId}:
* delete:
* summary: Remove a question from bookmarks
* tags: [Bookmarks]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: userId
* required: true
* schema:
* type: integer
* - in: path
* name: questionId
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Bookmark removed successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* description: Bookmark not found
*/
router.get('/:userId/bookmarks', verifyToken, userController.getUserBookmarks);
router.post('/:userId/bookmarks', verifyToken, userController.addBookmark);
router.delete('/:userId/bookmarks/:questionId', verifyToken, userController.removeBookmark);
module.exports = router;