79 KiB
Backend Development Tasks - Interview Quiz Application
Project Setup Phase
Task 1: Project Initialization
Priority: High | Status: ✅ Completed | Estimated Time: 1-2 hours
Subtasks:
- Create backend folder structure
- Initialize Node.js project with
npm init - Install core dependencies
npm install express sequelize mysql2 dotenv bcrypt jsonwebtoken npm install express-validator cors helmet morgan npm install --save-dev nodemon jest supertest - Install Sequelize CLI globally:
npm install -g sequelize-cli - Create
.gitignorefile - Create
.env.examplefile with all required variables - Setup basic Express server in
server.js - Configure port and basic middleware (CORS, JSON parser, helmet)
Acceptance Criteria:
- ✅ Server starts successfully on specified port
- ✅ Environment variables load correctly
- ✅ Basic middleware functions properly
Task 2: Database Setup
Priority: High | Status: ✅ Completed | Estimated Time: 2-3 hours
Subtasks:
- Install MySQL 8.0+ locally
- Create database:
interview_quiz_db - Initialize Sequelize:
npx sequelize-cli init - Create
.sequelizercconfiguration file - Configure
config/database.jswith connection settings - Test database connection
- Setup connection pooling configuration
- Create
models/index.jsfor model initialization
Acceptance Criteria:
- ✅ Database connection successful
- ✅ Sequelize properly configured
- ✅ Connection pool working
- ✅ Can execute test query
Files Created:
- ✅
.sequelizerc - ✅
config/database.js - ✅
config/db.js - ✅
models/index.js - ✅
test-db-connection.js
Task 3: Environment Configuration
Priority: High | Status: ✅ Completed | Estimated Time: 1 hour
Subtasks:
- Create
.envfile from.env.example - Configure database credentials
- Generate JWT secret key
- Setup NODE_ENV variables
- Configure API prefix
- Add rate limiting configuration
- Add Redis configuration (optional for caching)
Acceptance Criteria:
- ✅ All required environment variables configured
- ✅ Secure JWT secret generated (128 characters)
- ✅ Environment validation passes
- ✅ Configuration centralized in config module
- ✅ Server validates environment on startup
Files Created:
- ✅
.env(configured with all variables) - ✅
generate-jwt-secret.js(JWT secret generator) - ✅
validate-env.js(environment validator) - ✅
config/config.js(centralized configuration) - ✅
ENVIRONMENT_GUIDE.md(complete documentation)
Database Schema Phase
Task 4: Create User Model & Migration
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Create migration:
npx sequelize-cli migration:generate --name create-users - Define users table schema with UUID primary key
- Add all user fields (username, email, password, role, stats)
- Add indexes for email, username, role
- Create
models/User.jsSequelize model - Add model validations
- Add password hashing hooks (beforeCreate, beforeUpdate)
- Test migration:
npx sequelize-cli db:migrate
Reference:
See SAMPLE_MIGRATIONS.md - Migration 1
Acceptance Criteria:
- ✅ Migration runs successfully
- ✅ Users table created with all fields
- ✅ Indexes applied (email, username, role, is_active, created_at)
- ✅ Model validations work (email, username, password)
- ✅ Password auto-hashing on create/update
- ✅ UUID primary key generation
- ✅ Helper methods (comparePassword, calculateAccuracy, etc.)
Files Created:
- ✅
migrations/20251109214253-create-users.js - ✅
models/User.js - ✅
test-user-model.js
Task 5: Create Categories Model & Migration
Priority: High | Status: ✅ Completed | Estimated Time: 1.5 hours
Subtasks:
- Create migration:
npx sequelize-cli migration:generate --name create-categories - Define categories table schema
- Add guest_accessible and count fields
- Add indexes for slug, is_active, guest_accessible
- Create
models/Category.jsSequelize model - Add slug generation hook
- Test migration
Reference:
See SAMPLE_MIGRATIONS.md - Migration 2
Acceptance Criteria:
- ✅ Categories table created with 13 fields and 6 indexes
- ✅ Slug auto-generation works (beforeValidate, beforeCreate, beforeUpdate hooks)
- ✅ Model associations defined (Question, QuizSession, GuestSettings)
- ✅ Helper methods implemented (incrementQuestionCount, findBySlug, etc.)
- ✅ All 15 tests passing
Files Created:
- ✅
migrations/20251109214935-create-categories.js - ✅
models/Category.js(255 lines) - ✅
test-category-model.js(15 comprehensive tests)
Task 6: Create Questions Model & Migration
Priority: High | Status: ✅ Completed | Estimated Time: 3 hours
Subtasks:
- Create migration:
npx sequelize-cli migration:generate --name create-questions - Define questions table with JSON columns
- Add foreign key to categories
- Add foreign key to users (created_by)
- Add multiple indexes
- Add full-text index for search
- Create
models/Question.jsSequelize model - Handle JSON serialization for options, keywords, tags
- Add model associations (belongsTo Category, belongsTo User)
- Test migration
Reference:
See SAMPLE_MIGRATIONS.md - Migration 3
Acceptance Criteria:
- ✅ Questions table created with 20 fields including JSON columns (options, keywords, tags)
- ✅ Full-text search index created on question_text and explanation
- ✅ Foreign keys enforced (category_id RESTRICT, created_by SET NULL)
- ✅ JSON fields serialize/deserialize correctly with custom getters/setters
- ✅ 10 indexes created including composite indexes for query optimization
- ✅ Model validations implemented (question type, options, correct answer format)
- ✅ Helper methods: incrementAttempted, incrementCorrect, getAccuracy, toSafeJSON
- ✅ Class methods: findActiveQuestions, searchQuestions, getRandomQuestions, getQuestionsByCategory
- ✅ Auto-set points based on difficulty (easy: 10, medium: 20, hard: 30)
- ✅ All 18 tests passing
Files Created:
- ✅
migrations/20251109220030-create-questions.js - ✅
models/Question.js(455 lines) - ✅
test-question-model.js(18 comprehensive tests)
Task 7: Create Guest Sessions Model & Migration
Priority: High | Status: ✅ Completed | Estimated Time: 1.5 hours
Subtasks:
- ✅ Create migration:
npx sequelize-cli migration:generate --name create-guest-sessions - ✅ Define guest_sessions table (14 fields, 7 indexes)
- ✅ Add indexes for guest_id (unique), session_token (unique), expires_at, is_converted, converted_user_id, device_id, created_at
- ✅ Create
models/GuestSession.jsmodel (340+ lines) - ✅ Add JWT token generation and verification methods
- ✅ Add expiry validation and session extension
- ✅ Add guest-to-user conversion tracking
- ✅ Test migration (20 comprehensive tests - all passing)
Reference:
See SAMPLE_MIGRATIONS.md - Migration 4
Acceptance Criteria:
- ✅ Guest sessions table created with 14 fields
- ✅ JWT token generation works (format: guest_{timestamp}_{random})
- ✅ Expiry check functional with configurable duration
- ✅ Quiz limit tracking (default 3 quizzes per guest)
- ✅ Session extension capability (default 24 hours)
- ✅ Guest-to-user conversion tracking with foreign key
- ✅ Analytics methods: active count, conversion rate
- ✅ Test suite: 20 tests covering all functionality
Files Created:
- ✅
migrations/20251109221034-create-guest-sessions.js - ✅
models/GuestSession.js(340+ lines with JWT management) - ✅
test-guest-session-model.js(20 comprehensive tests) - ✅ Added test script:
npm run test:guest
Task 8: Create Quiz Sessions Model & Migration
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- ✅ Create migration:
npx sequelize-cli migration:generate --name create-quiz-sessions - ✅ Define quiz_sessions table (21 fields, 11 indexes)
- ✅ Add foreign keys (user_id, guest_session_id, category_id) with proper CASCADE/SET NULL/RESTRICT
- ✅ Add indexes for user_id, guest_session_id, category_id, status, quiz_type, started_at, completed_at, created_at, is_passed
- ✅ Add composite indexes for user_status and guest_status
- ✅ Create
models/QuizSession.jsmodel (650+ lines) - ✅ Add associations (belongsTo User, GuestSession, Category; hasMany QuizAnswer, QuizSessionQuestion)
- ✅ Implement quiz lifecycle methods (start, complete, abandon, timeout)
- ✅ Implement scoring and progress tracking
- ✅ Add validation (require either userId or guestSessionId, not both)
- ✅ Test migration (26 comprehensive tests - all passing)
Reference:
See SAMPLE_MIGRATIONS.md - Migration 5
Acceptance Criteria:
- ✅ Quiz sessions table created with 21 fields
- ✅ Support for both user and guest quizzes
- ✅ Multiple quiz types: practice, timed, exam
- ✅ Difficulty levels: easy, medium, hard, mixed
- ✅ Quiz lifecycle management (start, in_progress, complete, abandon, timeout)
- ✅ Automatic score calculation and pass/fail determination
- ✅ Time tracking with timeout support for timed quizzes
- ✅ Progress tracking (questions answered, remaining, accuracy)
- ✅ Statistics methods: user stats, category stats, history
- ✅ Cleanup method for abandoned sessions
- ✅ Test suite: 26 tests covering all functionality
Files Created:
- ✅
migrations/20251110190953-create-quiz-sessions.js - ✅
models/QuizSession.js(650+ lines with comprehensive quiz management) - ✅
test-quiz-session-model.js(26 comprehensive tests) - ✅ Added test script:
npm run test:quiz
Task 9: Create Quiz Answers & Junction Tables
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- ✅ Create quiz_answers table migration (9 fields, 5 indexes)
- ✅ Create quiz_session_questions junction table migration (5 fields, 4 indexes)
- ✅ Create user_bookmarks junction table migration (5 fields, 4 indexes)
- ✅ Create achievements table migration (13 fields, 5 indexes)
- ✅ Create user_achievements junction table migration (6 fields, 5 indexes)
- ✅ Run all migrations successfully
- ✅ Verify foreign keys and cascade rules
Acceptance Criteria:
- ✅ Quiz answers table created - stores individual answers during quizzes
- ✅ Quiz session questions junction table - links quizzes with questions in order
- ✅ User bookmarks junction table - allows users to save questions with notes
- ✅ Achievements table - defines available achievements with requirements
- ✅ User achievements junction table - tracks earned achievements
- ✅ All junction tables have unique composite indexes
- ✅ Foreign key constraints properly enforced (CASCADE on delete/update)
- ✅ All tables use UTF8MB4 charset for full Unicode support
Files Created:
- ✅
migrations/20251110191735-create-quiz-answers.js- Quiz answers table - ✅
migrations/20251110191906-create-quiz-session-questions.js- Quiz-question junction - ✅
migrations/20251110192000-create-user-bookmarks.js- User bookmarks junction - ✅
migrations/20251110192043-create-achievements.js- Achievements definitions - ✅
migrations/20251110192130-create-user-achievements.js- User-achievement junction
Database Schema Summary:
quiz_answers: Stores each answer given during a quiz
- Links to quiz_sessions and questions
- Tracks correct/incorrect, points earned, time taken
- Unique constraint: one answer per question per session
quiz_session_questions: Links quiz sessions with questions
- Maintains question order in quiz
- Enables loading quiz questions in sequence
user_bookmarks: User-saved questions for review
- Optional notes field for user annotations
- Prevents duplicate bookmarks
achievements: Gamification system
- 6 categories: quiz, streak, score, speed, milestone, special
- 8 requirement types: quizzes_completed, quizzes_passed, perfect_score, streak_days, etc.
- Configurable points and display order
user_achievements: Tracks earned achievements
- Notification tracking
- Prevents duplicate awards
Task 10: Database Seeding
Priority: Medium | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Create seeder for demo categories (7 categories)
- Create seeder for admin user (admin@quiz.com)
- Create seeder for sample questions (35 questions - 5 per category)
- Create seeder for achievements (19 achievements across 6 categories)
- Run all seeders:
npx sequelize-cli db:seed:all - Test data integrity
Reference:
See SAMPLE_MIGRATIONS.md - Seeders section
Acceptance Criteria:
- ✅ All seed data inserted (7 categories, 1 admin user, 35 questions, 19 achievements)
- ✅ Relationships maintained (questions linked to categories, created_by admin user)
- ✅ Admin credentials: admin@quiz.com / Admin@123
- ✅ Guest-accessible categories: JavaScript, Angular, React (3/7)
- ✅ Auth-only categories: Node.js, TypeScript, SQL & Databases, System Design (4/7)
Authentication & Authorization Phase
Task 11: User Registration Endpoint
Priority: High | Status: ✅ Completed | Estimated Time: 3 hours
Subtasks:
- Create
routes/auth.routes.js - Create
controllers/auth.controller.js - Implement
registercontroller function - Add input validation (email, password strength, username)
- Check for duplicate email/username
- Hash password with bcrypt (via User model hook)
- Generate JWT token
- Handle guest migration (optional guestSessionId)
- Add error handling with transactions
- Write middleware:
validation.middleware.jsandauth.middleware.js
API Endpoint:
POST /api/auth/register
Body: {
username, email, password, guestSessionId (optional)
}
Reference:
See interview_quiz_user_story.md - User Story 1.1
Acceptance Criteria:
- ✅ User registered successfully with JWT token
- ✅ Password hashed automatically by User model beforeCreate hook
- ✅ JWT token returned with user data (password excluded)
- ✅ Duplicate emails/usernames rejected with 400 status
- ✅ Input validation works (username 3-50 chars, email format, password min 8 chars with uppercase/lowercase/number)
- ✅ Guest migration supported with transaction rollback on errors
- ✅ Login endpoint implemented
- ✅ Token verification endpoint implemented
- ✅ Logout endpoint implemented
- ✅ Auth middleware created (verifyToken, isAdmin, isOwnerOrAdmin)
Task 12: User Login Endpoint
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Implement
logincontroller function - Validate email and password
- Compare password hash
- Generate JWT token
- Update last_login timestamp
- Return user data (exclude password)
- Add rate limiting
- Write unit tests
API Endpoint:
POST /api/auth/login
Body: { email, password }
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - User Operations
Acceptance Criteria:
- ✅ Login successful with valid credentials
- ✅ JWT token generated with 24h expiration
- ✅ Invalid credentials rejected with 401 status
- ✅ Password comparison using User.comparePassword() method
- ✅ User data returned (password excluded via toSafeJSON)
- ✅ Only active users can login (isActive check)
- ✅ Email normalized to lowercase for case-insensitive login
- ✅ Tests included in auth.test.js and logout-verify.test.js
- ⏳ Rate limiting (pending - to be added in Task 44)
Task 13: JWT Authentication Middleware
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Create
middleware/auth.middleware.js - Implement
verifyTokenmiddleware - Extract token from Authorization header
- Verify JWT signature
- Attach user to request object
- Handle expired tokens
- Handle invalid tokens
- Create
isAdminmiddleware - Write tests
Acceptance Criteria:
- ✅ Protected routes require valid token (Bearer format)
- ✅ User data available in req.user (userId, email, username, role)
- ✅ Expired tokens rejected with 401 status
- ✅ Admin-only routes protected with isAdmin middleware
- ✅ isOwnerOrAdmin middleware created for resource ownership checks
- ✅ Proper error handling (TokenExpiredError, JsonWebTokenError)
- ✅ Used in auth routes (GET /api/auth/verify)
- ✅ Tests included in auth.test.js and logout-verify.test.js
Task 14: User Logout & Token Verification
Priority: Medium | Status: ✅ Completed | Estimated Time: 1 hour
Subtasks:
- Implement
logoutendpoint (client-side token removal) - Implement
verifyTokenendpoint - Return user info if token valid
- Write tests
API Endpoints:
POST /api/auth/logout
GET /api/auth/verify
Acceptance Criteria:
- ✅ Logout endpoint returns success (stateless JWT approach)
- ✅ Token verification validates JWT and returns user data
- ✅ Password excluded from response (toSafeJSON method)
- ✅ Invalid tokens rejected with 401 status
- ✅ Missing tokens rejected with 401 status
- ✅ Expired tokens rejected with 401 status
- ✅ Inactive users rejected with 404 status
- ✅ Comprehensive test suite created (15+ tests)
- ✅ Manual test script created for verification
- ✅ Token still valid after logout (client-side token removal pattern)
Guest User Management Phase
Task 15: Guest Session Creation ✅
Priority: High | Status: Completed | Estimated Time: 2 hours
Subtasks:
- Create
routes/guest.routes.js - Create
controllers/guest.controller.js - Implement
startGuestSessionfunction - Generate unique guest_id (
guest_{timestamp}_{random}) - Generate session token (JWT with 24h expiry)
- Set expiry (24 hours default, configurable)
- Store IP address and user agent
- Return available categories (JavaScript, Angular, React)
- Return quiz restrictions (max 3 quizzes, feature flags)
- Write tests (7 test scenarios, all passing)
API Endpoints:
POST /api/guest/start-session
Body: { deviceId? }
Response: { guestId, sessionToken, expiresAt, restrictions, availableCategories }
GET /api/guest/session/:guestId
Response: { guestId, expiresAt, expiresIn, restrictions, availableCategories }
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Guest Session Operations
Acceptance Criteria:
- ✅ Guest session created with unique ID
- ✅ Token generated and valid for 24h
- ✅ Guest restrictions applied (max 3 quizzes)
- ✅ Feature restrictions set (no bookmarks, no progress tracking)
- ✅ Guest-accessible categories returned (JavaScript, Angular, React)
- ✅ Session retrieval and validation working
- ✅ All 7 tests passing
Task 16: Guest Quiz Limit Check ✅
Priority: High | Status: Completed | Estimated Time: 1.5 hours
Subtasks:
- Create guest authentication middleware (
middleware/guest.middleware.js) - Implement
verifyGuestTokenmiddleware with session validation - Implement
checkQuizLimitfunction in controller - Verify guest session exists and not expired (middleware)
- Check quizzes_attempted vs max_quizzes
- Return remaining quizzes and calculations
- Calculate and return reset time (time until session expiry)
- Return upgrade prompt when limit reached
- Write comprehensive tests (8 scenarios)
API Endpoint:
GET /api/guest/quiz-limit
Headers: { X-Guest-Token: <token> }
Response: {
guestId,
quizLimit: { maxQuizzes, quizzesAttempted, quizzesRemaining, hasReachedLimit },
session: { expiresAt, timeRemaining, resetTime },
upgradePrompt?: { message, benefits[], callToAction }
}
Acceptance Criteria:
- ✅ Guest token middleware validates JWT and session
- ✅ Middleware checks session exists, not expired, not converted
- ✅ Quiz limit calculation accurate (max - attempted)
- ✅ Time remaining calculated correctly (hours and minutes)
- ✅ Upgrade prompt shown when limit reached (5 benefits listed)
- ✅ Proper error handling (401, 404, 410 status codes)
- ✅ All 8 tests passing (valid token, no token, invalid token, non-existent guest, structure validation, calculations, limit reached scenario)
Task 17: Guest to User Conversion ✅
Priority: Medium | Status: Completed | Estimated Time: 3 hours
Subtasks:
- Review existing guest migration logic in auth registration
- Create standalone
convertGuestToUserendpoint - Add guest middleware protection (verifies guest session)
- Implement transaction-based data migration
- Create new user account with password hashing
- Migrate quiz sessions from guest to user
- Calculate and update user stats from migrated sessions
- Mark guest session as converted (isConverted=true)
- Handle duplicate email/username validation
- Handle rollback on error
- Generate JWT token for new user
- Write comprehensive tests (10 scenarios)
API Endpoint:
POST /api/guest/convert
Headers: { X-Guest-Token: <token> }
Body: { username, email, password }
Response: {
user: { id, username, email, role },
token: <JWT>,
migration: {
quizzesTransferred: number,
stats: { totalQuizzes, quizzesPassed, accuracy }
}
}
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Transaction Example
Acceptance Criteria:
- ✅ Guest session validated via middleware (must not be expired or converted)
- ✅ Input validation (username alphanumeric 3-50 chars, valid email, password 8+ chars)
- ✅ Duplicate email/username checks with 400 status
- ✅ User created with transaction rollback on error
- ✅ Quiz sessions migrated from guestSessionId to userId
- ✅ User stats calculated from migrated completed quizzes
- ✅ Guest session marked as converted with convertedUserId
- ✅ JWT token generated for immediate login
- ✅ Already converted sessions rejected with 410 status
- ✅ Converted user can login with new credentials
- ✅ All 10 tests passing (validation, success, duplicates, converted session, login)
Notes:
- Guest migration also supported in registration endpoint (Task 11) via optional guestSessionId parameter
- This standalone endpoint allows guests to convert without re-entering data
- Transaction ensures atomic operation - either all data migrates or nothing changes
- Password automatically hashed by User model beforeCreate hook
Category Management Phase
Task 18: Get All Categories ✅
Priority: High | Status: Completed | Estimated Time: 2 hours
Subtasks:
- Create
routes/category.routes.js - Create
controllers/category.controller.js - Create optional auth middleware (
optionalAuth) - Implement
getAllCategoriesfunction - Filter by isActive
- Include questionCount from model
- Handle guest vs registered user view (guestAccessible filter)
- Order by displayOrder and name
- Write comprehensive tests (7 scenarios)
- Add caching (Redis optional - deferred)
API Endpoint:
GET /api/categories
Headers: { Authorization: Bearer <token> } (optional)
Response: {
success: true,
count: number,
data: [{ id, name, slug, description, icon, color, questionCount, displayOrder, guestAccessible }],
message: string
}
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Category Operations
Acceptance Criteria:
- ✅ Public endpoint accessible without authentication
- ✅ Optional auth middleware attaches user if token provided
- ✅ Guest users see only guest-accessible categories (3: JavaScript, Angular, React)
- ✅ Authenticated users see all active categories (7 total, including Node.js, TypeScript, SQL, System Design)
- ✅ Only active categories returned (isActive=true)
- ✅ Categories ordered by displayOrder, then name
- ✅ Response includes questionCount for each category
- ✅ Proper response structure with success, count, data, message
- ✅ All 7 tests passing (guest view, auth view, filtering, ordering, structure validation)
Notes:
- Created
optionalAuthmiddleware in auth.middleware.js for public endpoints with optional authentication - Guest users see 3 categories, authenticated users see 7 (4 additional auth-only categories)
- Redis caching deferred to Task 45 (Database Optimization)
Task 19: Get Category Details ✅
Priority: Medium | Status: Completed | Estimated Time: 1 hour
Subtasks:
- Implement
getCategoryByIdfunction - Include related questions preview (first 5 questions)
- Return category stats (difficulty breakdown, accuracy)
- Write tests (9 comprehensive scenarios)
- Add UUID validation for category IDs
- Implement guest vs authenticated access control
API Endpoint:
GET /api/categories/:id
Headers: { Authorization: Bearer <token> } (optional)
Response: {
success: true,
data: {
category: { id, name, slug, description, icon, color, questionCount, displayOrder, guestAccessible },
questionPreview: [{ id, questionText, questionType, difficulty, points, accuracy }],
stats: {
totalQuestions,
questionsByDifficulty: { easy, medium, hard },
totalAttempts,
totalCorrect,
averageAccuracy
}
},
message: string
}
Acceptance Criteria:
- ✅ Category retrieved by UUID (not integer ID)
- ✅ Question preview limited to 5 questions ordered by creation date
- ✅ Stats calculated from all active questions in category
- ✅ Guest users can access guest-accessible categories
- ✅ Guest users blocked from auth-only categories with 403 status
- ✅ Authenticated users can access all active categories
- ✅ Invalid UUID format returns 400 status
- ✅ Non-existent category returns 404 status
- ✅ Question accuracy calculated per question
- ✅ Difficulty breakdown shows easy/medium/hard counts
- ✅ All 9 tests passing
Notes:
- Categories use UUID primary keys, not integers
- UUID validation accepts format:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Question displayOrder column doesn't exist, ordered by createdAt instead
- Test revealed login response structure:
response.data.data.token
Task 20: Create/Update/Delete Category (Admin) ✅
Priority: Medium | Status: Completed | Estimated Time: 3 hours
Subtasks:
- Implement
createCategory(admin only) - Auto-generate slug from name (via model hook)
- Validate unique slug and name
- Implement
updateCategory(admin only) - Implement
deleteCategory(soft delete) - Update guest_accessible flag
- Add authorization middleware (verifyToken + isAdmin)
- Write comprehensive tests (14 scenarios)
API Endpoints:
POST /api/categories (admin)
Body: { name, slug?, description?, icon?, color?, guestAccessible?, displayOrder? }
Response: { success, data: { id, name, slug, ... }, message }
PUT /api/categories/:id (admin)
Body: { name?, slug?, description?, icon?, color?, guestAccessible?, displayOrder?, isActive? }
Response: { success, data: { id, name, slug, ... }, message }
DELETE /api/categories/:id (admin)
Response: { success, data: { id, name, questionCount }, message }
Acceptance Criteria:
- ✅ Admin can create new categories with all fields
- ✅ Slug auto-generated from name if not provided
- ✅ Custom slug supported with uniqueness validation
- ✅ Duplicate name/slug rejected with 400 status
- ✅ Missing required name field rejected with 400 status
- ✅ Admin can update any category field
- ✅ Update validates name/slug uniqueness
- ✅ Non-existent category returns 404 status
- ✅ Soft delete sets isActive to false (not physical delete)
- ✅ Deleted category not shown in active category list
- ✅ Already deleted category cannot be deleted again
- ✅ Question count included in delete response
- ✅ Non-admin users blocked from all operations (403 status)
- ✅ Unauthenticated requests blocked (401 status)
- ✅ All 14 tests passing
Notes:
- Uses existing
isAdminmiddleware for authorization - Soft delete preserves data integrity (questions still reference category)
- Slug generation handled by Category model beforeValidate hook
- Default color is '#3B82F6' (blue) if not provided
- displayOrder defaults to 0 if not provided
Question Management Phase
Task 21: Get Questions by Category ✅
Priority: High | Status: Completed | Estimated Time: 2 hours
Subtasks:
- Create
routes/question.routes.js - Create
controllers/question.controller.js - Implement
getQuestionsByCategoryfunction - Filter by difficulty (optional: easy, medium, hard)
- Filter by visibility (guest vs authenticated user)
- Random selection support (random=true query param)
- Pagination support (limit parameter, max 50)
- Write comprehensive tests (14 scenarios)
API Endpoint:
GET /api/questions/category/:categoryId?difficulty=easy&limit=10&random=true
Headers: { Authorization: Bearer <token> } (optional)
Response: {
success: true,
count: number,
total: number,
category: { id, name, slug, icon, color },
filters: { difficulty, limit, random },
data: [{
id, questionText, questionType, options,
difficulty, points, timesAttempted, timesCorrect,
accuracy, explanation, tags, createdAt,
category: { id, name, slug, icon, color }
}],
message: string
}
Acceptance Criteria:
- ✅ Public endpoint with optional authentication
- ✅ Guest users can access guest-accessible categories (JavaScript, Angular, React)
- ✅ Guest users blocked from auth-only categories with 403 status
- ✅ Authenticated users can access all active categories
- ✅ UUID validation for category IDs (400 for invalid format)
- ✅ Non-existent category returns 404 status
- ✅ Difficulty filter works (easy, medium, hard)
- ✅ Invalid difficulty values ignored (defaults to 'all')
- ✅ Limit parameter enforced (default 10, max 50)
- ✅ Random selection works (random=true query param)
- ✅ Combined filters work (difficulty + limit + random)
- ✅ Question accuracy calculated (timesCorrect / timesAttempted * 100)
- ✅ Correct answer NOT exposed in response
- ✅ Category info included in response
- ✅ Total count returned (with filters applied)
- ✅ All 14 tests passing
Implementation Notes:
- Uses
optionalAuthmiddleware for public access with auth benefits - UUID regex validation:
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i - Random ordering via
sequelize.random() - Default ordering by
createdAt ASCwhen random=false - Limit validation:
Math.min(Math.max(parseInt(limit) || 10, 1), 50) - Accuracy calculation per question with 0 default for unattempted questions
- Category association included via
includewith alias 'category' - correctAnswer explicitly excluded from response attributes
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Question Operations
Task 22: Get Question by ID ✅
Priority: High | Status: Completed | Estimated Time: 1 hour
Subtasks:
- Implement
getQuestionByIdfunction - Check visibility permissions (guest vs authenticated)
- Don't expose correct_answer in response
- Include category info with association
- Write comprehensive tests (12 scenarios)
API Endpoint:
GET /api/questions/:id
Headers: { Authorization: Bearer <token> } (optional)
Response: {
success: true,
data: {
id, questionText, questionType, options,
difficulty, points, explanation, tags, keywords,
accuracy, createdAt, updatedAt,
statistics: { timesAttempted, timesCorrect, accuracy },
category: { id, name, slug, icon, color, guestAccessible }
},
message: string
}
Acceptance Criteria:
- ✅ Public endpoint with optional authentication
- ✅ UUID validation for question IDs (400 for invalid format)
- ✅ Non-existent question returns 404 status
- ✅ Guest users can access guest-accessible questions
- ✅ Guest users blocked from auth-only questions with 403 status
- ✅ Authenticated users can access all active questions
- ✅ Inactive category questions return 404 status
- ✅ Correct answer NOT exposed in response
- ✅ Category information included via association
- ✅ Question accuracy calculated (timesCorrect / timesAttempted * 100)
- ✅ Statistics object included (timesAttempted, timesCorrect, accuracy)
- ✅ All question types supported (multiple, trueFalse, written)
- ✅ Options array present for multiple choice questions
- ✅ Tags and keywords fields included (can be null or array)
- ✅ Points validated by difficulty (easy=5, medium=10, hard=15)
- ✅ All 12 tests passing
Implementation Notes:
- Added
getQuestionByIdfunction to question.controller.js - Uses
optionalAuthmiddleware for public access with auth benefits - UUID regex validation:
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i - Category association includes guestAccessible flag for access control
- correctAnswer explicitly excluded from query attributes
- Statistics object added for clearer attempt/success tracking
- Category's isActive flag removed from response (internal use only)
- Route added to question.routes.js as
GET /:id
Task 23: Question Search (Full-Text) ✅
Priority: Medium | Status: Completed | Estimated Time: 2 hours
Subtasks:
- Implement
searchQuestionsfunction - Use MySQL MATCH AGAINST for full-text search
- Filter by category (optional UUID)
- Filter by difficulty (optional: easy, medium, hard)
- Highlight matching text with ** markers
- Pagination support with page and limit
- Write comprehensive tests (14 scenarios)
API Endpoint:
GET /api/questions/search?q=javascript&category=uuid&difficulty=medium&limit=20&page=1
Headers: { Authorization: Bearer <token> } (optional)
Response: {
success: true,
count: number,
total: number,
page: number,
totalPages: number,
limit: number,
query: string,
filters: { category: uuid|null, difficulty: string|null },
data: [{
id, questionText, highlightedText, questionType, options,
difficulty, points, accuracy, explanation, tags,
relevance, createdAt,
category: { id, name, slug, icon, color }
}],
message: string
}
Acceptance Criteria:
- ✅ Full-text search using MySQL MATCH AGAINST on question_text and explanation
- ✅ Search query required (400 if missing or empty)
- ✅ Guest users see only guest-accessible category results
- ✅ Authenticated users see all category results
- ✅ Category filter by UUID (optional, validated)
- ✅ Difficulty filter (easy, medium, hard) (optional)
- ✅ Combined filters work (category + difficulty)
- ✅ Invalid category UUID returns 400
- ✅ Pagination support with page and limit parameters
- ✅ Default limit 20, max limit 100
- ✅ Results ordered by relevance DESC, then createdAt DESC
- ✅ Relevance score included in each result
- ✅ Text highlighting applied with ** markers for matched terms
- ✅ Response includes total, totalPages, page metadata
- ✅ Correct answer NOT exposed in results
- ✅ All 14 tests passing
Implementation Notes:
- Added
searchQuestionsfunction to question.controller.js (240+ lines) - Uses raw SQL with
sequelize.query()for MATCH AGAINST full-text search - Full-text index exists on questions(question_text, explanation)
- Text highlighting helper function splits search term into words
- Words shorter than 3 characters excluded from highlighting
- Pagination uses LIMIT and OFFSET for efficient paging
- Two queries: one for results, one for total count
- Route added as
GET /search(must come before/:idto avoid conflicts) - Guest accessibility checked via category join in WHERE clause
- JSON fields (options, tags) parsed from database strings
- Accuracy calculated per question (timesCorrect / timesAttempted * 100)
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Search Questions
Task 24: Create Question (Admin) ✅
Priority: Medium | Status: Completed | Estimated Time: 3 hours
Subtasks:
- Implement
createQuestion(admin only) - Validate question type (multiple/trueFalse/written)
- Validate options for multiple choice (2-6 options)
- Store options, keywords, tags as JSON
- Auto-calculate points based on difficulty
- Validate category exists and is active
- Increment category question count
- Write comprehensive tests (16 scenarios)
API Endpoint:
POST /api/admin/questions
Headers: { Authorization: Bearer <admin-token> }
Body: {
questionText: string (required),
questionType: 'multiple' | 'trueFalse' | 'written' (required),
options: [{ id, text }] (required for multiple choice),
correctAnswer: string (required),
difficulty: 'easy' | 'medium' | 'hard' (required),
points: number (optional, auto-calculated if not provided),
explanation: string (optional),
categoryId: uuid (required),
tags: string[] (optional),
keywords: string[] (optional)
}
Response: {
success: true,
data: {
id, questionText, questionType, options,
difficulty, points, explanation, tags, keywords,
category: { id, name, slug, icon, color },
createdAt
},
message: "Question created successfully"
}
Acceptance Criteria:
- ✅ Admin-only access (verifyToken + isAdmin middleware)
- ✅ Non-admin users blocked with 403 status
- ✅ Unauthenticated requests blocked with 401 status
- ✅ Required fields validated (questionText, questionType, correctAnswer, difficulty, categoryId)
- ✅ Question type validated (multiple, trueFalse, written)
- ✅ Difficulty validated (easy, medium, hard)
- ✅ Category UUID format validated
- ✅ Category existence checked (404 if not found)
- ✅ Inactive categories rejected
- ✅ Multiple choice questions require options array
- ✅ Options must have 2-6 items with id and text fields
- ✅ Correct answer must match one of the option IDs
- ✅ True/False questions validate correctAnswer as 'true' or 'false'
- ✅ Points auto-calculated: easy=5, medium=10, hard=15
- ✅ Custom points supported (overrides auto-calculation)
- ✅ Tags and keywords stored as JSON arrays
- ✅ Category questionCount incremented
- ✅ Question includes category info in response
- ✅ Created by admin userId tracked
- ✅ Correct answer NOT exposed in response
- ✅ All 16 tests passing
Implementation Notes:
- Added
createQuestionfunction to question.controller.js (260+ lines) - Created admin.routes.js for admin-only endpoints
- Route: POST
/api/admin/questionswith verifyToken + isAdmin - Registered admin routes in server.js at
/api/admin - Comprehensive validation for all question types
- Options validation: min 2, max 6 options
- Each option requires
idandtextfields - True/False answers validated as string 'true' or 'false'
- Points auto-calculation based on difficulty
- Category.increment('questionCount') updates count
- Question reloaded with category association after creation
- createdBy field set to req.user.userId from JWT
- JSON fields (options, tags, keywords) handled by Sequelize getters/setters
Reference:
See interview_quiz_user_story.md - User Story 5.1
Task 25: Update/Delete Question (Admin) ✅
Priority: Medium | Status: Completed | Estimated Time: 2 hours
Subtasks:
- Implement
updateQuestion(admin only) - Validate changes (partial updates supported)
- Implement
deleteQuestion(soft delete) - Update category counts on category change and delete
- Write comprehensive tests (26 scenarios)
API Endpoints:
PUT /api/admin/questions/:id
Headers: { Authorization: Bearer <admin-token> }
Body: {
questionText?: string,
questionType?: 'multiple' | 'trueFalse' | 'written',
options?: [{ id, text }],
correctAnswer?: string,
difficulty?: 'easy' | 'medium' | 'hard',
points?: number,
explanation?: string,
categoryId?: uuid,
tags?: string[],
keywords?: string[],
isActive?: boolean
}
Response: {
success: true,
data: {
id, questionText, questionType, options,
difficulty, points, explanation, tags, keywords,
category: { id, name, slug, icon, color },
isActive, updatedAt
},
message: "Question updated successfully"
}
DELETE /api/admin/questions/:id
Headers: { Authorization: Bearer <admin-token> }
Response: {
success: true,
data: {
id, questionText,
category: { id, name }
},
message: "Question deleted successfully"
}
Acceptance Criteria:
- ✅ Admin-only access (verifyToken + isAdmin middleware)
- ✅ Non-admin users blocked with 403 status
- ✅ Unauthenticated requests blocked with 401 status
- ✅ Partial updates supported (only provided fields updated)
- ✅ Question text validation (non-empty after trim)
- ✅ Question type validation (multiple, trueFalse, written)
- ✅ Options validation for multiple choice (2-6 options)
- ✅ Correct answer validation matches option IDs
- ✅ True/False answer validation ('true' or 'false')
- ✅ Difficulty validation (easy, medium, hard)
- ✅ Auto-points calculation when difficulty changes
- ✅ Custom points override supported
- ✅ Category UUID validation and existence check
- ✅ Category counts updated when category changes
- ✅ Invalid UUID format returns 400
- ✅ Non-existent question returns 404
- ✅ Soft delete sets isActive to false (not physical delete)
- ✅ Already deleted question cannot be deleted again
- ✅ Category question count decremented on delete
- ✅ Deleted questions not accessible via API
- ✅ Correct answer NOT exposed in response
- ✅ All 26 tests passing (19 update + 7 delete scenarios)
Implementation Notes:
- Added
updateQuestionfunction to question.controller.js (200+ lines) - Added
deleteQuestionfunction to question.controller.js (70+ lines) - Routes added to admin.routes.js: PUT
/api/admin/questions/:idand DELETE/api/admin/questions/:id - Partial update pattern: only fields provided in request body are updated
- Effective type validation: uses updated questionType if provided, else existing
- Category count management: decrement old category, increment new category on change
- Soft delete preserves data integrity (questions still exist in database)
- Category.decrement('questionCount') on delete maintains accurate counts
- UUID validation for both question ID and category ID
- Empty/whitespace-only question text rejected
- Points auto-calculated when difficulty changes (unless custom points provided)
- Tags and keywords optional (can be null or arrays)
- isActive flag allows manual activation/deactivation
- Test suite covers all validation scenarios and edge cases
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Update/Delete Operations
Quiz Session Management Phase
Task 26: Start Quiz Session ✅
Priority: High | Status: Completed | Estimated Time: 3 hours
Subtasks:
- Create
routes/quiz.routes.js - Create
controllers/quiz.controller.js - Create
models/QuizSessionQuestion.jsjunction model - Implement
startQuizSessionfunction - Check guest quiz limit (if guest)
- Create quiz session record with transaction
- Select random questions from category
- Create quiz_session_questions junction records
- Return session ID and questions (without correct answers)
- Increment guest quizzes_attempted
- Write comprehensive tests (20 scenarios, all passing)
API Endpoint:
POST /api/quiz/start
Headers: { Authorization: Bearer <token> } OR { X-Guest-Token: <token> }
Body: {
categoryId: uuid (required),
questionCount: number (1-50, default 10),
difficulty: 'easy' | 'medium' | 'hard' | 'mixed' (default 'mixed'),
quizType: 'practice' | 'timed' | 'exam' (default 'practice')
}
Response: {
success: true,
data: {
sessionId: uuid,
category: { id, name, slug, icon, color },
quizType: string,
difficulty: string,
totalQuestions: number,
totalPoints: number,
timeLimit: number | null (minutes, 2 per question for timed/exam),
status: 'in_progress',
startedAt: timestamp,
questions: [{
id, questionText, questionType, options,
difficulty, points, tags, order
}]
},
message: "Quiz session started successfully"
}
Acceptance Criteria:
- ✅ Supports both authenticated users and guest sessions
- ✅ Dual authentication middleware (user JWT or guest token)
- ✅ Category validation (exists, active, guest-accessible check)
- ✅ Guest quiz limit checked before session creation
- ✅ Guest quizzes_attempted incremented on quiz start
- ✅ Guest session expiry validated
- ✅ Converted guest sessions rejected
- ✅ Question count validation (1-50, default 10)
- ✅ Difficulty validation (easy, medium, hard, mixed)
- ✅ Quiz type validation (practice, timed, exam)
- ✅ Random question selection from category
- ✅ Difficulty filtering applied (or mixed for all difficulties)
- ✅ Handles insufficient questions gracefully
- ✅ Total points calculated from selected questions
- ✅ Time limit set for timed/exam quizzes (2 minutes per question)
- ✅ Quiz session created with in_progress status
- ✅ Junction records created for quiz-question linkage
- ✅ Questions ordered sequentially (1-based)
- ✅ Correct answers NOT exposed in response
- ✅ Transaction rollback on errors
- ✅ UUID validation for category IDs
- ✅ Proper error handling and status codes
- ✅ All 20 tests passing (user quizzes, guest quizzes, validation, structure)
Implementation Notes:
- Created
models/QuizSessionQuestion.jsfor junction table (58 lines) - Added
controllers/quiz.controller.jswith startQuizSession function (274 lines) - Created
routes/quiz.routes.jswith dual authentication middleware (58 lines) - Registered quiz routes in server.js at
/api/quiz - Dual auth middleware: tries user JWT first, then guest token using async/await with Promise wrapping
- Fixed guest middleware to set both
req.guestId(string) andreq.guestSessionId(UUID for foreign keys) - Fixed time limit to be stored in seconds (model validation requires min 60 seconds)
- Time limit calculation: totalQuestions × 2 minutes × 60 seconds = 120 seconds per question
- Time limit displayed in response as minutes for better UX
- Transaction ensures atomic quiz creation with rollback on errors
- Random question selection via
sequelize.random() - Questions returned with sequential order (1, 2, 3...)
- Guest limit enforcement integrated into quiz start flow
- Category guestAccessible flag checked for guest users
- Junction table stores questionOrder for maintaining quiz sequence
- Response excludes correctAnswer, createdBy, and other sensitive fields
- Test suite covers user quizzes (6 tests), guest quizzes (4 tests), validation (7 tests), structure (3 tests)
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Start Quiz Session
Task 27: Submit Answer
Priority: High | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Create QuizAnswer model with UUID, foreign keys, validations
- Implement
submitAnswercontroller function (232 lines) - Validate session exists, in-progress, belongs to user/guest
- Check if question belongs to session
- Check if already answered (duplicate prevention)
- Compare answer with correct_answer (case-insensitive, JSON parsing)
- Save to quiz_answers table with transaction
- Update quiz session score if correct
- Increment question times_attempted and times_correct
- Update session questionsAnswered, correctAnswers, timeSpent
- Return immediate feedback (isCorrect, explanation, points, progress)
- Add dual authentication route (user JWT or guest token)
- Write comprehensive tests (14 scenarios, all passing)
API Endpoint:
POST /api/quiz/submit
Body: { quizSessionId, questionId, userAnswer, timeTaken }
Auth: User JWT token OR Guest token (X-Guest-Token)
Implementation Details:
- Transaction-based: All database updates atomic with rollback on errors
- Answer Comparison: Case-insensitive, trimmed, JSON parsing for multiple choice arrays
- Validation Chain: 10+ checks including UUID format, authorization, session status
- Scoring: Points awarded only for correct answers, session score incremented
- Statistics: Updates both session progress and question attempt counts
- Feedback: Includes explanation always, correct answer shown only if incorrect
- Dual Auth: Works for authenticated users and guest sessions
Files Created:
- ✅
models/QuizAnswer.js(129 lines) - Complete model with associations - ✅
controllers/quiz.controller.js- Added submitAnswer (232 lines) - ✅
routes/quiz.routes.js- Added POST /submit route - ✅
test-submit-answer.js(450+ lines) - 14 comprehensive tests
Test Results:
✅ All 14/14 tests passing:
- Basic Functionality (3): proper feedback, incorrect handling, explanation included
- Validation (6): missing fields, invalid UUIDs, non-existent session
- Authorization (3): cross-user prevention, guest support, unauthenticated blocked
- Duplicate Prevention (1): cannot submit same question twice
- Question Validation (1): question must belong to session
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Submit Answer
Task 28: Complete Quiz Session
Priority: High | Status: ✅ Completed | Estimated Time: 3 hours
Subtasks:
- Implement
completeQuizSessioncontroller function (220+ lines) - Calculate final score from session (with DECIMAL field parsing)
- Calculate percentage based on total points
- Calculate time taken in seconds (start to completion)
- Update session status to 'completed' or 'timeout'
- Set endTime and completedAt timestamps
- Update user stats (totalQuizzes, quizzesPassed, accuracy, streak)
- Return comprehensive results with category, score, questions breakdown
- Time limit validation for timed/exam quizzes
- Add dual authentication route (user JWT or guest token)
- Write comprehensive tests (15 scenarios, 14/15 passing)
API Endpoint:
POST /api/quiz/complete
Body: { sessionId: uuid }
Auth: User JWT token OR Guest token (X-Guest-Token)
Response: {
sessionId, status, category, quizType, difficulty,
score: { earned, total, percentage },
questions: { total, answered, correct, incorrect, unanswered },
accuracy, isPassed,
time: { started, completed, taken, limit, isTimeout }
}
Implementation Details:
- Transaction-based: All database updates atomic with rollback on errors
- Score Calculation: Final score from session.score (parses DECIMAL to number)
- Percentage: (earned / total) * 100, rounded to integer
- Pass/Fail: 70% threshold determines isPassed flag
- Time Tracking: Calculates seconds from startedAt to completedAt
- Timeout Detection: Marks as 'timeout' if exceeded timeLimit
- User Stats Updates: Increments totalQuizzes, quizzesPassed, totalQuestionsAnswered, correctAnswers
- Streak Calculation: Updates currentStreak and longestStreak based on lastActiveDate
- Partial Completion: Supports completing with unanswered questions
- Dual Auth: Works for authenticated users and guest sessions
Files Created/Modified:
- ✅
controllers/quiz.controller.js- Added completeQuizSession function (220 lines) - ✅
routes/quiz.routes.js- Added POST /complete route - ✅
test-complete-quiz.js(530+ lines) - 15 comprehensive tests
Test Results:
✅ 14/15 tests passing consistently (15th test subject to rate limiting):
- Basic Functionality (5): detailed results, guest completion, percentage, pass/fail, time tracking
- Validation (6): missing fields, invalid UUIDs, non-existent session, authorization, already completed, unauthenticated
- Partial Completion (4): unanswered questions supported, status updated, category info, counts accurate
Bug Fixes:
- Fixed User model field names:
correctAnswers(nottotalCorrectAnswers) - Fixed DECIMAL field parsing:
parseFloat()for score and totalPoints - Fixed test for variable question counts per category
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Complete Quiz Session
Task 29: Get Session Details
Priority: Medium | Status: ✅ Completed | Estimated Time: 1.5 hours
Subtasks:
- Implement
getSessionDetailscontroller function (204 lines) - Return comprehensive session info with category details
- Include all questions with answers and feedback
- Include progress tracking (answered, correct, incorrect, unanswered)
- Check authorization (own session only - user or guest)
- Add dual authentication route (user JWT or guest token)
- Write comprehensive tests (14 scenarios, all passing)
API Endpoint:
GET /api/quiz/session/:sessionId
Headers: { Authorization: Bearer <token> } OR { X-Guest-Token: <token> }
Response: {
success: true,
data: {
session: {
id, status, quizType, difficulty,
category: { id, name, slug, icon, color },
score: { earned, total, percentage },
isPassed, startedAt, completedAt,
timeSpent, timeLimit, timeRemaining
},
progress: {
totalQuestions, answeredQuestions, correctAnswers,
incorrectAnswers, unansweredQuestions, progressPercentage
},
questions: [{
id, questionText, questionType, options,
difficulty, points, explanation, tags, order,
correctAnswer, userAnswer, isCorrect,
pointsEarned, timeTaken, answeredAt, isAnswered
}]
},
message: "Quiz session details retrieved successfully"
}
Implementation Details:
- Authorization: Verifies session belongs to authenticated user or guest
- Smart Correct Answer Display:
- In-progress sessions: Shows correct answer only for answered questions
- Completed/timeout sessions: Shows correct answers for all questions
- Progress Tracking: Real-time calculation of answered/unanswered questions
- Time Tracking:
- timeSpent: seconds from start to now (or completion)
- timeRemaining: null for practice, calculated for timed/exam
- Question Details: Includes all question data plus user's answer and feedback
- Dual Auth: Works for authenticated users and guest sessions
- Field Mapping: Uses
selectedOptionfrom QuizAnswer model (notuserAnswer)
Files Created/Modified:
- ✅
controllers/quiz.controller.js- Added getSessionDetails function (204 lines) - ✅
routes/quiz.routes.js- Added GET /session/:sessionId route - ✅
test-session-details.js(620+ lines) - 14 comprehensive tests
Test Results:
✅ All 14/14 tests passing:
- Basic Functionality (3): in-progress session, completed session, guest session
- Validation (5): missing ID, invalid UUID, non-existent session, cross-user access, unauthenticated
- Structure Validation (3): session fields, progress fields, question fields
- Calculations (3): time tracking, progress percentages, answer feedback
Bug Fixes:
- Fixed field name:
selectedOptionin QuizAnswer model (notuserAnswer) - Fixed boolean logic:
isCorrectnow properly handlesfalsevalues
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Get Quiz Session Details
Task 30: Review Completed Quiz
Priority: Medium | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Implement
reviewQuizSessioncontroller function (230+ lines) - Return all questions with user answers and correct answers
- Include explanations for all questions
- Mark correct/incorrect with visual feedback (resultStatus)
- Include time spent per question and time statistics
- Add multiple choice option-level feedback (isCorrect, isSelected, feedback)
- Validate session is completed or timed out (reject in-progress)
- Add dual authentication route (user JWT or guest token)
- Write comprehensive tests (16 scenarios, all passing)
API Endpoint:
GET /api/quiz/review/:sessionId
Headers: { Authorization: Bearer <token> } OR { X-Guest-Token: <token> }
Response: {
success: true,
data: {
session: {
id, status, quizType, difficulty,
category: { id, name, slug, icon, color },
startedAt, completedAt, timeSpent
},
summary: {
score: { earned, total, percentage },
questions: { total, answered, correct, incorrect, unanswered },
accuracy, isPassed,
timeStatistics: {
totalTime, averageTimePerQuestion,
timeLimit, wasTimedOut
}
},
questions: [{
id, questionText, questionType, options,
difficulty, points, explanation, tags, order,
correctAnswer, userAnswer, isCorrect,
resultStatus, pointsEarned, pointsPossible,
timeTaken, answeredAt, showExplanation, wasAnswered
}]
},
message: "Quiz review retrieved successfully"
}
Implementation Details:
- Status Validation: Only allows review of completed or timed out quizzes (rejects in-progress with 400)
- Authorization: Verifies session belongs to authenticated user or guest
- Result Status: Each question marked as 'correct', 'incorrect', or 'unanswered' for visual feedback
- Multiple Choice Feedback: Options include isCorrect, isSelected, and feedback fields ('correct-answer', 'user-selected-wrong', or null)
- Explanations: Always shown in review (showExplanation=true for all questions)
- Points Tracking:
- pointsEarned: actual points earned for this question
- pointsPossible: maximum points available
- Summary includes total earned vs total possible
- Time Statistics:
- Per-question: timeTaken and answeredAt timestamp
- Summary: totalTime, averageTimePerQuestion
- Timeout detection: wasTimedOut flag if session status is 'timeout'
- Comprehensive Summary: Includes score breakdown, question counts, accuracy percentage, pass/fail status
- Dual Auth: Works for authenticated users and guest sessions
Files Created/Modified:
- ✅
controllers/quiz.controller.js- Added reviewQuizSession function (230+ lines, total 1165+ lines) - ✅
routes/quiz.routes.js- Added GET /review/:sessionId route - ✅
test-review-quiz.js(700+ lines) - 16 comprehensive tests
Test Results:
✅ All 16/16 tests passing:
- Basic Functionality (2): completed quiz review (user), guest quiz review
- Validation (6): in-progress blocked, missing ID, invalid UUID, non-existent session, cross-user access, unauthenticated
- Structure Validation (3): session fields, summary fields, question fields
- Feedback Validation (5): result status marking, explanations shown, points tracking, time statistics, multiple choice option feedback
Key Features:
- Visual Feedback: resultStatus ('correct'/'incorrect'/'unanswered') for UI styling
- Option-Level Feedback: Multiple choice options show which is correct and which was selected
- Complete Explanations: All questions include explanations for learning
- Time Analytics: Per-question and aggregate time statistics
- Summary Statistics: Complete breakdown of performance (score, accuracy, pass/fail)
- Authorization: Own-session-only access with dual auth support
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Review Quiz Session
User Dashboard & Analytics Phase
Task 31: Get User Dashboard
Priority: High | Status: ✅ COMPLETED | Actual Time: 3 hours
Subtasks:
- Create
routes/user.routes.js - Create
controllers/user.controller.js - Implement
getUserDashboardfunction - Return user stats (total quizzes, accuracy, streak)
- Return recent quiz sessions (last 10)
- Return category-wise performance
- Calculate overall accuracy
- Include streak status (active/at-risk/inactive)
- Add recent activity tracking (last 30 days)
- Write tests (14/14 passing)
Implementation Details:
- Files Created:
controllers/user.controller.js(252 lines)routes/user.routes.js(13 lines)test-user-dashboard.js(527 lines)
- Files Modified:
server.js- Registered user routes at/api/userscontrollers/quiz.controller.js- Fixed streak tracking logic
Features Implemented:
- User information (username, email, role, profile image, member since)
- Statistics (total quizzes, pass rate, accuracy, questions answered, streaks)
- Recent 10 quiz sessions with category, scores, and time spent
- Category-wise performance (quizzes taken, pass rate, accuracy per category)
- Recent activity (daily quiz counts for last 30 days)
- Streak status calculation (active if quiz today, at-risk if last active yesterday, inactive otherwise)
Bug Fixes:
- Fixed field name mapping:
lastQuizDateinstead oflastActiveDate - Removed non-existent
percentagefield, calculating fromscore/totalPoints - Fixed authorization order: check user existence before authorization (proper 404 responses)
- Fixed streak logic: update
longestStreakwhen resettingcurrentStreak
Test Results:
✅ All 14/14 tests passing:
- Dashboard data retrieval
- User info structure validation
- Stats calculations accuracy
- Recent sessions ordering
- Category performance calculations
- Authorization checks (cross-user, unauthenticated, invalid UUID, non-existent user)
- Streak status validation
API Endpoint:
GET /api/users/:userId/dashboard
Authorization: Bearer <JWT_TOKEN>
Response Structure:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"username": "string",
"email": "string",
"role": "string",
"profileImage": "string|null",
"memberSince": "ISO8601"
},
"stats": {
"totalQuizzes": 10,
"quizzesPassed": 8,
"passRate": 80,
"totalQuestionsAnswered": 50,
"correctAnswers": 42,
"overallAccuracy": 84,
"currentStreak": 3,
"longestStreak": 5,
"streakStatus": "active|at-risk|inactive",
"lastActiveDate": "ISO8601|null"
},
"recentSessions": [
{
"id": "uuid",
"category": { "id": "uuid", "name": "JavaScript", "slug": "javascript", "icon": "📜", "color": "#f7df1e" },
"quizType": "practice",
"difficulty": "medium",
"status": "completed",
"score": { "earned": 45, "total": 50, "percentage": 90 },
"isPassed": true,
"questionsAnswered": 10,
"correctAnswers": 9,
"accuracy": 90,
"timeSpent": 300,
"completedAt": "ISO8601"
}
],
"categoryPerformance": [
{
"category": { "id": "uuid", "name": "JavaScript", "slug": "javascript", "icon": "📜", "color": "#f7df1e" },
"stats": {
"quizzesTaken": 5,
"quizzesPassed": 4,
"averageScore": 85,
"totalQuestions": 50,
"correctAnswers": 43,
"accuracy": 86,
"lastAttempt": "ISO8601"
}
}
],
"recentActivity": [
{ "date": "2025-11-11", "quizzesCompleted": 3 },
{ "date": "2025-11-10", "quizzesCompleted": 2 }
]
},
"message": "User dashboard retrieved successfully"
}
Reference:
See interview_quiz_user_story.md - User Story 4.1
Notes:
- Users can only access their own dashboard (authorization enforced)
- Streak calculation: active if quiz completed today, at-risk if last active yesterday, inactive otherwise
- Category performance uses SQL aggregation for efficiency
- Recent activity limited to last 30 days
Task 32: Get Quiz History
Priority: Medium | Status: ✅ COMPLETED | Estimated Time: 2 hours | Actual Time: 3 hours
Subtasks:
- Implement
getQuizHistoryfunction inuser.controller.js - Pagination support (page, limit with max 50)
- Filter by category UUID
- Filter by status (completed/timeout/abandoned)
- Filter by date range (startDate, endDate)
- Sort by date or score (asc/desc)
- Return formatted session summaries
- Write comprehensive tests (18 test cases)
- All tests passing (18/18)
Implementation Details:
Controller: controllers/user.controller.js
getQuizHistory()function (233 lines)- Validates userId UUID format
- Checks user existence (404 if not found)
- Authorization check (403 if accessing other user's history)
- Pagination with configurable page and limit
- Maximum 50 items per page enforced
- Category filtering with UUID validation
- Status filtering with enum validation
- Date range filtering with ISO 8601 format validation
- End date includes full day (23:59:59.999)
- Sorting by completedAt (date) or score
- Ascending or descending order
- Returns formatted sessions with:
- Category info (id, name, slug, icon, color)
- Score (earned, total, percentage)
- Questions (answered, total, correct, accuracy)
- Time (spent, allocated, percentage)
- Status and pass/fail indicator
- Timestamps (startedAt, completedAt)
- Pagination metadata (currentPage, totalPages, totalItems, itemsPerPage, hasNextPage, hasPreviousPage)
- Filter and sorting info in response
Route: routes/user.routes.js
GET /:userId/historywith verifyToken middleware- Private access (users can only view own history)
Tests: test-quiz-history.js (552 lines)
- ✅ Test 1: Default pagination (page 1, limit 10)
- ✅ Test 2: Pagination structure validation
- ✅ Test 3: Session fields validation
- ✅ Test 4: Custom limit
- ✅ Test 5: Page 2 navigation
- ✅ Test 6: Category filter
- ✅ Test 7: Status filter
- ✅ Test 8: Sort by score descending
- ✅ Test 9: Sort by date ascending
- ✅ Test 10: Default sort (date descending)
- ✅ Test 11: Max limit enforcement (50)
- ✅ Test 12: Cross-user access blocked (403)
- ✅ Test 13: Unauthenticated blocked (401)
- ✅ Test 14: Invalid UUID format (400)
- ✅ Test 15: Non-existent user (404)
- ✅ Test 16: Invalid category ID (400)
- ✅ Test 17: Invalid date format (400)
- ✅ Test 18: Combined filters and sorting
Test Results: 18/18 tests passing ✅
API Endpoint:
GET /api/users/:userId/history
Query Parameters:
page(integer, default: 1) - Page numberlimit(integer, default: 10, max: 50) - Items per pagecategory(UUID) - Filter by category IDstatus(string) - Filter by status (completed/timeout/abandoned)startDate(ISO 8601) - Filter sessions completed on or after this dateendDate(ISO 8601) - Filter sessions completed on or before this date (includes full day)sortBy(string, default: 'date') - Sort by 'date' or 'score'sortOrder(string, default: 'desc') - Sort order 'asc' or 'desc'
Example Request:
GET /api/users/b65d40c4-9971-48f3-b626-0520f9a9ac61/history?page=1&limit=5&category=d29f3a12-8e4c-4c8d-9f1a-2b3c4d5e6f7a&sortBy=score&sortOrder=desc
Authorization: Bearer <token>
Example Response:
{
"success": true,
"data": {
"sessions": [
{
"id": "b6ea0e7d-73a5-4310-b480-e102804221de",
"category": {
"id": "d29f3a12-8e4c-4c8d-9f1a-2b3c4d5e6f7a",
"name": "JavaScript",
"slug": "javascript",
"icon": "fab fa-js",
"color": "#f7df1e"
},
"quizType": "practice",
"difficulty": "medium",
"status": "completed",
"score": {
"earned": 15,
"total": 20,
"percentage": 75
},
"isPassed": true,
"questions": {
"answered": 4,
"total": 4,
"correct": 3,
"accuracy": 75
},
"time": {
"spent": 45,
"allocated": 120,
"percentage": 37.5
},
"startedAt": "2025-11-12T10:15:00.000Z",
"completedAt": "2025-11-12T10:16:30.000Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 2,
"totalItems": 8,
"itemsPerPage": 5,
"hasNextPage": true,
"hasPreviousPage": false
},
"filters": {
"category": "d29f3a12-8e4c-4c8d-9f1a-2b3c4d5e6f7a",
"status": null,
"dateRange": {
"startDate": null,
"endDate": null
}
},
"sorting": {
"sortBy": "score",
"sortOrder": "desc"
}
}
}
Bug Fixes During Testing:
- Fixed field names for quiz submission:
- Changed
selectedOption→userAnswer - Changed
timeTaken→timeSpent - Used
quizSessionIdfor submit endpoint
- Changed
- Fixed complete quiz endpoint field:
- Used
sessionId(notquizSessionId) for complete endpoint
- Used
Files Modified:
- ✅
controllers/user.controller.js- Added getQuizHistory function (485 lines total) - ✅
routes/user.routes.js- Added history route (27 lines total) - ✅
test-quiz-history.js- Created comprehensive test suite (552 lines)
Notes:
- Users can only access their own quiz history (enforced by verifyToken and authorization check)
- Date filtering supports ISO 8601 format (e.g., "2025-11-12" or "2025-11-12T10:00:00Z")
- End date filter includes the entire day (00:00:00 to 23:59:59.999)
- Default sorting is by most recent first (date descending)
- Maximum 50 items per page to prevent performance issues
- Empty result sets return empty array with correct pagination metadata
- All validation errors return 400 with descriptive messages
- Authorization errors return 403, missing resources return 404
Task 33: Update User Profile
Priority: Medium | Status: ✅ Completed | Estimated Time: 2 hours
Subtasks:
- Implement
updateUserProfilefunction - Allow username change (check uniqueness)
- Allow profile_image upload (URL string, max 255 chars)
- Password change (verify old password)
- Email change (verify new email)
- Write tests
API Endpoint:
PUT /api/users/:userId
Implementation Details:
- Controller:
controllers/user.controller.js- AddedupdateUserProfilefunction (87 lines) - Route:
routes/user.routes.js- Added PUT /:userId with verifyToken middleware - Tests:
test-update-profile.js- 21 comprehensive test scenarios (all passing) - Authorization: Users can only update their own profile (userId must match JWT token)
- Features Implemented:
- Username update: Alphanumeric only, 3-50 chars, uniqueness check
- Email update: Format validation, uniqueness check
- Password update: Requires currentPassword verification, min 6 chars, bcrypt hashing via model hook
- Profile image: URL string (max 255 chars), nullable
- Validation: UUID format, field requirements, format checks, uniqueness constraints
- Response: Updated user object (password excluded) + changedFields array
- Error Handling: 400 (validation), 401 (incorrect password/unauthenticated), 403 (unauthorized), 404 (not found), 409 (duplicate)
- Bug Fix: Removed manual password hashing from controller to prevent double-hashing (model beforeUpdate hook handles it)
Test Results:
✅ 21/21 tests passing
- Username update (with revert)
- Email update (with revert)
- Password update (with login verification and revert)
- Profile image update and removal
- Multiple fields at once
- Duplicate username/email rejection (409)
- Invalid email format (400)
- Short username/password rejection (400)
- Invalid username characters (400)
- Password change without current password (400)
- Incorrect current password (401)
- Empty update rejection (400)
- Cross-user update blocked (403)
- Unauthenticated blocked (401)
- Invalid UUID format (400)
- Non-existent user (404)
- Long profile image URL (400)
- Password excluded from response
Files Modified:
controllers/user.controller.js(487 → 701 lines, +214)routes/user.routes.js(27 → 37 lines, +10)test-update-profile.js(NEW FILE, 592 lines)
Bookmark Management Phase
Task 34: Add/Remove Bookmark
Priority: Medium | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Implement
addBookmarkfunction - Check if already bookmarked
- Create user_bookmarks record
- Implement
removeBookmarkfunction - Delete user_bookmarks record
- Write tests
API Endpoints:
POST /api/users/:userId/bookmarks
DELETE /api/users/:userId/bookmarks/:questionId
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Bookmark Operations
Task 35: Get User Bookmarks
Priority: Medium | Status: Not Started | Estimated Time: 1.5 hours
Subtasks:
- Implement
getUserBookmarksfunction - Include question details
- Include category info
- Sort by bookmarked_at
- Pagination
- Write tests
API Endpoint:
GET /api/users/:userId/bookmarks
Admin Features Phase
Task 36: Admin Statistics Dashboard
Priority: Medium | Status: Not Started | Estimated Time: 3 hours
Subtasks:
- Create
routes/admin.routes.js - Create
controllers/admin.controller.js - Implement
getSystemStatisticsfunction - Count total users
- Count active users (last 7 days)
- Count total quiz sessions
- Get popular categories
- Calculate average score
- Get user growth data
- Add authorization (admin only)
- Write tests
API Endpoint:
GET /api/admin/statistics
Reference:
See SEQUELIZE_QUICK_REFERENCE.md - Admin Operations
Task 37: Guest Settings Management
Priority: Medium | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Implement
getGuestSettingsfunction - Implement
updateGuestSettingsfunction - Validate settings (max quizzes, expiry hours)
- Update public categories list
- Update feature restrictions
- Write tests
API Endpoints:
GET /api/admin/guest-settings
PUT /api/admin/guest-settings
Task 38: User Management (Admin)
Priority: Low | Status: Not Started | Estimated Time: 3 hours
Subtasks:
- Implement
getAllUsersfunction (paginated) - Implement
getUserByIdfunction - Implement
updateUserRolefunction - Implement
deactivateUserfunction - Write tests
API Endpoints:
GET /api/admin/users
GET /api/admin/users/:userId
PUT /api/admin/users/:userId/role
DELETE /api/admin/users/:userId
Task 39: Guest Analytics
Priority: Low | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Implement
getGuestAnalyticsfunction - Count total guest sessions
- Calculate guest-to-user conversion rate
- Average quizzes taken before conversion
- Guest bounce rate
- Write tests
API Endpoint:
GET /api/admin/guest-analytics
Testing & Optimization Phase
Task 40: Unit Tests
Priority: High | Status: Not Started | Estimated Time: 5 hours
Subtasks:
- Setup Jest testing framework
- Write tests for auth controllers
- Write tests for quiz controllers
- Write tests for user controllers
- Write tests for admin controllers
- Mock database calls
- Achieve 80%+ code coverage
Task 41: Integration Tests
Priority: High | Status: Not Started | Estimated Time: 4 hours
Subtasks:
- Setup Supertest for API testing
- Test complete registration flow
- Test complete quiz flow (start -> answer -> complete)
- Test guest to user conversion
- Test authorization scenarios
- Test error scenarios
Task 42: API Documentation
Priority: Medium | Status: Not Started | Estimated Time: 3 hours
Subtasks:
- Install Swagger/OpenAPI
- Document all endpoints
- Add request/response examples
- Add authentication details
- Generate interactive API docs
- Host at
/api-docs
Task 43: Error Handling & Logging
Priority: High | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Create centralized error handler middleware
- Handle Sequelize errors gracefully
- Setup logging with Winston/Morgan
- Log all requests (development)
- Log errors with stack traces
- Setup log rotation
Task 44: Rate Limiting & Security
Priority: High | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Install express-rate-limit
- Add rate limiting to auth endpoints
- Add rate limiting to API endpoints
- Setup Helmet for security headers
- Add input sanitization
- Add CORS configuration
- Test security measures
Task 45: Database Optimization
Priority: Medium | Status: Not Started | Estimated Time: 3 hours
Subtasks:
- Review and optimize all queries
- Add missing indexes
- Implement query result caching (Redis)
- Use eager loading to avoid N+1 queries
- Optimize full-text search queries
- Run EXPLAIN on complex queries
- Benchmark query performance
Task 46: Performance Testing
Priority: Medium | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Setup load testing tool (Apache JMeter or Artillery)
- Test concurrent user scenarios
- Test database under load
- Monitor response times
- Identify bottlenecks
- Optimize as needed
Deployment Preparation Phase
Task 47: Docker Configuration
Priority: Low | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Create
Dockerfilefor backend - Create
docker-compose.ymlwith MySQL service - Configure environment variables for Docker
- Test Docker build and run
- Create docker-compose for development
Task 48: CI/CD Setup
Priority: Low | Status: Not Started | Estimated Time: 3 hours
Subtasks:
- Create GitHub Actions workflow
- Setup automated testing on PR
- Setup automated deployment (staging)
- Add environment secrets
- Test CI/CD pipeline
Task 49: Production Configuration
Priority: Low | Status: Not Started | Estimated Time: 2 hours
Subtasks:
- Create production environment config
- Setup connection pooling for production
- Configure SSL for database connection
- Setup monitoring (New Relic/DataDog)
- Configure backup strategy
- Create deployment documentation
Task 50: Final Testing & Documentation
Priority: High | Status: Not Started | Estimated Time: 4 hours
Subtasks:
- End-to-end testing of all features
- Create API usage examples
- Write README.md with setup instructions
- Document environment variables
- Create troubleshooting guide
- Code review and refactoring
- Performance optimization
- Security audit
Task Summary
Total Tasks: 50 Estimated Total Time: 100-120 hours (2-3 months part-time)
Priority Breakdown:
- High Priority: 25 tasks (Core functionality)
- Medium Priority: 18 tasks (Important features)
- Low Priority: 7 tasks (Nice to have)
Phase Breakdown:
- Project Setup: Tasks 1-3 (4-6 hours)
- Database Schema: Tasks 4-10 (15-20 hours)
- Authentication: Tasks 11-14 (8-10 hours)
- Guest Management: Tasks 15-17 (6-8 hours)
- Category Management: Tasks 18-20 (6-8 hours)
- Question Management: Tasks 21-25 (10-12 hours)
- Quiz Sessions: Tasks 26-30 (11-14 hours)
- User Dashboard: Tasks 31-33 (7-9 hours)
- Bookmarks: Tasks 34-35 (3-4 hours)
- Admin Features: Tasks 36-39 (10-12 hours)
- Testing & Optimization: Tasks 40-46 (21-25 hours)
- Deployment: Tasks 47-50 (11-13 hours)
Getting Started
Recommended Order:
- Start with Task 1 (Project Setup)
- Complete Tasks 2-3 (Database & Environment)
- Work through Tasks 4-10 (Database Schema) sequentially
- Then proceed with feature development in order
Daily Workflow:
- Pick a task based on priority
- Read the task requirements and references
- Implement the feature
- Write tests
- Update task status
- Commit with descriptive message
- Move to next task
Good luck with the development! 🚀