├── vercel.json ├── models ├── Certificate.js ├── Skill.js ├── Project.js ├── MainCategory.js └── Course.js ├── routes ├── certificateRoutes.js ├── projectRoutes.js ├── skillRoutes.js ├── courseRoutes.js ├── mainCategoryRoutes.js └── noteRoutes.js ├── package.json ├── .gitignore ├── LICENSE ├── controller ├── certificateController.js ├── skillController.js ├── ProjectController.js ├── courseController.js ├── noteController.js └── mainCategoryController.js ├── server.js └── Readme.md /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "rewrites": [{ "source": "/(.*)", "destination": "server.js" }] 10 | } 11 | -------------------------------------------------------------------------------- /models/Certificate.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const certificateSchema = new mongoose.Schema({ 4 | imageUrl: { 5 | type: String, 6 | required: true, 7 | }, 8 | courseName: { 9 | type: String, 10 | required: true, 11 | }, 12 | }); 13 | 14 | const Certificate = mongoose.model("Certificate", certificateSchema); 15 | 16 | module.exports = Certificate; 17 | -------------------------------------------------------------------------------- /routes/certificateRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { 4 | getAllCertificates, 5 | addCertificate, 6 | deleteCertificate, 7 | } = require("../controller/certificateController"); 8 | 9 | // Routes for certificates 10 | router.get("/", getAllCertificates); 11 | router.post("/", addCertificate); 12 | router.delete("/:id", deleteCertificate); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udemy-trackker", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start": "nodemon server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "description": "", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.4.5", 16 | "express": "^4.21.1", 17 | "helmet": "^8.0.0", 18 | "mongoose": "^8.8.2", 19 | "nodemon": "^3.1.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /routes/projectRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | getAllProjects, 4 | addProject, 5 | updateProject, 6 | deleteProject, 7 | } = require("../controller/ProjectController"); 8 | 9 | const router = express.Router(); 10 | 11 | // Route to fetch all projects 12 | router.get("/", getAllProjects); 13 | 14 | // Route to add a new project 15 | router.post("/", addProject); 16 | 17 | // Route to update an existing project by ID 18 | router.put("/:id", updateProject); 19 | 20 | // Route to delete a project by ID 21 | router.delete("/:id", deleteProject); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /routes/skillRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const skillController = require("../controller/skillController"); 4 | 5 | // Create a new skill 6 | router.post("/", skillController.createSkill); 7 | 8 | // Get all skills 9 | router.get("/", skillController.getAllSkills); 10 | 11 | // Get a single skill by ID 12 | router.get("/:id", skillController.getSkillById); 13 | 14 | // Update a skill 15 | router.put("/:id", skillController.updateSkill); 16 | 17 | // Delete a skill 18 | router.delete("/:id", skillController.deleteSkill); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /models/Skill.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const skillSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | trim: true, 9 | }, 10 | description: { 11 | type: String, 12 | required: true, 13 | }, 14 | level: { 15 | type: String, 16 | enum: ["Beginner", "Intermediate", "Advanced"], 17 | required: true, 18 | }, 19 | icon: { 20 | type: String, // Stores the name of the React Icon component (e.g., 'FaPython') 21 | required: true, 22 | }, 23 | }); 24 | 25 | const Skill = mongoose.model("Skill", skillSchema); 26 | 27 | module.exports = Skill; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Environment variables 8 | .env 9 | 10 | # Logs 11 | logs/ 12 | *.log 13 | *.log.* 14 | pids/ 15 | *.pid 16 | *.pid.lock 17 | 18 | # Directory for user-generated content 19 | uploads/ 20 | 21 | # Database files 22 | *.sqlite3 23 | *.db 24 | 25 | # OS-specific files 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # IDE-specific files 30 | .vscode/ 31 | .idea/ 32 | *.sublime-project 33 | *.sublime-workspace 34 | 35 | # Optional npm cache directory 36 | .npm/ 37 | 38 | # Optional eslint cache 39 | .eslintcache 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | # Output of 'npm pack' 45 | *.tgz 46 | 47 | # Build files 48 | dist/ 49 | build/ 50 | 51 | # Coverage reports 52 | coverage/ 53 | 54 | # Vercel or Netlify configuration 55 | .vercel/ 56 | .netlify/ 57 | -------------------------------------------------------------------------------- /routes/courseRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | createCourse, 4 | getCourses, 5 | getCourseById, 6 | updateCourse, 7 | deleteCourse, 8 | syncCourses, 9 | } = require("../controller/courseController"); 10 | const noteRoutes = require("./noteRoutes"); // Import note routes 11 | 12 | const router = express.Router(); 13 | 14 | // Main course routes 15 | router.post("/sync", syncCourses); 16 | router.post("/", createCourse); // Create a course 17 | router.get("/", getCourses); // Get all courses 18 | router.get("/:id", getCourseById); // Get a single course by ID 19 | router.put("/:id", updateCourse); // Update a course by ID 20 | router.delete("/:id", deleteCourse); // Delete a course by ID 21 | 22 | // Mount the notes routes under the course's notes path 23 | router.use("/:courseId/notes", noteRoutes); // Handles all /courses/:courseId/notes operations 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /models/Project.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const projectSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | }, 8 | description: { 9 | type: String, 10 | required: true, 11 | }, 12 | tech: { 13 | type: [String], // Array of technologies used in the project 14 | required: true, 15 | }, 16 | link: { 17 | type: String, // GitHub or repository link 18 | required: true, 19 | }, 20 | liveDemo: { 21 | type: String, // Live demo URL 22 | required: false, 23 | }, 24 | category: { 25 | type: String, // Project category (e.g., Web Development, Data Science) 26 | required: true, 27 | }, 28 | subCategory: { 29 | type: String, // Project subcategory (e.g., React, Node.js) 30 | required: false, 31 | }, 32 | }); 33 | 34 | const Project = mongoose.model("Project", projectSchema); 35 | 36 | module.exports = Project; 37 | -------------------------------------------------------------------------------- /models/MainCategory.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define the schema for sub-target goals 4 | const SubTargetGoalSchema = new mongoose.Schema({ 5 | name: { type: String, required: true }, // Name of the sub-goal 6 | isChecked: { type: Boolean, default: false }, // Checkbox state 7 | }); 8 | 9 | // Define the schema for main target goals 10 | const MainTargetGoalSchema = new mongoose.Schema({ 11 | name: { type: String, required: true }, // Name of the main goal 12 | isChecked: { type: Boolean, default: false }, // Checkbox state 13 | subGoals: [SubTargetGoalSchema], // Array of sub-target goals 14 | }); 15 | 16 | // Define the schema for main categories 17 | const MainCategorySchema = new mongoose.Schema({ 18 | name: { type: String, required: true }, // Name of the main category 19 | isChecked: { type: Boolean, default: false }, // Checkbox state 20 | mainGoals: [MainTargetGoalSchema], // Array of main target goals 21 | }); 22 | 23 | // Create the model 24 | const MainCategory = mongoose.model("MainCategory", MainCategorySchema); 25 | 26 | module.exports = MainCategory; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 rohanmistry231 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /routes/mainCategoryRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { 4 | createMainCategory, 5 | updateMainCategory, 6 | deleteMainCategory, 7 | createMainTargetGoal, 8 | updateMainTargetGoal, 9 | deleteMainTargetGoal, 10 | createSubTargetGoal, 11 | updateSubTargetGoal, 12 | deleteSubTargetGoal, 13 | getMainCategories, 14 | } = require("../controller/mainCategoryController"); 15 | 16 | // Fetch all main categories 17 | router.get("/", getMainCategories); 18 | 19 | // Routes for Main Categories 20 | router.post("/", createMainCategory); // Create a main category 21 | router.put("/:categoryId", updateMainCategory); // Update a main category 22 | router.delete("/:categoryId", deleteMainCategory); // Delete a main category 23 | 24 | // Routes for Main Target Goals 25 | router.post("/:categoryId/main-goal", createMainTargetGoal); // Create a main target goal 26 | router.put("/:categoryId/main-goal/:goalId", updateMainTargetGoal); // Update a main target goal 27 | router.delete("/:categoryId/main-goal/:goalId", deleteMainTargetGoal); // Delete a main target goal 28 | 29 | // Routes for Sub Target Goals 30 | router.post("/:categoryId/main-goal/:goalId/sub-goal", createSubTargetGoal); // Create a sub-target goal 31 | router.put( 32 | "/:categoryId/main-goal/:goalId/sub-goal/:subGoalId", 33 | updateSubTargetGoal 34 | ); // Update a sub-target goal 35 | router.delete( 36 | "/:categoryId/main-goal/:goalId/sub-goal/:subGoalId", 37 | deleteSubTargetGoal 38 | ); // Delete a sub-target goal 39 | 40 | module.exports = router; 41 | -------------------------------------------------------------------------------- /models/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | // Define Note Schema (for individual questions and answers) 4 | const noteSchema = new mongoose.Schema({ 5 | question: { type: String, required: true }, 6 | answer: { type: String, required: true }, 7 | mainTargetCategory: { type: String, required: true }, // Main Target Goals Category (e.g., "Programming Language") 8 | mainTargetGoal: { type: String, required: true }, // Target Goal (e.g., "Python") 9 | subTargetGoal: { type: String }, // Sub Target Goal (e.g., "Data Types") 10 | createdAt: { type: Date, default: Date.now }, 11 | }); 12 | 13 | // Define Course Schema 14 | const courseSchema = new mongoose.Schema({ 15 | no: { type: Number, required: true }, 16 | name: { type: String, required: true }, 17 | category: { type: String, required: true }, 18 | categoryPriority: { 19 | type: String, 20 | enum: [ 21 | "High priority", 22 | "Medium priority", 23 | "Low priority", 24 | "Parallel Priority", 25 | ], 26 | default: "Medium priority", 27 | }, 28 | subCategory: { type: String }, 29 | subSubCategory: { type: String }, 30 | importantStatus: { 31 | type: String, 32 | enum: ["Important", "Very Important", "Extra", "Not Important"], 33 | default: "Important", 34 | }, 35 | status: { 36 | type: String, 37 | enum: ["Not Started Yet", "In Progress", "Completed"], 38 | default: "Not Started Yet", 39 | }, 40 | durationInHours: { type: Number, required: true }, 41 | subLearningSkillsSet: { type: [String] }, 42 | learningSkillsSet: { type: String }, 43 | notes: [noteSchema], // Embed notes as an array 44 | dateAdded: { type: Date, default: Date.now }, 45 | }); 46 | 47 | // Export Course Model 48 | const Course = mongoose.model("Course", courseSchema); 49 | module.exports = Course; 50 | -------------------------------------------------------------------------------- /controller/certificateController.js: -------------------------------------------------------------------------------- 1 | const Certificate = require("../models/Certificate"); 2 | 3 | // Fetch all certificates 4 | const getAllCertificates = async (req, res) => { 5 | try { 6 | const certificates = await Certificate.find(); 7 | res.status(200).json({ 8 | message: "Certificates retrieved successfully", 9 | certificates, 10 | }); 11 | } catch (error) { 12 | res.status(500).json({ 13 | message: "Error fetching certificates", 14 | error: error.message, 15 | }); 16 | } 17 | }; 18 | 19 | // Add a new certificate 20 | const addCertificate = async (req, res) => { 21 | try { 22 | const { imageUrl, courseName } = req.body; 23 | 24 | if (!imageUrl || !courseName) { 25 | return res 26 | .status(400) 27 | .json({ message: "Image URL and Course Name are required" }); 28 | } 29 | 30 | const newCertificate = new Certificate({ imageUrl, courseName }); 31 | await newCertificate.save(); 32 | 33 | res.status(201).json({ 34 | message: "Certificate added successfully", 35 | certificate: newCertificate, 36 | }); 37 | } catch (error) { 38 | res.status(500).json({ 39 | message: "Error adding certificate", 40 | error: error.message, 41 | }); 42 | } 43 | }; 44 | 45 | // Delete a certificate by its ID 46 | const deleteCertificate = async (req, res) => { 47 | try { 48 | const { id } = req.params; 49 | 50 | const deletedCertificate = await Certificate.findByIdAndDelete(id); 51 | if (!deletedCertificate) { 52 | return res.status(404).json({ message: "Certificate not found" }); 53 | } 54 | 55 | res.status(200).json({ 56 | message: "Certificate deleted successfully", 57 | certificate: deletedCertificate, 58 | }); 59 | } catch (error) { 60 | res.status(500).json({ 61 | message: "Error deleting certificate", 62 | error: error.message, 63 | }); 64 | } 65 | }; 66 | 67 | module.exports = { 68 | getAllCertificates, 69 | addCertificate, 70 | deleteCertificate, 71 | }; 72 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const cors = require("cors"); 4 | const helmet = require("helmet"); // Import Helmet 5 | const courseRoutes = require("./routes/courseRoutes"); 6 | const noteRoutes = require("./routes/noteRoutes"); 7 | const mainCategoryRoutes = require("./routes/mainCategoryRoutes"); 8 | const certificateRoutes = require("./routes/certificateRoutes"); 9 | const projectRoutes = require("./routes/projectRoutes"); 10 | const skillRoutes = require("./routes/skillRoutes"); 11 | require("dotenv").config(); 12 | 13 | const app = express(); 14 | 15 | // Configure Helmet to allow Vercel live script 16 | app.use( 17 | helmet({ 18 | contentSecurityPolicy: { 19 | directives: { 20 | "default-src": ["'self'"], 21 | "script-src": ["'self'", "'unsafe-inline'", "https://vercel.live"], 22 | "connect-src": ["'self'", "https://vercel.live"], 23 | }, 24 | }, 25 | }) 26 | ); 27 | 28 | // CORS configuration 29 | app.use( 30 | cors({ 31 | origin: "*", 32 | methods: ["GET", "POST", "PUT", "DELETE"], 33 | allowedHeaders: ["Content-Type", "Authorization"], 34 | }) 35 | ); 36 | 37 | app.use(express.json()); 38 | 39 | // Connect to MongoDB 40 | mongoose 41 | .connect(process.env.MONGODB_URI) 42 | .then(() => console.log("MongoDB connected")) 43 | .catch((err) => console.error("Connection failed", err)); 44 | 45 | // Root route 46 | app.get("/", (req, res) => { 47 | res.send("Welcome to the API!"); // Basic response for root path 48 | }); 49 | 50 | // API Routes 51 | app.use("/courses", courseRoutes); 52 | app.use("/courses/:courseId/notes", noteRoutes); 53 | app.use("/notes", noteRoutes); 54 | app.use("/main-category", mainCategoryRoutes); 55 | app.use("/certificate", certificateRoutes); 56 | app.use("/project", projectRoutes); 57 | app.use("/skill", skillRoutes); 58 | 59 | // Start the server 60 | const PORT = process.env.PORT || 5000; // Fallback to 5000 if PORT is not defined 61 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); 62 | -------------------------------------------------------------------------------- /controller/skillController.js: -------------------------------------------------------------------------------- 1 | const Skill = require("../models/Skill"); 2 | 3 | // Create a new skill 4 | exports.createSkill = async (req, res) => { 5 | try { 6 | const { name, description, level, icon } = req.body; 7 | const newSkill = new Skill({ name, description, level, icon }); 8 | await newSkill.save(); 9 | res.status(201).json(newSkill); 10 | } catch (error) { 11 | console.error(error); 12 | res.status(400).json({ error: "Error creating skill" }); 13 | } 14 | }; 15 | 16 | // Get all skills 17 | exports.getAllSkills = async (req, res) => { 18 | try { 19 | const skills = await Skill.find(); 20 | res.status(200).json(skills); 21 | } catch (error) { 22 | console.error(error); 23 | res.status(500).json({ error: "Error fetching skills" }); 24 | } 25 | }; 26 | 27 | // Get a single skill by ID 28 | exports.getSkillById = async (req, res) => { 29 | try { 30 | const skill = await Skill.findById(req.params.id); 31 | if (!skill) { 32 | return res.status(404).json({ error: "Skill not found" }); 33 | } 34 | res.status(200).json(skill); 35 | } catch (error) { 36 | console.error(error); 37 | res.status(500).json({ error: "Error fetching skill" }); 38 | } 39 | }; 40 | 41 | // Update a skill 42 | exports.updateSkill = async (req, res) => { 43 | try { 44 | const { name, description, level, icon } = req.body; 45 | const skill = await Skill.findByIdAndUpdate( 46 | req.params.id, 47 | { name, description, level, icon }, 48 | { new: true } 49 | ); 50 | if (!skill) { 51 | return res.status(404).json({ error: "Skill not found" }); 52 | } 53 | res.status(200).json(skill); 54 | } catch (error) { 55 | console.error(error); 56 | res.status(400).json({ error: "Error updating skill" }); 57 | } 58 | }; 59 | 60 | // Delete a skill 61 | exports.deleteSkill = async (req, res) => { 62 | try { 63 | const skill = await Skill.findByIdAndDelete(req.params.id); 64 | if (!skill) { 65 | return res.status(404).json({ error: "Skill not found" }); 66 | } 67 | res.status(200).json({ message: "Skill deleted successfully" }); 68 | } catch (error) { 69 | console.error(error); 70 | res.status(500).json({ error: "Error deleting skill" }); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /controller/ProjectController.js: -------------------------------------------------------------------------------- 1 | const Project = require("../models/Project"); 2 | 3 | // Fetch all projects 4 | const getAllProjects = async (req, res) => { 5 | try { 6 | const projects = await Project.find(); 7 | res.status(200).json(projects); 8 | } catch (error) { 9 | res.status(500).json({ message: "Error fetching projects", error }); 10 | } 11 | }; 12 | 13 | // Add a new project 14 | const addProject = async (req, res) => { 15 | const { title, description, tech, link, liveDemo, category, subCategory } = 16 | req.body; 17 | 18 | // Validate required fields 19 | if (!title || !description || !tech || !link || !category) { 20 | return res 21 | .status(400) 22 | .json({ message: "All required fields must be provided." }); 23 | } 24 | 25 | try { 26 | const newProject = new Project({ 27 | title, 28 | description, 29 | tech, 30 | link, 31 | liveDemo, 32 | category, 33 | subCategory, 34 | }); 35 | 36 | const savedProject = await newProject.save(); 37 | res.status(201).json(savedProject); 38 | } catch (error) { 39 | res.status(500).json({ message: "Error adding project", error }); 40 | } 41 | }; 42 | 43 | // Update an existing project 44 | const updateProject = async (req, res) => { 45 | const { id } = req.params; 46 | const { title, description, tech, link, liveDemo, category, subCategory } = 47 | req.body; 48 | 49 | try { 50 | const updatedProject = await Project.findByIdAndUpdate( 51 | id, 52 | { title, description, tech, link, liveDemo, category, subCategory }, 53 | { new: true } // Return the updated document 54 | ); 55 | 56 | if (!updatedProject) { 57 | return res.status(404).json({ message: "Project not found" }); 58 | } 59 | 60 | res.status(200).json(updatedProject); 61 | } catch (error) { 62 | res.status(500).json({ message: "Error updating project", error }); 63 | } 64 | }; 65 | 66 | // Delete a project 67 | const deleteProject = async (req, res) => { 68 | const { id } = req.params; 69 | 70 | try { 71 | const deletedProject = await Project.findByIdAndDelete(id); 72 | 73 | if (!deletedProject) { 74 | return res.status(404).json({ message: "Project not found" }); 75 | } 76 | 77 | res.status(200).json({ message: "Project deleted successfully" }); 78 | } catch (error) { 79 | res.status(500).json({ message: "Error deleting project", error }); 80 | } 81 | }; 82 | 83 | module.exports = { 84 | getAllProjects, 85 | addProject, 86 | updateProject, 87 | deleteProject, 88 | }; 89 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Udemy Tracker Backend 2 | 3 | This is the backend server for the **Udemy Tracker Application**, built using Node.js, Express.js, and MongoDB. It manages course data, tracks progress, and syncs updates between local IndexedDB and MongoDB Cloud. 4 | 5 | --- 6 | 7 | ## Features 8 | 9 | - RESTful API for managing Udemy courses. 10 | - Integration with Udemy API for course data retrieval. 11 | - CRUD operations for courses and notes. 12 | - Local storage syncing with MongoDB Cloud. 13 | - IndexedDB for offline-first support. 14 | - Scalable backend using Node.js and Express.js. 15 | 16 | --- 17 | 18 | ## Table of Contents 19 | 20 | - [Getting Started](#getting-started) 21 | - [Prerequisites](#prerequisites) 22 | - [Installation](#installation) 23 | - [Environment Variables](#environment-variables) 24 | - [Usage](#usage) 25 | - [API Endpoints](#api-endpoints) 26 | - [Folder Structure](#folder-structure) 27 | - [Contributing](#contributing) 28 | - [License](#license) 29 | 30 | --- 31 | 32 | ## Getting Started 33 | 34 | Follow these instructions to set up and run the project locally. 35 | 36 | --- 37 | 38 | ## Prerequisites 39 | 40 | Make sure you have the following installed: 41 | 42 | - [Node.js](https://nodejs.org/) (v16 or higher) 43 | - [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) account for cloud database setup 44 | - Udemy Client API credentials 45 | 46 | --- 47 | 48 | ## Installation 49 | 50 | 1. Clone the repository: 51 | ```bash 52 | git clone https://github.com/your-username/udemy-tracker-backend.git 53 | cd udemy-tracker-backend 54 | ``` 55 | 56 | 2. Install dependencies: 57 | ```bash 58 | npm install 59 | ``` 60 | 61 | 3. Create a `.env` file and configure it as per the [Environment Variables](#environment-variables) section. 62 | 63 | 4. Start the development server: 64 | ```bash 65 | npm run dev 66 | ``` 67 | 68 | 5. The server will run at `http://localhost:5000` by default. 69 | 70 | --- 71 | 72 | ## Environment Variables 73 | 74 | Create a `.env` file in the root directory and configure the following: 75 | 76 | ```env 77 | PORT=5000 78 | MONGO_URI=your-mongodb-atlas-uri 79 | ``` 80 | 81 | --- 82 | 83 | ## Usage 84 | 85 | - Retrieve Udemy courses and sync them with MongoDB. 86 | - Manage course notes, progress, and categories. 87 | - Sync updates between IndexedDB and MongoDB. 88 | 89 | --- 90 | 91 | ## Contributing 92 | 93 | Contributions are welcome! Feel free to open issues or submit pull requests to improve the project. 94 | 95 | --- 96 | 97 | ## License 98 | 99 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /controller/courseController.js: -------------------------------------------------------------------------------- 1 | const Course = require("../models/Course"); 2 | 3 | // Create a new course 4 | const createCourse = async (req, res) => { 5 | try { 6 | const course = new Course(req.body); 7 | await course.save(); 8 | res.status(201).json(course); 9 | } catch (error) { 10 | res.status(400).json({ error: error.message }); 11 | } 12 | }; 13 | 14 | // Get all courses 15 | const getCourses = async (req, res) => { 16 | try { 17 | const courses = await Course.find(); 18 | res.json(courses); 19 | } catch (error) { 20 | res.status(500).json({ error: error.message }); 21 | } 22 | }; 23 | 24 | // Get a specific course by ID 25 | const getCourseById = async (req, res) => { 26 | try { 27 | const course = await Course.findById(req.params.id); 28 | if (!course) return res.status(404).json({ message: "Course not found" }); 29 | res.json(course); 30 | } catch (error) { 31 | res.status(500).json({ error: error.message }); 32 | } 33 | }; 34 | 35 | // Update a course 36 | const updateCourse = async (req, res) => { 37 | try { 38 | const course = await Course.findByIdAndUpdate(req.params.id, req.body, { 39 | new: true, 40 | }); 41 | if (!course) return res.status(404).json({ message: "Course not found" }); 42 | res.json(course); 43 | } catch (error) { 44 | res.status(500).json({ error: error.message }); 45 | } 46 | }; 47 | 48 | // Delete a course 49 | const deleteCourse = async (req, res) => { 50 | try { 51 | const course = await Course.findByIdAndDelete(req.params.id); 52 | if (!course) return res.status(404).json({ message: "Course not found" }); 53 | res.json({ message: "Course deleted successfully" }); 54 | } catch (error) { 55 | res.status(500).json({ error: error.message }); 56 | } 57 | }; 58 | 59 | // Sync courses from localStorage 60 | const syncCourses = async (req, res) => { 61 | try { 62 | const coursesData = req.body.courses; // Array of courses from the client 63 | 64 | // Loop through each course data to either create or update in the database 65 | for (const courseData of coursesData) { 66 | const { no, ...rest } = courseData; 67 | 68 | // Check if the course already exists based on the unique 'no' field 69 | const existingCourse = await Course.findOne({ no }); 70 | 71 | if (existingCourse) { 72 | // If course exists, update it with the new data 73 | await Course.updateOne({ no }, { $set: rest }); 74 | } else { 75 | // If course doesn't exist, create a new course 76 | await new Course(courseData).save(); 77 | } 78 | } 79 | 80 | res.status(200).json({ message: "Courses synced successfully" }); 81 | } catch (error) { 82 | console.error("Error syncing courses:", error); 83 | res.status(500).json({ error: error.message }); 84 | } 85 | }; 86 | 87 | module.exports = { 88 | createCourse, 89 | getCourses, 90 | getCourseById, 91 | updateCourse, 92 | deleteCourse, 93 | syncCourses, // Export the syncCourses function 94 | }; 95 | -------------------------------------------------------------------------------- /controller/noteController.js: -------------------------------------------------------------------------------- 1 | // controllers/noteController.js 2 | const Course = require("../models/Course"); 3 | 4 | // Add a note to a course 5 | const addNote = async (req, res) => { 6 | try { 7 | const { courseId } = req.params; 8 | const { 9 | question, 10 | answer, 11 | mainTargetCategory, 12 | mainTargetGoal, 13 | subTargetGoal, 14 | } = req.body; 15 | 16 | // Validate required fields 17 | if (!question || !answer || !mainTargetCategory || !mainTargetGoal) { 18 | return res.status(400).json({ message: "Missing required fields" }); 19 | } 20 | 21 | const course = await Course.findById(courseId); 22 | if (!course) { 23 | return res.status(404).json({ message: "Course not found" }); 24 | } 25 | 26 | // Create a new note object 27 | const newNote = { 28 | question, 29 | answer, 30 | mainTargetCategory, 31 | mainTargetGoal, 32 | subTargetGoal, 33 | }; 34 | 35 | // Add the new note to the notes array 36 | course.notes.push(newNote); 37 | await course.save(); 38 | 39 | res.status(201).json({ 40 | message: "Note added successfully", 41 | note: course.notes.slice(-1)[0], 42 | }); 43 | } catch (error) { 44 | res 45 | .status(500) 46 | .json({ message: "Error adding note", error: error.message }); 47 | } 48 | }; 49 | 50 | // Update a note in a course 51 | const updateNote = async (req, res) => { 52 | try { 53 | const { courseId, noteId } = req.params; 54 | const { 55 | question, 56 | answer, 57 | mainTargetCategory, 58 | mainTargetGoal, 59 | subTargetGoal, 60 | } = req.body; 61 | 62 | const course = await Course.findById(courseId); 63 | if (!course) { 64 | return res.status(404).json({ message: "Course not found" }); 65 | } 66 | 67 | const note = course.notes.id(noteId); 68 | if (!note) { 69 | return res.status(404).json({ message: "Note not found" }); 70 | } 71 | 72 | // Update note fields 73 | note.question = question || note.question; 74 | note.answer = answer || note.answer; 75 | note.mainTargetCategory = mainTargetCategory || note.mainTargetCategory; 76 | note.mainTargetGoal = mainTargetGoal || note.mainTargetGoal; 77 | note.subTargetGoal = subTargetGoal || note.subTargetGoal; 78 | 79 | await course.save(); 80 | 81 | res.json({ message: "Note updated successfully", note }); 82 | } catch (error) { 83 | res 84 | .status(500) 85 | .json({ message: "Error updating note", error: error.message }); 86 | } 87 | }; 88 | 89 | // Delete a note from a course 90 | const deleteNote = async (req, res) => { 91 | try { 92 | const { courseId, noteId } = req.params; 93 | 94 | const course = await Course.findById(courseId); 95 | if (!course) { 96 | return res.status(404).json({ message: "Course not found" }); 97 | } 98 | 99 | const note = course.notes.id(noteId); 100 | if (!note) { 101 | return res.status(404).json({ message: "Note not found" }); 102 | } 103 | 104 | // Remove the note using Mongoose's remove method 105 | note.remove(); 106 | await course.save(); 107 | 108 | res.status(200).json({ message: "Note deleted successfully" }); 109 | } catch (error) { 110 | res 111 | .status(500) 112 | .json({ message: "Error deleting note", error: error.message }); 113 | } 114 | }; 115 | 116 | module.exports = { 117 | addNote, 118 | updateNote, 119 | deleteNote, 120 | }; 121 | -------------------------------------------------------------------------------- /controller/mainCategoryController.js: -------------------------------------------------------------------------------- 1 | const MainCategory = require("../models/MainCategory"); 2 | 3 | // Get all main categories 4 | exports.getMainCategories = async (req, res) => { 5 | try { 6 | const mainCategories = await MainCategory.find(); 7 | res.status(200).json(mainCategories); 8 | } catch (error) { 9 | res.status(500).json({ message: "Error fetching main categories", error }); 10 | } 11 | }; 12 | 13 | // Create a new main category 14 | exports.createMainCategory = async (req, res) => { 15 | try { 16 | const { name } = req.body; 17 | 18 | const newMainCategory = new MainCategory({ 19 | name, 20 | isChecked: false, 21 | mainGoals: [], 22 | }); 23 | 24 | await newMainCategory.save(); 25 | res.status(201).json(newMainCategory); 26 | } catch (error) { 27 | res.status(500).json({ message: "Error creating main category", error }); 28 | } 29 | }; 30 | 31 | // Update an existing main category 32 | exports.updateMainCategory = async (req, res) => { 33 | const { categoryId } = req.params; 34 | const { name, isChecked } = req.body; 35 | 36 | try { 37 | const mainCategory = await MainCategory.findById(categoryId); 38 | 39 | if (!mainCategory) { 40 | return res.status(404).json({ message: "Main category not found" }); 41 | } 42 | 43 | mainCategory.name = name || mainCategory.name; 44 | mainCategory.isChecked = 45 | isChecked !== undefined ? isChecked : mainCategory.isChecked; 46 | 47 | await mainCategory.save(); 48 | res.status(200).json(mainCategory); 49 | } catch (error) { 50 | res.status(500).json({ message: "Error updating main category", error }); 51 | } 52 | }; 53 | 54 | // Delete a main category 55 | exports.deleteMainCategory = async (req, res) => { 56 | const { categoryId } = req.params; 57 | 58 | try { 59 | await MainCategory.findByIdAndDelete(categoryId); 60 | res.status(200).json({ message: "Main category deleted successfully" }); 61 | } catch (error) { 62 | res.status(500).json({ message: "Error deleting main category", error }); 63 | } 64 | }; 65 | 66 | // Create a new main target goal 67 | exports.createMainTargetGoal = async (req, res) => { 68 | const { categoryId } = req.params; 69 | const { name } = req.body; 70 | 71 | try { 72 | const mainCategory = await MainCategory.findById(categoryId); 73 | 74 | if (!mainCategory) { 75 | return res.status(404).json({ message: "Main category not found" }); 76 | } 77 | 78 | const newMainGoal = { name, isChecked: false, subGoals: [] }; 79 | mainCategory.mainGoals.push(newMainGoal); 80 | 81 | await mainCategory.save(); 82 | res.status(201).json(mainCategory); 83 | } catch (error) { 84 | res.status(500).json({ message: "Error creating main target goal", error }); 85 | } 86 | }; 87 | 88 | // Update a main target goal 89 | exports.updateMainTargetGoal = async (req, res) => { 90 | const { categoryId, goalId } = req.params; 91 | const { name, isChecked } = req.body; 92 | 93 | try { 94 | const mainCategory = await MainCategory.findById(categoryId); 95 | 96 | if (!mainCategory) { 97 | return res.status(404).json({ message: "Main category not found" }); 98 | } 99 | 100 | const mainGoal = mainCategory.mainGoals.id(goalId); 101 | 102 | if (!mainGoal) { 103 | return res.status(404).json({ message: "Main target goal not found" }); 104 | } 105 | 106 | mainGoal.name = name || mainGoal.name; 107 | mainGoal.isChecked = 108 | isChecked !== undefined ? isChecked : mainGoal.isChecked; 109 | 110 | await mainCategory.save(); 111 | res.status(200).json(mainCategory); 112 | } catch (error) { 113 | res.status(500).json({ message: "Error updating main target goal", error }); 114 | } 115 | }; 116 | 117 | // Delete a main target goal 118 | exports.deleteMainTargetGoal = async (req, res) => { 119 | const { categoryId, goalId } = req.params; 120 | 121 | try { 122 | // Find the main category by ID 123 | const mainCategory = await MainCategory.findById(categoryId); 124 | 125 | if (!mainCategory) { 126 | return res.status(404).json({ message: "Main category not found" }); 127 | } 128 | 129 | // Find the index of the main goal 130 | const goalIndex = mainCategory.mainGoals.findIndex( 131 | (goal) => goal._id.toString() === goalId 132 | ); 133 | 134 | if (goalIndex === -1) { 135 | return res.status(404).json({ message: "Main target goal not found" }); 136 | } 137 | 138 | // Remove the main target goal from the array 139 | mainCategory.mainGoals.splice(goalIndex, 1); 140 | 141 | // Save the updated category 142 | await mainCategory.save(); 143 | 144 | res.status(200).json({ message: "Main target goal deleted successfully" }); 145 | } catch (error) { 146 | res 147 | .status(500) 148 | .json({ 149 | message: "Error deleting main target goal", 150 | error: error.message, 151 | }); 152 | } 153 | }; 154 | 155 | // Create a new sub-target goal 156 | exports.createSubTargetGoal = async (req, res) => { 157 | const { categoryId, goalId } = req.params; 158 | const { name } = req.body; 159 | 160 | try { 161 | const mainCategory = await MainCategory.findById(categoryId); 162 | 163 | if (!mainCategory) { 164 | return res.status(404).json({ message: "Main category not found" }); 165 | } 166 | 167 | const mainGoal = mainCategory.mainGoals.id(goalId); 168 | 169 | if (!mainGoal) { 170 | return res.status(404).json({ message: "Main target goal not found" }); 171 | } 172 | 173 | const newSubGoal = { name, isChecked: false }; 174 | mainGoal.subGoals.push(newSubGoal); 175 | 176 | await mainCategory.save(); 177 | res.status(201).json(mainCategory); 178 | } catch (error) { 179 | res.status(500).json({ message: "Error creating sub-target goal", error }); 180 | } 181 | }; 182 | 183 | // Update a sub-target goal 184 | exports.updateSubTargetGoal = async (req, res) => { 185 | const { categoryId, goalId, subGoalId } = req.params; 186 | const { name, isChecked } = req.body; 187 | 188 | try { 189 | const mainCategory = await MainCategory.findById(categoryId); 190 | 191 | if (!mainCategory) { 192 | return res.status(404).json({ message: "Main category not found" }); 193 | } 194 | 195 | const mainGoal = mainCategory.mainGoals.id(goalId); 196 | 197 | if (!mainGoal) { 198 | return res.status(404).json({ message: "Main target goal not found" }); 199 | } 200 | 201 | const subGoal = mainGoal.subGoals.id(subGoalId); 202 | 203 | if (!subGoal) { 204 | return res.status(404).json({ message: "Sub-target goal not found" }); 205 | } 206 | 207 | subGoal.name = name || subGoal.name; 208 | subGoal.isChecked = isChecked !== undefined ? isChecked : subGoal.isChecked; 209 | 210 | await mainCategory.save(); 211 | res.status(200).json(mainCategory); 212 | } catch (error) { 213 | res.status(500).json({ message: "Error updating sub-target goal", error }); 214 | } 215 | }; 216 | 217 | // Delete a sub-target goal 218 | exports.deleteSubTargetGoal = async (req, res) => { 219 | const { categoryId, goalId, subGoalId } = req.params; 220 | 221 | try { 222 | // Find the main category by ID 223 | const mainCategory = await MainCategory.findById(categoryId); 224 | 225 | if (!mainCategory) { 226 | return res.status(404).json({ message: "Main category not found" }); 227 | } 228 | 229 | // Find the main goal by ID 230 | const mainGoal = mainCategory.mainGoals.id(goalId); 231 | 232 | if (!mainGoal) { 233 | return res.status(404).json({ message: "Main target goal not found" }); 234 | } 235 | 236 | // Find the index of the sub-target goal 237 | const subGoalIndex = mainGoal.subGoals.findIndex( 238 | (subGoal) => subGoal._id.toString() === subGoalId 239 | ); 240 | 241 | if (subGoalIndex === -1) { 242 | return res.status(404).json({ message: "Sub-target goal not found" }); 243 | } 244 | 245 | // Remove the sub-target goal from the array 246 | mainGoal.subGoals.splice(subGoalIndex, 1); 247 | 248 | // Save the updated main category 249 | await mainCategory.save(); 250 | 251 | res.status(200).json({ message: "Sub-target goal deleted successfully" }); 252 | } catch (error) { 253 | res 254 | .status(500) 255 | .json({ 256 | message: "Error deleting sub-target goal", 257 | error: error.message, 258 | }); 259 | } 260 | }; 261 | -------------------------------------------------------------------------------- /routes/noteRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const Course = require("../models/Course"); // Import Course model 3 | 4 | const router = express.Router({ mergeParams: true }); // Merge params for courseId access 5 | 6 | // Fetch all notes across all courses 7 | router.get("/all", async (req, res) => { 8 | try { 9 | const courses = await Course.find({}, "notes"); // Fetch only notes from each course 10 | const allNotes = courses.flatMap((course) => course.notes); // Flatten notes into a single array 11 | res.json({ message: "All notes retrieved successfully", notes: allNotes }); 12 | } catch (error) { 13 | res 14 | .status(500) 15 | .json({ message: "Error fetching all notes", error: error.message }); 16 | } 17 | }); 18 | 19 | // Add a new note to a specific course 20 | router.post("/", async (req, res) => { 21 | try { 22 | const { courseId } = req.params; 23 | const { 24 | question, 25 | answer, 26 | mainTargetCategory, 27 | mainTargetGoal, 28 | subTargetGoal, 29 | } = req.body; 30 | 31 | const course = await Course.findById(courseId); 32 | if (!course) { 33 | return res.status(404).json({ message: "Course not found" }); 34 | } 35 | 36 | // Create and add a new note 37 | const newNote = { 38 | question, 39 | answer, 40 | mainTargetCategory, 41 | mainTargetGoal, 42 | subTargetGoal, 43 | }; 44 | course.notes.push(newNote); 45 | await course.save(); 46 | 47 | res.status(201).json({ 48 | message: "Note added successfully", 49 | note: course.notes.slice(-1)[0], 50 | }); 51 | } catch (error) { 52 | res 53 | .status(500) 54 | .json({ message: "Error adding note", error: error.message }); 55 | } 56 | }); 57 | 58 | // Update an existing note in a specific course 59 | router.put("/:noteId", async (req, res) => { 60 | try { 61 | const { courseId, noteId } = req.params; 62 | const { 63 | question, 64 | answer, 65 | mainTargetCategory, 66 | mainTargetGoal, 67 | subTargetGoal, 68 | } = req.body; 69 | 70 | const course = await Course.findById(courseId); 71 | if (!course) { 72 | return res.status(404).json({ message: "Course not found" }); 73 | } 74 | 75 | const note = course.notes.id(noteId); 76 | if (!note) { 77 | return res.status(404).json({ message: "Note not found" }); 78 | } 79 | 80 | // Update note fields 81 | note.question = question || note.question; 82 | note.answer = answer || note.answer; 83 | note.mainTargetCategory = mainTargetCategory || note.mainTargetCategory; 84 | note.mainTargetGoal = mainTargetGoal || note.mainTargetGoal; 85 | note.subTargetGoal = subTargetGoal || note.subTargetGoal; 86 | 87 | await course.save(); 88 | res.json({ message: "Note updated successfully", note }); 89 | } catch (error) { 90 | res 91 | .status(500) 92 | .json({ message: "Error updating note", error: error.message }); 93 | } 94 | }); 95 | 96 | // Delete a specific note from a course 97 | router.delete("/:noteId", async (req, res) => { 98 | try { 99 | const { courseId, noteId } = req.params; 100 | 101 | const course = await Course.findById(courseId); 102 | if (!course) { 103 | return res.status(404).json({ message: "Course not found" }); 104 | } 105 | 106 | // Filter out the note to delete it 107 | course.notes = course.notes.filter( 108 | (note) => note._id.toString() !== noteId 109 | ); 110 | await course.save(); 111 | 112 | res.status(200).json({ message: "Note deleted successfully" }); 113 | } catch (error) { 114 | res 115 | .status(500) 116 | .json({ message: "Error deleting note", error: error.message }); 117 | } 118 | }); 119 | 120 | // Delete a note by its ID from a specific course 121 | router.delete("/byNoteId/:courseId/:noteId", async (req, res) => { 122 | try { 123 | const { courseId, noteId } = req.params; 124 | 125 | // Find the course that contains the note 126 | const course = await Course.findById(courseId); 127 | if (!course) { 128 | return res.status(404).json({ message: "Course not found" }); 129 | } 130 | 131 | // Use $pull to remove the note from the course's notes array 132 | const result = await Course.updateOne( 133 | { _id: courseId }, 134 | { $pull: { notes: { _id: noteId } } } 135 | ); 136 | 137 | // Check if the note was found and removed 138 | if (result.modifiedCount === 0) { 139 | return res.status(404).json({ message: "Note not found" }); 140 | } 141 | 142 | res 143 | .status(200) 144 | .json({ message: "Note deleted successfully from the course" }); 145 | } catch (error) { 146 | res.status(500).json({ 147 | message: "Error deleting note from course", 148 | error: error.message, 149 | }); 150 | } 151 | }); 152 | 153 | // Delete a note by its ID (without requiring courseId) 154 | router.delete("/deleteByNoteId/:noteId", async (req, res) => { 155 | try { 156 | const { noteId } = req.params; 157 | 158 | // Find the course that contains the note and remove it 159 | const result = await Course.updateOne( 160 | { "notes._id": noteId }, 161 | { $pull: { notes: { _id: noteId } } } 162 | ); 163 | 164 | // Check if the note was found and removed 165 | if (result.modifiedCount === 0) { 166 | return res.status(404).json({ message: "Note not found" }); 167 | } 168 | 169 | res.status(200).json({ message: "Note deleted successfully" }); 170 | } catch (error) { 171 | res 172 | .status(500) 173 | .json({ message: "Error deleting note", error: error.message }); 174 | } 175 | }); 176 | 177 | // Get all notes for a specific course or a specific note by ID 178 | router.get("/:noteId?", async (req, res) => { 179 | try { 180 | const { courseId, noteId } = req.params; 181 | 182 | const course = await Course.findById(courseId).select("notes"); 183 | if (!course) { 184 | return res.status(404).json({ message: "Course not found" }); 185 | } 186 | 187 | // If noteId is provided, retrieve that specific note 188 | if (noteId) { 189 | const note = course.notes.id(noteId); 190 | if (!note) { 191 | return res.status(404).json({ message: "Note not found" }); 192 | } 193 | return res.json({ message: "Note retrieved successfully", note }); 194 | } 195 | 196 | // If no noteId, return all notes for the course 197 | res.json({ message: "Notes retrieved successfully", notes: course.notes }); 198 | } catch (error) { 199 | res 200 | .status(500) 201 | .json({ message: "Error fetching notes", error: error.message }); 202 | } 203 | }); 204 | 205 | // Update a note by its ID (using a more focused route) 206 | router.put("/update/:noteId", async (req, res) => { 207 | try { 208 | const { noteId } = req.params; 209 | const { 210 | question, 211 | answer, 212 | mainTargetCategory, 213 | mainTargetGoal, 214 | subTargetGoal, 215 | } = req.body; 216 | 217 | // Find the course that contains the note using the noteId 218 | const course = await Course.findOne({ "notes._id": noteId }); 219 | if (!course) { 220 | return res.status(404).json({ message: "Course or note not found" }); 221 | } 222 | 223 | // Find the specific note by its ID inside the course's notes array 224 | const note = course.notes.id(noteId); 225 | if (!note) { 226 | return res.status(404).json({ message: "Note not found" }); 227 | } 228 | 229 | // Update the note fields 230 | note.question = question || note.question; 231 | note.answer = answer || note.answer; 232 | note.mainTargetCategory = mainTargetCategory || note.mainTargetCategory; 233 | note.mainTargetGoal = mainTargetGoal || note.mainTargetGoal; 234 | note.subTargetGoal = subTargetGoal || note.subTargetGoal; 235 | 236 | // Save the course with the updated note 237 | await course.save(); 238 | 239 | // Respond with the updated note data 240 | res.json({ 241 | message: "Note updated successfully", 242 | note: { 243 | _id: note._id, 244 | question: note.question, 245 | answer: note.answer, 246 | mainTargetCategory: note.mainTargetCategory, 247 | mainTargetGoal: note.mainTargetGoal, 248 | subTargetGoal: note.subTargetGoal, 249 | }, 250 | }); 251 | } catch (error) { 252 | res 253 | .status(500) 254 | .json({ message: "Error updating note by ID", error: error.message }); 255 | } 256 | }); 257 | 258 | // Find a note by its ID (without requiring courseId) 259 | router.get("/note/:noteId", async (req, res) => { 260 | try { 261 | const { noteId } = req.params; 262 | 263 | // Find the course that contains the note using the noteId 264 | const course = await Course.findOne( 265 | { "notes._id": noteId }, 266 | { "notes.$": 1 } 267 | ); // Select only the specific note with $ projection 268 | if (!course || course.notes.length === 0) { 269 | return res.status(404).json({ message: "Note not found" }); 270 | } 271 | 272 | // Extract the note from the course's notes array 273 | const note = course.notes[0]; 274 | res.json({ 275 | message: "Note retrieved successfully", 276 | note: { 277 | _id: note._id, 278 | question: note.question, 279 | answer: note.answer, 280 | mainTargetCategory: note.mainTargetCategory, 281 | mainTargetGoal: note.mainTargetGoal, 282 | subTargetGoal: note.subTargetGoal, 283 | }, 284 | }); 285 | } catch (error) { 286 | res 287 | .status(500) 288 | .json({ message: "Error fetching note by ID", error: error.message }); 289 | } 290 | }); 291 | 292 | // Delete all notes from a specific course 293 | router.delete("/deleteAllNotes/:courseId", async (req, res) => { 294 | try { 295 | const { courseId } = req.params; 296 | 297 | // Find the course by ID and set the notes array to an empty array 298 | const course = await Course.findById(courseId); 299 | if (!course) { 300 | return res.status(404).json({ message: "Course not found" }); 301 | } 302 | 303 | // Clear the notes array 304 | course.notes = []; 305 | await course.save(); 306 | 307 | res 308 | .status(200) 309 | .json({ message: "All notes deleted successfully from the course" }); 310 | } catch (error) { 311 | res.status(500).json({ 312 | message: "Error deleting all notes from course", 313 | error: error.message, 314 | }); 315 | } 316 | }); 317 | 318 | module.exports = router; 319 | --------------------------------------------------------------------------------