├── .env.example ├── .gitignore ├── middlewares ├── multerConfig.js └── auth.js ├── utils └── role.js ├── models ├── badge.model.js ├── lesson.model.js ├── progress.model.js ├── forumComment.model.js ├── quiz.model.js ├── course.model.js ├── forumPost.model.js └── user.model.js ├── routes ├── progress.routes.js ├── quiz.routes.js ├── comment.routes.js ├── badge.routes.js ├── forum.routes.js ├── user.routes.js └── course.routes.js ├── helpers └── gamification.helper.js ├── config └── db.config.js ├── index.js ├── package.json ├── controllers ├── badge.controller.js ├── quiz.controller.js ├── forumComments.controller.js ├── progress.controller.js ├── fourm.controller.js ├── user.controller.js └── course.controller.js └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | JWT_SECRET=my_jwt_secret 2 | MONGO_URI=mongodb://localhost:27017/eLearning 3 | PORT=3000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # dependencies 3 | node_modules 4 | /.pnp 5 | .pnp.js 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | build 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* -------------------------------------------------------------------------------- /middlewares/multerConfig.js: -------------------------------------------------------------------------------- 1 | const multer = require("multer"); 2 | 3 | const storage = multer.diskStorage({ 4 | destination: (req, file, cb) => { 5 | cb(null, "uploads/"); 6 | }, 7 | filename: (req, file, cb) => { 8 | cb(null, `${Date.now()}-${file.originalname}`); 9 | }, 10 | }); 11 | 12 | const upload = multer({ storage }); 13 | 14 | module.exports = upload; 15 | -------------------------------------------------------------------------------- /utils/role.js: -------------------------------------------------------------------------------- 1 | const ROLE = { 2 | ADMIN: "admin", 3 | INSTRUCTOR: "instructor", 4 | STUDENT: "student", 5 | }; 6 | 7 | const authorize = (...roles) => { 8 | return (req, res, next) => { 9 | if (!roles.includes(req.user.role)) { 10 | return res.status(403).send({ error: "Access denied." }); 11 | } 12 | next(); 13 | }; 14 | }; 15 | 16 | module.exports = { ROLE, authorize }; 17 | -------------------------------------------------------------------------------- /models/badge.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const badgeSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | trim: true, 8 | }, 9 | description: { 10 | type: String, 11 | required: true, 12 | trim: true, 13 | }, 14 | pointsRequired: { 15 | type: Number, 16 | required: true, 17 | }, 18 | }); 19 | 20 | const Badge = mongoose.model("Badge", badgeSchema); 21 | module.exports = Badge; 22 | -------------------------------------------------------------------------------- /models/lesson.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const lessonSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | trim: true, 8 | }, 9 | content: { 10 | type: String, 11 | required: true, 12 | trim: true, 13 | }, 14 | course: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | ref: "Course", 17 | required: true, 18 | }, 19 | }); 20 | 21 | module.exports = mongoose.model("Lesson", lessonSchema); 22 | -------------------------------------------------------------------------------- /routes/progress.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const auth = require("../middlewares/auth"); 4 | const progressController = require("../controllers/progress.controller"); 5 | 6 | router.post( 7 | "/courses/:courseId/lessons/:lessonId/complete", 8 | auth, 9 | progressController.markLessonAsComplete 10 | ); 11 | router.get( 12 | "/courses/:courseId/progress", 13 | auth, 14 | progressController.getProgressByCourse 15 | ); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /models/progress.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const progressSchema = new mongoose.Schema({ 4 | user: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: "User", 7 | required: true, 8 | }, 9 | course: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: "Course", 12 | required: true, 13 | }, 14 | completedLessons: [ 15 | { 16 | type: mongoose.Schema.Types.ObjectId, 17 | ref: "Lesson", 18 | }, 19 | ], 20 | }); 21 | 22 | module.exports = mongoose.model("Progress", progressSchema); 23 | -------------------------------------------------------------------------------- /models/forumComment.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const commentSchema = new mongoose.Schema({ 4 | content: { 5 | type: String, 6 | required: true, 7 | }, 8 | createdBy: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | required: true, 12 | }, 13 | post: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | ref: "ForumPost", 16 | required: true, 17 | }, 18 | createdAt: { 19 | type: Date, 20 | default: Date.now, 21 | }, 22 | }); 23 | 24 | module.exports = mongoose.model("Comment", commentSchema); 25 | -------------------------------------------------------------------------------- /helpers/gamification.helper.js: -------------------------------------------------------------------------------- 1 | const Badge = require("../models/badge.model"); 2 | const User = require("../models/user.model"); 3 | 4 | const checkAndAwardBadge = async (userId) => { 5 | const user = await User.findById(userId).populate("badges"); 6 | const allBadges = await Badge.find({}); 7 | 8 | for (const badge of allBadges) { 9 | if ( 10 | user.points >= badge.pointsRequired && 11 | !user.badges.includes(badge.id) 12 | ) { 13 | user.badges.push(badge); 14 | await user.save(); 15 | } 16 | } 17 | }; 18 | 19 | module.exports = { checkAndAwardBadge }; 20 | -------------------------------------------------------------------------------- /routes/quiz.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const quizController = require("../controllers/quiz.controller"); 3 | const auth = require("../middlewares/auth"); 4 | const { authorize } = require("../utils/role"); 5 | 6 | const router = new express.Router(); 7 | 8 | router.use(auth); 9 | router.post( 10 | "/courses/:courseId/quizzes", 11 | authorize("instructor"), 12 | quizController.createQuiz 13 | ); 14 | router.get("/courses/:courseId/quizzes", quizController.getQuizzesByCourse); 15 | router.post( 16 | "/quizzes/:quizId/attempt", 17 | authorize("student"), 18 | quizController.attemptQuiz 19 | ); 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /routes/comment.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router({ mergeParams: true }); 3 | const auth = require("../middlewares/auth"); 4 | const commentController = require("../controllers/forumComments.controller"); 5 | 6 | // Create comment 7 | router.post("/", auth, commentController.createComment); 8 | 9 | // Update comment 10 | router.put("/:commentId", auth, commentController.updateComment); 11 | 12 | // Delete comment 13 | router.delete("/:commentId", auth, commentController.deleteComment); 14 | 15 | // Get comments by post 16 | router.get("/", commentController.getCommentsByPost); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /routes/badge.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const badgeController = require("../controllers/badge.controller"); 3 | const auth = require("../middlewares/auth"); 4 | const { authorize } = require("../utils/role"); 5 | 6 | const router = express.Router(); 7 | 8 | router.post("/", auth, authorize("admin"), badgeController.createBadge); 9 | router.get("/", auth, authorize("admin"), badgeController.getAllBadges); 10 | router.put("/:badgeId", auth, authorize("admin"), badgeController.updateBadge); 11 | router.delete( 12 | "/:badgeId", 13 | auth, 14 | authorize("admin"), 15 | badgeController.deleteBadge 16 | ); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /config/db.config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | const mongoose = require("mongoose"); 3 | 4 | dotenv.config(); 5 | 6 | mongoose.connect(process.env.MONGO_URI); 7 | 8 | mongoose.connection.on("connected", () => { 9 | console.log("Mongoose connected to mydatabase"); 10 | }); 11 | 12 | mongoose.connection.on("error", (err) => { 13 | console.log("Mongoose connection error:", err); 14 | }); 15 | 16 | mongoose.connection.on("disconnected", () => { 17 | console.log("Mongoose disconnected"); 18 | }); 19 | 20 | process.on("SIGINT", () => { 21 | mongoose.connection.close(() => { 22 | console.log("Mongoose connection closed"); 23 | process.exit(0); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /routes/forum.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router({ mergeParams: true }); 3 | const forumController = require("../controllers/fourm.controller"); 4 | const auth = require("../middlewares/auth"); 5 | const commentRoutes = require("../routes/comment.routes"); 6 | 7 | router.post("/", auth, forumController.createPost); 8 | router.get("/", forumController.getPosts); 9 | router.get("/search", forumController.searchPosts); 10 | router.put("/:postId", auth, forumController.updatePost); 11 | router.patch("/:postId/vote", auth, forumController.votePost); 12 | router.patch("/:postId/accept", auth, forumController.acceptAnswer); 13 | router.use("/:postId/comments", commentRoutes); 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../models/user.model"); 3 | const dotenv = require("dotenv"); 4 | 5 | dotenv.config(); 6 | 7 | const auth = async (req, res, next) => { 8 | try { 9 | const token = req.header("Authorization").replace("Bearer ", ""); 10 | 11 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 12 | const user = await User.findOne({ 13 | _id: decoded._id, 14 | "tokens.token": token, 15 | }); 16 | 17 | if (!user) { 18 | throw new Error(); 19 | } 20 | 21 | req.token = token; 22 | req.user = user; 23 | next(); 24 | } catch (error) { 25 | res.status(401).send({ error: "Please authenticate." }); 26 | } 27 | }; 28 | 29 | module.exports = auth; 30 | -------------------------------------------------------------------------------- /routes/user.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const userController = require("../controllers/user.controller"); 3 | const auth = require("../middlewares/auth"); 4 | const { authorize } = require("../utils/role"); 5 | 6 | const router = new express.Router(); 7 | 8 | router.post("/register", userController.registerUser); 9 | router.post("/login", userController.loginUser); 10 | router.get("/me", auth, userController.getProfile); 11 | router.patch("/me", auth, userController.updateProfile); 12 | router.delete("/me", auth, userController.deleteProfile); 13 | router.get("/users", auth, authorize("admin"), userController.getUsers); 14 | router.get( 15 | "/instructors/:id/courses", 16 | auth, 17 | authorize("instructor"), 18 | userController.getInstructorCourses 19 | ); 20 | router.get("/leaderboard", userController.getLeaderboard); 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /models/quiz.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const quizSchema = new mongoose.Schema({ 4 | title: { type: String, required: true, trim: true }, 5 | questions: [ 6 | { 7 | question: { type: String, required: true, trim: true }, 8 | options: [ 9 | { 10 | option: { type: String, required: true, trim: true }, 11 | }, 12 | ], 13 | correctAnswer: { type: Number, required: true }, 14 | }, 15 | ], 16 | course: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | ref: "Course", 19 | required: true, 20 | }, 21 | attempts: [ 22 | { 23 | student: { 24 | type: mongoose.Schema.Types.ObjectId, 25 | ref: "User", 26 | }, 27 | score: { 28 | type: Number, 29 | }, 30 | }, 31 | ], 32 | }); 33 | 34 | module.exports = mongoose.model("Quiz", quizSchema); 35 | -------------------------------------------------------------------------------- /models/course.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const courseSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | trim: true, 8 | minlength: 5, 9 | maxlength: 100, 10 | }, 11 | description: { 12 | type: String, 13 | required: true, 14 | trim: true, 15 | minlength: 10, 16 | maxlength: 500, 17 | }, 18 | createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, 19 | enrolledStudents: [ 20 | { 21 | type: mongoose.Schema.Types.ObjectId, 22 | ref: "User", 23 | }, 24 | ], 25 | quizzes: [ 26 | { 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: "Quiz", 29 | }, 30 | ], 31 | materials: [ 32 | { 33 | type: String, 34 | }, 35 | ], 36 | lessons: [ 37 | { 38 | type: mongoose.Schema.Types.ObjectId, 39 | ref: "Lesson", 40 | }, 41 | ], 42 | }); 43 | 44 | module.exports = mongoose.model("Course", courseSchema); 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const dotenv = require("dotenv"); 4 | const cors = require("cors"); 5 | require("./config/db.config"); 6 | dotenv.config(); 7 | 8 | const userRoutes = require("./routes/user.routes"); 9 | const courseRoutes = require("./routes/course.routes"); 10 | const quizRoutes = require("./routes/quiz.routes"); 11 | const forumRoutes = require("./routes/forum.routes"); 12 | const progressRoutes = require("./routes/progress.routes"); 13 | const badgeRoutes = require("./routes/badge.routes"); 14 | const app = express(); 15 | app.use(cors()); 16 | app.use(bodyParser.json()); 17 | 18 | app.use("/api/auth", userRoutes); 19 | app.use("/api/courses", courseRoutes); 20 | app.use("/api", quizRoutes); 21 | app.use("/api/courses/:courseId/forums", forumRoutes); 22 | app.use("/api", progressRoutes); 23 | app.use("/api/badges", badgeRoutes); 24 | 25 | app.listen(process.env.PORT, () => { 26 | console.log(`Server is running on port ${process.env.PORT}`); 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elearnig-api", 3 | "version": "1.0.0", 4 | "description": "A RESTful API built using Node.js, Express, and MongoDB for creating and managing an e-learning platform where instructors can create courses, and students can enroll in courses, access course materials, and attempt quizzes.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon index.js" 9 | }, 10 | "keywords": [ 11 | "elearnig", 12 | "api", 13 | "nodejs", 14 | "express", 15 | "online learning", 16 | "course management" 17 | ], 18 | "author": "Shahghasi Adil", 19 | "license": "ISC", 20 | "dependencies": { 21 | "bcryptjs": "^2.4.3", 22 | "body-parser": "^1.20.2", 23 | "cors": "^2.8.5", 24 | "dotenv": "^16.0.3", 25 | "express": "^4.18.2", 26 | "jsonwebtoken": "^9.0.0", 27 | "mongoose": "^7.1.0", 28 | "multer": "^1.4.5-lts.1", 29 | "validator": "^13.9.0" 30 | }, 31 | "devDependencies": { 32 | "nodemon": "^2.0.22" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /models/forumPost.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const forumPostSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | }, 8 | content: { 9 | type: String, 10 | required: true, 11 | }, 12 | createdBy: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: "User", 15 | required: true, 16 | }, 17 | course: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: "Course", 20 | required: true, 21 | }, 22 | tags: [ 23 | { 24 | type: String, 25 | }, 26 | ], 27 | votes: [ 28 | { 29 | user: { 30 | type: mongoose.Schema.Types.ObjectId, 31 | ref: "User", 32 | }, 33 | vote: { 34 | type: Number, 35 | enum: [-1, 1], 36 | }, 37 | }, 38 | ], 39 | acceptedAnswer: { 40 | type: Boolean, 41 | default: false, 42 | }, 43 | createdAt: { 44 | type: Date, 45 | default: Date.now, 46 | }, 47 | }); 48 | 49 | const ForumPost = mongoose.model("ForumPost", forumPostSchema); 50 | 51 | module.exports = ForumPost; 52 | -------------------------------------------------------------------------------- /routes/course.routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const courseController = require("../controllers/course.controller"); 3 | const auth = require("../middlewares/auth"); 4 | const { authorize } = require("../utils/role"); 5 | const upload = require("../middlewares/multerConfig"); 6 | const router = new express.Router(); 7 | 8 | router.use(auth); 9 | 10 | router.post("/", authorize("instructor"), courseController.createCourse); 11 | router.get("/", courseController.getCourses); 12 | router.get("/:id", courseController.getCourseById); 13 | router.patch("/:id", authorize("instructor"), courseController.updateCourse); 14 | router.delete("/:id", authorize("instructor"), courseController.deleteCourse); 15 | 16 | router.post( 17 | "/:id/enroll", 18 | authorize("student"), 19 | courseController.enrollInCourse 20 | ); 21 | 22 | // Route to upload course material (Instructor only) 23 | router.post( 24 | "/:id/materials", 25 | authorize("instructor"), 26 | upload.single("materials"), 27 | courseController.uploadCourseMaterial 28 | ); 29 | 30 | router.get("/:courseId/lessons", courseController.getCourseLessons); 31 | router.post("/:courseId/lessons", courseController.createLesson); 32 | router.get("/:courseId/lessons/:lessonId", courseController.getLesson); 33 | router.patch("/:courseId/lessons/:lessonId", courseController.updateLesson); 34 | router.delete("/:courseId/lessons/:lessonId", courseController.deleteLesson); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /controllers/badge.controller.js: -------------------------------------------------------------------------------- 1 | const Badge = require("../models/badge.model"); 2 | 3 | const createBadge = async (req, res) => { 4 | const badge = new Badge(req.body); 5 | 6 | try { 7 | await badge.save(); 8 | res.status(201).json(badge); 9 | } catch (error) { 10 | res.status(400).json({ error: "Error creating badge" }); 11 | } 12 | }; 13 | 14 | const getAllBadges = async (req, res) => { 15 | try { 16 | const badges = await Badge.find({}); 17 | res.status(200).json(badges); 18 | } catch (error) { 19 | res.status(500).json({ error: "Error fetching badges" }); 20 | } 21 | }; 22 | 23 | const updateBadge = async (req, res) => { 24 | const updates = Object.keys(req.body); 25 | const allowedUpdates = ["name", "description", "pointsRequired"]; 26 | const isValidUpdate = updates.every((update) => 27 | allowedUpdates.includes(update) 28 | ); 29 | 30 | if (!isValidUpdate) { 31 | return res.status(400).json({ error: "Invalid updates" }); 32 | } 33 | 34 | try { 35 | const badge = await Badge.findById(req.params.badgeId); 36 | 37 | if (!badge) { 38 | return res.status(404).json({ error: "Badge not found" }); 39 | } 40 | 41 | updates.forEach((update) => (badge[update] = req.body[update])); 42 | await badge.save(); 43 | res.status(200).json(badge); 44 | } catch (error) { 45 | res.status(500).json({ error: "Error updating badge" }); 46 | } 47 | }; 48 | 49 | const deleteBadge = async (req, res) => { 50 | try { 51 | const badge = await Badge.findByIdAndDelete(req.params.badgeId); 52 | 53 | if (!badge) { 54 | return res.status(404).json({ error: "Badge not found" }); 55 | } 56 | 57 | res.status(200).json({ message: "Badge deleted" }); 58 | } catch (error) { 59 | res.status(500).json({ error: "Error deleting badge" }); 60 | } 61 | }; 62 | 63 | module.exports = { 64 | createBadge, 65 | getAllBadges, 66 | updateBadge, 67 | deleteBadge, 68 | }; 69 | -------------------------------------------------------------------------------- /controllers/quiz.controller.js: -------------------------------------------------------------------------------- 1 | const Quiz = require("../models/quiz.model"); 2 | const Course = require("../models/course.model"); 3 | 4 | const createQuiz = async (req, res) => { 5 | const quiz = new Quiz({ ...req.body, course: req.params.courseId }); 6 | 7 | try { 8 | await quiz.save(); 9 | res.status(201).send(quiz); 10 | } catch (error) { 11 | res.status(400).send(error); 12 | } 13 | }; 14 | 15 | const getQuizzesByCourse = async (req, res) => { 16 | try { 17 | const quizzes = await Quiz.find({ course: req.params.courseId }); 18 | res.send(quizzes); 19 | } catch (error) { 20 | res.status(500).send(); 21 | } 22 | }; 23 | 24 | const attemptQuiz = async (req, res) => { 25 | try { 26 | const quiz = await Quiz.findById(req.params.quizId); 27 | if (!quiz) { 28 | return res.status(404).send({ error: "Quiz not found" }); 29 | } 30 | const courseId = quiz.course._id; 31 | const studentId = req.user._id; 32 | const course = await Course.findById(courseId); 33 | 34 | if (!course.enrolledStudents.includes(studentId)) { 35 | return res.status(403).send({ 36 | error: "You must be enrolled in the course to attempt the quiz", 37 | }); 38 | } 39 | const answers = req.body.answers; 40 | const totalQuestions = quiz.questions.length; 41 | let correctAnswers = 0; 42 | 43 | for (let i = 0; i < totalQuestions; i++) { 44 | if (answers[i] === quiz.questions[i].correctAnswer) { 45 | correctAnswers++; 46 | } 47 | } 48 | 49 | const score = (correctAnswers / totalQuestions) * 100; 50 | 51 | quiz.attempts.push({ student: req.user._id, score }); 52 | await quiz.save(); 53 | 54 | res.status(200).send({ message: "Quiz attempted successfully", score }); 55 | } catch (error) { 56 | res.status(500).send({ error: "Error attempting quiz" }); 57 | } 58 | }; 59 | 60 | module.exports = { 61 | createQuiz, 62 | getQuizzesByCourse, 63 | attemptQuiz, 64 | }; 65 | -------------------------------------------------------------------------------- /models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcryptjs"); 3 | const validator = require("validator"); 4 | const dotenv = require("dotenv"); 5 | const jwt = require("jsonwebtoken"); 6 | dotenv.config(); 7 | 8 | const userSchema = new mongoose.Schema({ 9 | name: { type: String, required: true, trim: true }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | trim: true, 15 | lowercase: true, 16 | validate(value) { 17 | if (!validator.isEmail(value)) { 18 | throw new Error("Email is invalid"); 19 | } 20 | }, 21 | }, 22 | password: { 23 | type: String, 24 | required: true, 25 | minlength: 6, 26 | trim: true, 27 | validate(value) { 28 | if (value.toLowerCase().includes("password")) { 29 | throw new Error('Password must not contain the word "password"'); 30 | } 31 | }, 32 | }, 33 | role: { type: String, default: "student" }, 34 | tokens: [ 35 | { 36 | token: { 37 | type: String, 38 | required: false, 39 | }, 40 | }, 41 | ], 42 | points: { 43 | type: Number, 44 | default: 0, 45 | }, 46 | badges: [ 47 | { 48 | type: mongoose.Schema.Types.ObjectId, 49 | ref: "Badge", 50 | }, 51 | ], 52 | }); 53 | userSchema.pre("save", async function (next) { 54 | if (this.isModified("password")) { 55 | this.password = await bcrypt.hash(this.password, 10); 56 | } 57 | next(); 58 | }); 59 | 60 | userSchema.methods.verifyPassword = function (password) { 61 | return bcrypt.compare(password, this.password); 62 | }; 63 | userSchema.methods.generateAuthToken = async function () { 64 | const user = this; 65 | const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET); 66 | 67 | user.tokens = user.tokens.concat({ token }); 68 | await user.save(); 69 | 70 | return token; 71 | }; 72 | 73 | userSchema.methods.toJSON = function () { 74 | const user = this; 75 | const userObject = user.toObject(); 76 | 77 | delete userObject.password; 78 | delete userObject.tokens; 79 | 80 | return userObject; 81 | }; 82 | 83 | module.exports = mongoose.model("User", userSchema); 84 | -------------------------------------------------------------------------------- /controllers/forumComments.controller.js: -------------------------------------------------------------------------------- 1 | const Comment = require("../models/forumComment.model"); 2 | 3 | // Create comment 4 | async function createComment(req, res) { 5 | try { 6 | const { content } = req.body; 7 | const newComment = new Comment({ 8 | content, 9 | createdBy: req.user._id, 10 | post: req.params.postId, 11 | }); 12 | 13 | await newComment.save(); 14 | res.status(201).json(newComment); 15 | } catch (error) { 16 | res.status(400).json({ message: error.message }); 17 | } 18 | } 19 | 20 | // Update comment 21 | async function updateComment(req, res) { 22 | try { 23 | const { content } = req.body; 24 | const updatedComment = await Comment.findOneAndUpdate( 25 | { _id: req.params.commentId, createdBy: req.user._id }, 26 | { content }, 27 | { new: true, runValidators: true } 28 | ); 29 | 30 | if (!updatedComment) { 31 | return res.status(404).json({ 32 | message: "Comment not found or you do not have permission to edit it.", 33 | }); 34 | } 35 | 36 | res.status(200).json(updatedComment); 37 | } catch (error) { 38 | res.status(400).json({ message: error.message }); 39 | } 40 | } 41 | 42 | // Delete comment 43 | async function deleteComment(req, res) { 44 | try { 45 | const deletedComment = await Comment.findOneAndDelete({ 46 | _id: req.params.commentId, 47 | createdBy: req.user._id, 48 | }); 49 | 50 | if (!deletedComment) { 51 | return res.status(404).json({ 52 | message: 53 | "Comment not found or you do not have permission to delete it.", 54 | }); 55 | } 56 | 57 | res.status(200).json({ message: "Comment deleted successfully" }); 58 | } catch (error) { 59 | res.status(400).json({ message: error.message }); 60 | } 61 | } 62 | 63 | // Get comments by post 64 | async function getCommentsByPost(req, res) { 65 | try { 66 | const comments = await Comment.find({ post: req.params.postId }); 67 | res.status(200).json(comments); 68 | } catch (error) { 69 | res.status(400).json({ message: error.message }); 70 | } 71 | } 72 | 73 | // Exports at the end of the file 74 | module.exports = { 75 | createComment, 76 | updateComment, 77 | deleteComment, 78 | getCommentsByPost, 79 | }; 80 | -------------------------------------------------------------------------------- /controllers/progress.controller.js: -------------------------------------------------------------------------------- 1 | const Progress = require("../models/progress.model"); 2 | const Course = require("../models/course.model"); 3 | const Lesson = require("../models/lesson.model"); 4 | const userModel = require("../models/user.model"); 5 | 6 | exports.markLessonAsComplete = async (req, res) => { 7 | const { courseId, lessonId } = req.params; 8 | const userId = req.user._id; 9 | const user = await userModel.findById(userId); 10 | if (!user) { 11 | return res.status(404).json({ error: "User or lesson not found" }); 12 | } 13 | 14 | try { 15 | const course = await Course.findById(courseId); 16 | if (!course) { 17 | return res.status(404).json({ error: "Course not found" }); 18 | } 19 | 20 | const lesson = await Lesson.findById(lessonId); 21 | if (!lesson) { 22 | return res.status(404).json({ error: "Lesson not found" }); 23 | } 24 | 25 | const progress = await Progress.findOne({ user: userId, course: courseId }); 26 | 27 | if (progress) { 28 | if (!progress.completedLessons.includes(lessonId)) { 29 | progress.completedLessons.push(lessonId); 30 | await progress.save(); 31 | // Increase user's points 32 | user.points += 10; // Award 10 points for completing a lesson 33 | await user.save(); 34 | 35 | // Check and award badges 36 | await checkAndAwardBadge(user._id); 37 | } 38 | } else { 39 | const newProgress = new Progress({ 40 | user: userId, 41 | course: courseId, 42 | completedLessons: [lessonId], 43 | }); 44 | await newProgress.save(); 45 | // Increase user's points 46 | user.points += 10; // Award 10 points for completing a lesson 47 | await user.save(); 48 | 49 | // Check and award badges 50 | await checkAndAwardBadge(user._id); 51 | } 52 | 53 | res.status(200).json({ message: "Lesson marked as complete" }); 54 | } catch (error) { 55 | res.status(500).json({ error: "Internal Server Error" }); 56 | } 57 | }; 58 | 59 | exports.getProgressByCourse = async (req, res) => { 60 | const { courseId } = req.params; 61 | const userId = req.user._id; 62 | 63 | try { 64 | const course = await Course.findById(courseId); 65 | if (!course) { 66 | return res.status(404).json({ error: "Course not found" }); 67 | } 68 | 69 | const progress = await Progress.findOne({ 70 | user: userId, 71 | course: courseId, 72 | }).populate("completedLessons"); 73 | 74 | if (!progress) { 75 | return res 76 | .status(404) 77 | .json({ error: "Progress not found for the course" }); 78 | } 79 | 80 | res.status(200).json(progress); 81 | } catch (error) { 82 | res.status(500).json({ error: "Internal Server Error" }); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /controllers/fourm.controller.js: -------------------------------------------------------------------------------- 1 | const ForumPost = require("../models/forumPost.model"); 2 | 3 | const createPost = async (req, res) => { 4 | try { 5 | const { title, content, tags } = req.body; 6 | 7 | const newPost = new ForumPost({ 8 | title, 9 | content, 10 | tags, 11 | createdBy: req.user._id, 12 | course: req.params.courseId, 13 | }); 14 | 15 | await newPost.save(); 16 | res.status(201).json(newPost); 17 | } catch (error) { 18 | res.status(400).json({ message: error.message }); 19 | } 20 | }; 21 | 22 | const getPosts = async (req, res) => { 23 | try { 24 | const posts = await ForumPost.find({ 25 | course: req.params.courseId, 26 | }).populate("createdBy", "name"); 27 | res.status(200).json(posts); 28 | } catch (error) { 29 | res.status(400).json({ message: error.message }); 30 | } 31 | }; 32 | 33 | const searchPosts = async (req, res) => { 34 | try { 35 | const { tags } = req.query; 36 | let query = { course: req.params.courseId }; 37 | 38 | if (tags) { 39 | const tagArray = tags.split(",").map((tag) => tag.trim()); 40 | query.tags = { $in: tagArray }; 41 | } 42 | 43 | const posts = await ForumPost.find(query).populate("createdBy", "name"); 44 | res.status(200).json(posts); 45 | } catch (error) { 46 | res.status(400).json({ message: error.message }); 47 | } 48 | }; 49 | 50 | const updatePost = async (req, res) => { 51 | try { 52 | const { title, content, tags } = req.body; 53 | 54 | const updatedPost = await ForumPost.findOneAndUpdate( 55 | { _id: req.params.postId, createdBy: req.user._id }, 56 | { title, content, tags }, 57 | { new: true, runValidators: true } 58 | ); 59 | 60 | if (!updatedPost) { 61 | return res.status(404).json({ 62 | message: "Post not found or you do not have permission to edit it.", 63 | }); 64 | } 65 | 66 | res.status(200).json(updatedPost); 67 | } catch (error) { 68 | res.status(400).json({ message: error.message }); 69 | } 70 | }; 71 | 72 | const votePost = async (req, res) => { 73 | try { 74 | const post = await ForumPost.findById(req.params.postId); 75 | if (!post) { 76 | return res.status(404).json({ message: "Post not found" }); 77 | } 78 | 79 | const existingVote = post.votes.find( 80 | (v) => v.user.toString() === req.user._id.toString() 81 | ); 82 | if (existingVote) { 83 | existingVote.vote = req.body.vote; 84 | } else { 85 | post.votes.push({ user: req.user._id, vote: req.body.vote }); 86 | } 87 | 88 | await post.save(); 89 | res.status(200).json(post); 90 | } catch (error) { 91 | res.status(400).json({ message: error.message }); 92 | } 93 | }; 94 | 95 | const acceptAnswer = async (req, res) => { 96 | try { 97 | const post = await ForumPost.findById(req.params.postId); 98 | if (!post) { 99 | return res.status(404).json({ message: "Post not found" }); 100 | } 101 | 102 | post.acceptedAnswer = req.body.accepted; 103 | await post.save(); 104 | res.status(200).json(post); 105 | } catch (error) { 106 | res.status(400).json({ message: error.message }); 107 | } 108 | }; 109 | 110 | module.exports = { 111 | createPost, 112 | getPosts, 113 | searchPosts, 114 | updatePost, 115 | votePost, 116 | acceptAnswer, 117 | }; 118 | -------------------------------------------------------------------------------- /controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user.model"); 2 | const Course = require("../models/course.model"); 3 | const dotenv = require("dotenv"); 4 | 5 | dotenv.config(); 6 | 7 | // Register a new user 8 | const registerUser = async (req, res) => { 9 | const user = new User(req.body); 10 | try { 11 | await user.save(); 12 | const token = await user.generateAuthToken(); 13 | res.status(201).send({ user, token }); 14 | } catch (error) { 15 | res.status(400).send(error.message); 16 | } 17 | }; 18 | 19 | // Log in an existing user 20 | const loginUser = async (req, res) => { 21 | try { 22 | const user = await User.findOne({ email: req.body.email }); 23 | if (!user) { 24 | throw new Error("Invalid email or password"); 25 | } 26 | 27 | const isMatch = await user.verifyPassword(req.body.password); 28 | 29 | if (!isMatch) { 30 | throw new Error("Invalid email or password"); 31 | } 32 | 33 | const token = await user.generateAuthToken(); 34 | res.send({ user, token }); 35 | } catch (error) { 36 | res.status(400).send({ error: error.message }); 37 | } 38 | }; 39 | 40 | // Get the user's profile 41 | const getProfile = async (req, res) => { 42 | res.send(req.user.toJSON()); 43 | }; 44 | 45 | // Update the user's profile 46 | const updateProfile = async (req, res) => { 47 | const updates = Object.keys(req.body); 48 | const allowedUpdates = ["name", "email", "password", "role"]; 49 | const isValidOperation = updates.every((update) => 50 | allowedUpdates.includes(update) 51 | ); 52 | 53 | if (!isValidOperation) { 54 | return res.status(400).send({ error: "Invalid updates!" }); 55 | } 56 | 57 | try { 58 | updates.forEach((update) => (req.user[update] = req.body[update])); 59 | await req.user.save(); 60 | res.send(req.user.toJSON()); 61 | } catch (error) { 62 | res.status(400).send(error); 63 | } 64 | }; 65 | 66 | // Delete the user's profile 67 | const deleteProfile = async (req, res) => { 68 | try { 69 | await req.user.remove(); 70 | res.send(req.user); 71 | } catch (error) { 72 | res.status(500).send(error); 73 | } 74 | }; 75 | 76 | // Get all users (admin only) 77 | const getUsers = async (req, res) => { 78 | try { 79 | const users = await User.find({}); 80 | res.send(users); 81 | } catch (error) { 82 | res.status(500).send(error); 83 | } 84 | }; 85 | const getInstructorCourses = async (req, res) => { 86 | const instructorId = req.params.id; 87 | 88 | try { 89 | const instructor = await User.findById(instructorId).select( 90 | "-password -tokens" 91 | ); 92 | 93 | if (!instructor) { 94 | return res.status(404).send({ error: "Instructor not found" }); 95 | } 96 | 97 | const courses = await Course.find({ createdBy: instructor._id }); 98 | res.status(200).send({ instructor, courses }); 99 | } catch (error) { 100 | res.status(500).send({ error: "Error fetching instructor courses" }); 101 | } 102 | }; 103 | const getLeaderboard = async (req, res) => { 104 | try { 105 | const users = await User.find({}) 106 | .select("name points badges") 107 | .populate("badges") 108 | .sort({ points: -1 }); // Sort by points in descending order 109 | 110 | res.status(200).json(users); 111 | } catch (error) { 112 | res.status(500).json({ error: "Internal Server Error" }); 113 | } 114 | }; 115 | module.exports = { 116 | registerUser, 117 | loginUser, 118 | getProfile, 119 | updateProfile, 120 | deleteProfile, 121 | getUsers, 122 | getInstructorCourses, 123 | getLeaderboard, 124 | }; 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E-Learning Platform API 2 | 3 | A RESTful API built using Node.js, Express, and MongoDB for creating and managing an e-learning platform where instructors can create courses, and students can enroll in courses, access course materials, and attempt quizzes. The platform also features user authentication and authorization, discussion forums, progress tracking, and gamification. 4 | 5 | ## Features 6 | 7 | - User authentication and authorization with JWT 8 | - Role-based access control (Student, Admin and Instructor) 9 | - Course and lesson management (Create, Read, Update, Delete) 10 | - Student enrollment in courses 11 | - Quiz management (Create quizzes with multiple-choice questions) 12 | - Quiz attempts and score tracking 13 | - Discussion forums with posts and comments 14 | - Progress tracking for students 15 | - Gamification with badges and leaderboard 16 | 17 | ## Prerequisites 18 | 19 | - Node.js (v14 or later) 20 | - MongoDB (v4.4 or later) 21 | 22 | ## Installation 23 | 24 | 1. Clone the repository: 25 | `git clone https://github.com/shahghasiadil/elearning-platform-api` 26 | 27 | `cd elearning-platform-api` 28 | 29 | 2. Install the dependencies: 30 | 31 | ``` 32 | npm install 33 | ``` 34 | 35 | 3. Create a `.env` file in the root directory and add your MongoDB connection string and JWT secret: 36 | 37 | 4. Start the server: 38 | 39 | ``` 40 | npm run start 41 | ``` 42 | 43 | The API will be available at `http://localhost:3000/api`. 44 | 45 | ## API Endpoints 46 | 47 | ### User 48 | 49 | - POST `/api/auth/register`: Register a new user 50 | - POST `/api/auth/login`: Login an existing user 51 | - GET `/api/auth/me`: Get the logged-in user's profile 52 | - PATCH `/api/auth/me`: Update the logged-in user's profile 53 | - DELETE `/api/auth/me`: Delete the logged-in user's profile 54 | - GET `/api/auth/users`: Get all users (Admin only) 55 | - GET `/api/auth/instructors/:id/courses`: Get Instructor Courses 56 | 57 | ### Courses 58 | 59 | - POST `/api/courses`: Create a new course (Instructor only) 60 | - GET `/api/courses`: Get all courses 61 | - GET `/api/courses/:id`: Get a course by ID 62 | - PATCH `/api/courses/:id`: Update a course (Instructor only) 63 | - DELETE `/api/courses/:id`: Delete a course (Instructor only) 64 | - POST `/api/courses/:id/enroll`: Enroll in a course (Student only) 65 | - POST `/api/courses/:id/materials`: Upload course materials (Instructor only) 66 | 67 | ### Quizzes 68 | 69 | - POST `/api/courses/:courseId/quizzes`: Create a quiz for a course (Instructor only) 70 | - GET `/api/courses/:courseId/quizzes`: Get all quizzes for a course 71 | - POST `/api/quizzes/:quizId/attempt`: Attempt a quiz (Student only, enrolled courses) 72 | 73 | ### Forums 74 | 75 | - POST `/api/courses/:courseId/forums`: Create a forum post for a course (Authenticated users) 76 | - GET `/api/courses/:courseId/forums`: Get all forum posts for a course 77 | - PUT `/api/courses/:courseId/forums/:postId`: Update a forum post (Post author only) 78 | - DELETE `/api/courses/:courseId/forums/:postId`: Delete a forum post (Post author only) 79 | - GET `/api/courses/:courseId/forums/:postId`: Get a forum post by ID 80 | 81 | ### Comments 82 | 83 | - POST `/api/courses/:courseId/forums/:postId/comments`: Create a comment for a forum post (Authenticated users) 84 | - GET `/api/courses/:courseId/forums/:postId/comments`: Get all comments for a forum post 85 | - PUT `/api/courses/:courseId/forums/:postId/comments/:commentId`: Update a comment (Comment author only) 86 | - DELETE `/api/courses/:courseId/forums/:postId/comments/:commentId`: Delete a comment (Comment author only) 87 | 88 | ### Lessons 89 | 90 | - POST `/api/courses/:courseId/lessons`: Create a lesson for a course (Course instructor only) 91 | - GET `/api/courses/:courseId/lessons`: Get all lessons for a course 92 | - GET `/api/courses/:courseId/lessons/:lessonId`: Get a specific lesson by its ID 93 | - PUT `/api/courses/:courseId/lessons/:lessonId`: Update a lesson (Course instructor only) 94 | - DELETE `/api/courses/:courseId/lessons/:lessonId`: Delete a lesson (Course instructor only) 95 | 96 | ### Badges 97 | 98 | - POST `/api/badges`: Create a badge (Admin only) 99 | - GET `/api/badges`: Get all badges (Admin only) 100 | - PUT `/api/badges/:badgeId`: Update a badge (Admin only) 101 | - DELETE `/api/badges/:badgeId`: Delete a badge (Admin only) 102 | 103 | ### Progress 104 | 105 | - GET `/api/courses/:courseId/progress`: Get course progress for the current user (Student only, enrolled courses) 106 | - POST `/api/courses/:courseId/progress/:lessonId`: Mark a lesson as completed (Student only, enrolled courses) 107 | 108 | ### Leaderboard 109 | 110 | - GET `/api/leaderboard`: Get the leaderboard based on total points 111 | 112 | ## License 113 | 114 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 115 | -------------------------------------------------------------------------------- /controllers/course.controller.js: -------------------------------------------------------------------------------- 1 | const Course = require("../models/course.model"); 2 | const Lesson = require("../models/lesson.model"); 3 | 4 | // Create Course 5 | const createCourse = async (req, res) => { 6 | const course = new Course({ ...req.body, createdBy: req.user._id }); 7 | 8 | try { 9 | await course.save(); 10 | res.status(201).send(course); 11 | } catch (error) { 12 | res.status(400).send(error); 13 | } 14 | }; 15 | 16 | // Get all courses 17 | const getCourses = async (req, res) => { 18 | try { 19 | const courses = await Course.find({}); 20 | res.status(200).send(courses); 21 | } catch (error) { 22 | res.status(500).send(error); 23 | } 24 | }; 25 | 26 | // Get course by ID 27 | const getCourseById = async (req, res) => { 28 | const _id = req.params.id; 29 | 30 | try { 31 | const course = await Course.findOne({ _id }); 32 | 33 | if (!course) { 34 | return res.status(404).send({ error: "Course not found." }); 35 | } 36 | 37 | res.status(200).send(course); 38 | } catch (error) { 39 | res.status(500).send(error); 40 | } 41 | }; 42 | 43 | // Update course 44 | const updateCourse = async (req, res) => { 45 | const updates = Object.keys(req.body); 46 | const allowedUpdates = ["title", "description"]; 47 | const isValidOperation = updates.every((update) => 48 | allowedUpdates.includes(update) 49 | ); 50 | 51 | if (!isValidOperation) { 52 | return res.status(400).send({ error: "Invalid updates!" }); 53 | } 54 | 55 | try { 56 | const course = await Course.findOne({ 57 | _id: req.params.id, 58 | createdBy: req.user._id, 59 | }); 60 | 61 | if (!course) { 62 | return res.status(404).send({ error: "Course not found." }); 63 | } 64 | 65 | updates.forEach((update) => (course[update] = req.body[update])); 66 | await course.save(); 67 | 68 | res.status(200).send(course); 69 | } catch (error) { 70 | res.status(400).send(error); 71 | } 72 | }; 73 | 74 | // Delete course 75 | const deleteCourse = async (req, res) => { 76 | try { 77 | const course = await Course.findOneAndDelete({ 78 | _id: req.params.id, 79 | createdBy: req.user._id, 80 | }); 81 | 82 | if (!course) { 83 | return res.status(404).send({ error: "Course not found." }); 84 | } 85 | 86 | res.status(200).send(course); 87 | } catch (error) { 88 | res.status(500).send(error); 89 | } 90 | }; 91 | 92 | // Enroll Course by Student 93 | const enrollInCourse = async (req, res) => { 94 | const courseId = req.params.id; 95 | const studentId = req.user._id; 96 | 97 | try { 98 | const course = await Course.findById(courseId); 99 | if (!course) { 100 | return res.status(404).send({ error: "Course not found" }); 101 | } 102 | 103 | if (course.enrolledStudents.includes(studentId)) { 104 | return res 105 | .status(400) 106 | .send({ error: "Student already enrolled in this course" }); 107 | } 108 | 109 | course.enrolledStudents.push(studentId); 110 | await course.save(); 111 | 112 | res 113 | .status(200) 114 | .send({ message: "Enrolled in course successfully", course }); 115 | } catch (error) { 116 | res.status(500).send({ error: "Error enrolling in course" }); 117 | } 118 | }; 119 | 120 | const uploadCourseMaterial = async (req, res) => { 121 | try { 122 | const course = await Course.findById(req.params.id); 123 | 124 | if (!course) { 125 | return res.status(404).send({ error: "Course not found" }); 126 | } 127 | 128 | // Save the uploaded file's path to the course materials array 129 | course.materials.push(req.file.path); 130 | await course.save(); 131 | 132 | res.status(201).send({ 133 | message: "Course material uploaded successfully", 134 | path: req.file.path, 135 | }); 136 | } catch (error) { 137 | res.status(500).send({ error: "Error uploading course material" }); 138 | } 139 | }; 140 | 141 | // Create course lesson 142 | const createLesson = async (req, res) => { 143 | const { courseId } = req.params; 144 | const { title, content } = req.body; 145 | 146 | try { 147 | const course = await Course.findById(courseId); 148 | if (!course) { 149 | return res.status(404).json({ error: "Course not found" }); 150 | } 151 | 152 | // Ensure the user is the course creator 153 | if (course.createdBy.toString() !== req.user._id.toString()) { 154 | return res.status(403).json({ error: "Unauthorized" }); 155 | } 156 | 157 | const lesson = new Lesson({ 158 | title, 159 | content, 160 | course: courseId, 161 | }); 162 | 163 | await lesson.save(); 164 | 165 | course.lessons.push(lesson._id); 166 | await course.save(); 167 | 168 | res.status(201).json(lesson); 169 | } catch (error) { 170 | res.status(500).json({ error: "Internal Server Error" }); 171 | } 172 | }; 173 | 174 | const getLesson = async (req, res) => { 175 | const { courseId, lessonId } = req.params; 176 | 177 | try { 178 | const course = await Course.findById(courseId); 179 | if (!course) { 180 | return res.status(404).json({ error: "Course not found" }); 181 | } 182 | 183 | const lesson = await Lesson.findOne({ _id: lessonId, course: courseId }); 184 | if (!lesson) { 185 | return res.status(404).json({ error: "Lesson not found" }); 186 | } 187 | 188 | res.status(200).json(lesson); 189 | } catch (error) { 190 | res.status(500).json({ error: "Internal Server Error" }); 191 | } 192 | }; 193 | 194 | const updateLesson = async (req, res) => { 195 | const { courseId, lessonId } = req.params; 196 | const { title, content } = req.body; 197 | 198 | try { 199 | const course = await Course.findById(courseId); 200 | if (!course) { 201 | return res.status(404).json({ error: "Course not found" }); 202 | } 203 | 204 | if (course.createdBy.toString() !== req.user._id.toString()) { 205 | return res.status(403).json({ error: "Unauthorized" }); 206 | } 207 | 208 | const lesson = await Lesson.findOneAndUpdate( 209 | { _id: lessonId, course: courseId }, 210 | { title, content }, 211 | { new: true, runValidators: true } 212 | ); 213 | 214 | if (!lesson) { 215 | return res.status(404).json({ error: "Lesson not found" }); 216 | } 217 | 218 | res.status(200).json(lesson); 219 | } catch (error) { 220 | res.status(500).json({ error: "Internal Server Error" }); 221 | } 222 | }; 223 | 224 | const deleteLesson = async (req, res) => { 225 | const { courseId, lessonId } = req.params; 226 | 227 | try { 228 | const course = await Course.findById(courseId); 229 | if (!course) { 230 | return res.status(404).json({ error: "Course not found" }); 231 | } 232 | 233 | if (course.createdBy.toString() !== req.user._id.toString()) { 234 | return res.status(403).json({ error: "Unauthorized" }); 235 | } 236 | 237 | const lesson = await Lesson.findOneAndDelete({ 238 | _id: lessonId, 239 | course: courseId, 240 | }); 241 | 242 | if (!lesson) { 243 | return res.status(404).json({ error: "Lesson not found" }); 244 | } 245 | 246 | course.lessons = course.lessons.filter((id) => id.toString() !== lessonId); 247 | await course.save(); 248 | 249 | res.status(200).json({ message: "Lesson deleted successfully" }); 250 | } catch (error) { 251 | res.status(500).json({ error: "Internal Server Error" }); 252 | } 253 | }; 254 | 255 | const getCourseLessons = async (req, res) => { 256 | const { courseId } = req.params; 257 | 258 | try { 259 | const course = await Course.findById(courseId).populate("lessons"); 260 | if (!course) { 261 | return res.status(404).json({ error: "Course not found" }); 262 | } 263 | 264 | const courseWithLessons = { 265 | _id: course._id, 266 | title: course.title, 267 | description: course.description, 268 | createdBy: course.createdBy, 269 | lessons: course.lessons, 270 | }; 271 | 272 | res.status(200).json(courseWithLessons); 273 | } catch (error) { 274 | res.status(500).json({ error: "Internal Server Error" }); 275 | } 276 | }; 277 | 278 | module.exports = { 279 | createCourse, 280 | getCourses, 281 | getCourseById, 282 | updateCourse, 283 | deleteCourse, 284 | enrollInCourse, 285 | uploadCourseMaterial, 286 | createLesson, 287 | updateLesson, 288 | deleteLesson, 289 | getLesson, 290 | getCourseLessons, 291 | }; 292 | --------------------------------------------------------------------------------