add changes
This commit is contained in:
@@ -1,35 +1,419 @@
|
||||
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');
|
||||
|
||||
/**
|
||||
* @route POST /api/admin/questions
|
||||
* @desc Create a new question (Admin only)
|
||||
* @access Admin
|
||||
* @body {
|
||||
* questionText, questionType, options, correctAnswer,
|
||||
* difficulty, points, explanation, categoryId, tags, keywords
|
||||
* }
|
||||
* @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, true_false, 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.post('/questions', verifyToken, isAdmin, questionController.createQuestion);
|
||||
|
||||
/**
|
||||
* @route PUT /api/admin/questions/:id
|
||||
* @desc Update a question (Admin only)
|
||||
* @access Admin
|
||||
* @body {
|
||||
* questionText?, questionType?, options?, correctAnswer?,
|
||||
* difficulty?, points?, explanation?, categoryId?, tags?, keywords?, isActive?
|
||||
* }
|
||||
*/
|
||||
router.put('/questions/:id', verifyToken, isAdmin, questionController.updateQuestion);
|
||||
|
||||
/**
|
||||
* @route DELETE /api/admin/questions/:id
|
||||
* @desc Delete a question - soft delete (Admin only)
|
||||
* @access Admin
|
||||
*/
|
||||
router.delete('/questions/:id', verifyToken, isAdmin, questionController.deleteQuestion);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -3,33 +3,199 @@ 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');
|
||||
|
||||
/**
|
||||
* @route POST /api/auth/register
|
||||
* @desc Register a new user
|
||||
* @access Public
|
||||
* @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', validateRegistration, authController.register);
|
||||
router.post('/register', registerLimiter, validateRegistration, authController.register);
|
||||
|
||||
/**
|
||||
* @route POST /api/auth/login
|
||||
* @desc Login user
|
||||
* @access Public
|
||||
* @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', validateLogin, authController.login);
|
||||
router.post('/login', loginLimiter, validateLogin, authController.login);
|
||||
|
||||
/**
|
||||
* @route POST /api/auth/logout
|
||||
* @desc Logout user (client-side token removal)
|
||||
* @access Public
|
||||
* @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', authController.logout);
|
||||
router.post('/logout', authLimiter, authController.logout);
|
||||
|
||||
/**
|
||||
* @route GET /api/auth/verify
|
||||
* @desc Verify JWT token and return user info
|
||||
* @access Private
|
||||
* @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', verifyToken, authController.verifyToken);
|
||||
router.get('/verify', authLimiter, verifyToken, authController.verifyToken);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -2,40 +2,141 @@ 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');
|
||||
|
||||
/**
|
||||
* @route GET /api/categories
|
||||
* @desc Get all active categories (guest sees only guest-accessible, auth sees all)
|
||||
* @access Public (optional auth)
|
||||
* @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, categoryController.getAllCategories);
|
||||
|
||||
/**
|
||||
* @route GET /api/categories/:id
|
||||
* @desc Get category details with question preview and stats
|
||||
* @access Public (optional auth, some categories require auth)
|
||||
*/
|
||||
router.get('/:id', authMiddleware.optionalAuth, categoryController.getCategoryById);
|
||||
|
||||
/**
|
||||
* @route POST /api/categories
|
||||
* @desc Create new category
|
||||
* @access Private/Admin
|
||||
*/
|
||||
router.post('/', authMiddleware.verifyToken, authMiddleware.isAdmin, categoryController.createCategory);
|
||||
|
||||
/**
|
||||
* @route PUT /api/categories/:id
|
||||
* @desc Update category
|
||||
* @access Private/Admin
|
||||
*/
|
||||
router.put('/:id', authMiddleware.verifyToken, authMiddleware.isAdmin, categoryController.updateCategory);
|
||||
|
||||
/**
|
||||
* @route DELETE /api/categories/:id
|
||||
* @desc Delete category (soft delete)
|
||||
* @access Private/Admin
|
||||
*/
|
||||
router.delete('/:id', authMiddleware.verifyToken, authMiddleware.isAdmin, categoryController.deleteCategory);
|
||||
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;
|
||||
|
||||
@@ -2,33 +2,174 @@ 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');
|
||||
|
||||
/**
|
||||
* @route POST /api/guest/start-session
|
||||
* @desc Start a new guest session
|
||||
* @access Public
|
||||
*/
|
||||
router.post('/start-session', guestController.startGuestSession);
|
||||
|
||||
/**
|
||||
* @route GET /api/guest/session/:guestId
|
||||
* @desc Get guest session details
|
||||
* @access Public
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @route GET /api/guest/quiz-limit
|
||||
* @desc Check guest quiz limit and remaining quizzes
|
||||
* @access Protected (Guest Token Required)
|
||||
*/
|
||||
router.get('/quiz-limit', guestMiddleware.verifyGuestToken, guestController.checkQuizLimit);
|
||||
|
||||
/**
|
||||
* @route POST /api/guest/convert
|
||||
* @desc Convert guest session to registered user account
|
||||
* @access Protected (Guest Token Required)
|
||||
*/
|
||||
router.post('/convert', guestMiddleware.verifyGuestToken, guestController.convertGuestToUser);
|
||||
router.post('/convert', guestSessionLimiter, guestMiddleware.verifyGuestToken, guestController.convertGuestToUser);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
@@ -53,59 +54,196 @@ const authenticateUserOrGuest = async (req, res, next) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @route POST /api/quiz/start
|
||||
* @desc Start a new quiz session
|
||||
* @access Private (User or Guest)
|
||||
* @body {
|
||||
* categoryId: uuid (required),
|
||||
* questionCount: number (1-50, default 10),
|
||||
* difficulty: 'easy' | 'medium' | 'hard' | 'mixed' (default 'mixed'),
|
||||
* quizType: 'practice' | 'timed' | 'exam' (default 'practice')
|
||||
* }
|
||||
*/
|
||||
router.post('/start', authenticateUserOrGuest, quizController.startQuizSession);
|
||||
|
||||
/**
|
||||
* @route POST /api/quiz/submit
|
||||
* @desc Submit an answer for a quiz question
|
||||
* @access Private (User or Guest)
|
||||
* @body {
|
||||
* quizSessionId: uuid (required),
|
||||
* questionId: uuid (required),
|
||||
* userAnswer: string (required),
|
||||
* timeSpent: number (optional, seconds)
|
||||
* }
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @route POST /api/quiz/complete
|
||||
* @desc Complete a quiz session and get final results
|
||||
* @access Private (User or Guest)
|
||||
* @body {
|
||||
* sessionId: uuid (required)
|
||||
* }
|
||||
*/
|
||||
router.post('/complete', authenticateUserOrGuest, quizController.completeQuizSession);
|
||||
|
||||
/**
|
||||
* @route GET /api/quiz/session/:sessionId
|
||||
* @desc Get quiz session details with questions and answers
|
||||
* @access Private (User or Guest)
|
||||
* @params {
|
||||
* sessionId: uuid (required)
|
||||
* }
|
||||
*/
|
||||
router.get('/session/:sessionId', authenticateUserOrGuest, quizController.getSessionDetails);
|
||||
|
||||
/**
|
||||
* @route GET /api/quiz/review/:sessionId
|
||||
* @desc Review completed quiz with all answers, explanations, and visual feedback
|
||||
* @access Private (User or Guest)
|
||||
* @params {
|
||||
* sessionId: uuid (required)
|
||||
* }
|
||||
*/
|
||||
router.get('/review/:sessionId', authenticateUserOrGuest, quizController.reviewQuizSession);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -4,37 +4,333 @@ const userController = require('../controllers/user.controller');
|
||||
const { verifyToken } = require('../middleware/auth.middleware');
|
||||
|
||||
/**
|
||||
* @route GET /api/users/:userId/dashboard
|
||||
* @desc Get user dashboard with stats, recent sessions, and category performance
|
||||
* @access Private (User - own dashboard only)
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @route GET /api/users/:userId/history
|
||||
* @desc Get user quiz history with pagination, filtering, and sorting
|
||||
* @query page - Page number (default: 1)
|
||||
* @query limit - Items per page (default: 10, max: 50)
|
||||
* @query category - Filter by category ID
|
||||
* @query status - Filter by status (completed, timeout, abandoned)
|
||||
* @query startDate - Filter by start date (ISO 8601)
|
||||
* @query endDate - Filter by end date (ISO 8601)
|
||||
* @query sortBy - Sort by field (date, score) (default: date)
|
||||
* @query sortOrder - Sort order (asc, desc) (default: desc)
|
||||
* @access Private (User - own history only)
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @route PUT /api/users/:userId
|
||||
* @desc Update user profile
|
||||
* @body username - New username (optional)
|
||||
* @body email - New email (optional)
|
||||
* @body currentPassword - Current password (required if changing password)
|
||||
* @body newPassword - New password (optional)
|
||||
* @body profileImage - Profile image URL (optional)
|
||||
* @access Private (User - own profile only)
|
||||
* @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;
|
||||
|
||||
Reference in New Issue
Block a user