├── .gitignore ├── src ├── .vscode │ └── settings.json ├── config │ ├── jwt.config.js │ ├── constants.config.js │ └── db.config.js ├── routes │ ├── authRouter.js │ ├── index.js │ ├── activityRouter.js │ ├── userRouter.js │ └── courseRouter.js ├── auth │ └── auth.js ├── models │ ├── userCoursesModel.js │ ├── answerModel.js │ ├── questionModel.js │ ├── courseModel.js │ ├── activityModel.js │ ├── index.js │ └── userModel.js ├── package.json ├── index.js └── controllers │ ├── activityController.js │ ├── userController.js │ └── courseController.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/package-lock.json 3 | .vscode/settings.json 4 | src/config/db.config.js 5 | -------------------------------------------------------------------------------- /src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": [ 3 | "validate try catch for all database qureies" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/config/jwt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | SECRET_KEY: 'Mahboub & Adel', 3 | SECRET_KEY_RESET_PASSWORD: "enta falaaa7", 4 | JWT_EXPIRES_IN: '30d' 5 | } -------------------------------------------------------------------------------- /src/config/constants.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | userType: { 3 | LEARNER: "learner", 4 | INSTRUCTOR: "instructor", 5 | ADMIN: "admin" 6 | }, 7 | env: { 8 | CLIENT_URL: "http://localhost:8080" 9 | } 10 | } -------------------------------------------------------------------------------- /src/routes/authRouter.js: -------------------------------------------------------------------------------- 1 | const userController = require("../controllers/userController.js"); 2 | 3 | const router = require("express").Router(); 4 | 5 | router.post("/login", userController.login); 6 | router.post("/signup", userController.signup); 7 | router.post("/forgot", userController.forgotPassword); 8 | router.post("/reset", userController.resetPassword); 9 | 10 | module.exports = router; 11 | 12 | 13 | /** 14 | * localhost:4000/api/signup 15 | */ -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const authRouter = require("./authRouter"); 4 | const userRouter = require("./userRouter"); 5 | const courseRouter = require("./courseRouter"); 6 | const activityRouter = require("./activityRouter"); 7 | 8 | const router = express.Router(); 9 | 10 | router.use("/", authRouter); 11 | router.use("/users", userRouter); 12 | router.use("/courses", courseRouter); 13 | router.use("/activities", activityRouter); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /src/routes/activityRouter.js: -------------------------------------------------------------------------------- 1 | const auth = require("../auth/auth"); 2 | const router = require("express").Router(); 3 | 4 | const activityController = require("../controllers/activityController.js"); 5 | 6 | router 7 | .route("/:courseId") 8 | .get(auth.verifyToken, activityController.getActivities) 9 | .post(auth.verifyToken, activityController.addActivity); 10 | 11 | router 12 | .route("/:id") 13 | .delete(auth.verifyToken, activityController.deleteActivity); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /src/auth/auth.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require("http-status"); 2 | 3 | function verifyToken(req, res, next) { 4 | const bearerHeader = req.headers["authorization"]; 5 | 6 | if (typeof bearerHeader !== "undefined") { 7 | const bearer = bearerHeader.split(" "); 8 | const bearerToken = bearer[1]; 9 | 10 | req.token = bearerToken; 11 | next(); 12 | } else { 13 | return res.status(httpStatus.FORBIDDEN).send({ 14 | message: "This operation is forbibden for you hehe hahhaha !", 15 | }); 16 | } 17 | } 18 | 19 | module.exports = { 20 | verifyToken, 21 | }; 22 | -------------------------------------------------------------------------------- /src/models/userCoursesModel.js: -------------------------------------------------------------------------------- 1 | const user = require("./userModel"); 2 | const course = require("./courseModel"); 3 | 4 | module.exports = (sequelize, DataTypes) => { 5 | return sequelize.define("user_courses", { 6 | userId: { 7 | type: DataTypes.INTEGER, 8 | references: { 9 | model: user, 10 | key: "_id", 11 | }, 12 | }, 13 | courseId: { 14 | type: DataTypes.INTEGER, 15 | references: { 16 | model: course, 17 | key: "_id", 18 | }, 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/config/db.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | HOST: "localhost", 3 | USER: "root", 4 | PASSWORD: "root", 5 | DB: "lms", 6 | dialect: "mysql", 7 | 8 | pool: { 9 | max: 5, 10 | min: 0, 11 | acquire: 30000, 12 | idle: 10000, 13 | }, 14 | }; 15 | 16 | // module.exports = { 17 | // HOST: "sql11.freemysqlhosting.net", 18 | // USER: "sql11461123", 19 | // PASSWORD: "HuuCN9953R", 20 | // DB: "sql11461123", 21 | // dialect: "mysql", 22 | 23 | // pool: { 24 | // max: 5, 25 | // min: 0, 26 | // acquire: 30000, 27 | // idle: 10000, 28 | // }, 29 | // }; 30 | -------------------------------------------------------------------------------- /src/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | const userController = require("../controllers/userController.js"); 2 | const auth = require("../auth/auth"); 3 | 4 | const router = require("express").Router(); 5 | 6 | router.route("/").get(auth.verifyToken, userController.getUsers); 7 | 8 | router.route("/enrollMe").post(auth.verifyToken, userController.enrollMe); 9 | 10 | router 11 | .route("/:id") 12 | .get(auth.verifyToken, userController.getUser) 13 | .put(auth.verifyToken, userController.updateUser) 14 | .delete(auth.verifyToken, userController.deleteUser); 15 | 16 | router 17 | .route("/upgrade/:username") 18 | .put(auth.verifyToken, userController.upgradeLearner); 19 | 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /src/models/answerModel.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("answer", { 3 | _id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | allowNull: false, 7 | autoIncrement: true, 8 | }, 9 | username: { 10 | type: DataTypes.STRING, 11 | allowNull: false, 12 | }, 13 | questionId: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false, 16 | }, 17 | body: { 18 | type: DataTypes.STRING, 19 | allowNull: false, 20 | }, 21 | date: { 22 | type: DataTypes.DATEONLY, 23 | allowNull: false, 24 | }, 25 | }); 26 | }; -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lms", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx nodemon index", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "ahmed mahboub", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.0.1", 14 | "body-parser": "^1.19.1", 15 | "cors": "^2.8.5", 16 | "express": "^4.17.2", 17 | "http-status": "^1.5.0", 18 | "jsonwebtoken": "^8.5.1", 19 | "mysql2": "^2.3.3", 20 | "nodemailer": "^6.7.2", 21 | "sequelize": "^6.12.0" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/models/questionModel.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("question", { 3 | _id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | allowNull: false, 7 | autoIncrement: true, 8 | }, 9 | username: { 10 | type: DataTypes.STRING, 11 | allowNull: false, 12 | }, 13 | courseId: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false, 16 | }, 17 | body: { 18 | type: DataTypes.STRING, 19 | allowNull: false, 20 | }, 21 | date: { 22 | type: DataTypes.DATEONLY, 23 | allowNull: false, 24 | }, 25 | }); 26 | }; -------------------------------------------------------------------------------- /src/models/courseModel.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("course", { 3 | _id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | allowNull: false, 7 | autoIncrement: true, 8 | }, 9 | name: { 10 | type: DataTypes.STRING, 11 | unique: true, 12 | allowNull: false, 13 | }, 14 | syllabus: { 15 | type: DataTypes.STRING, 16 | allowNull: false, 17 | get() { 18 | const syllabus = this.getDataValue('syllabus'); 19 | return syllabus.split("$%&"); 20 | }, 21 | set(syllabus) { 22 | this.setDataValue('syllabus', syllabus.join("$%&")); 23 | } 24 | }, 25 | }); 26 | }; -------------------------------------------------------------------------------- /src/models/activityModel.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | return sequelize.define("activity", { 3 | _id: { 4 | type: DataTypes.INTEGER, 5 | primaryKey: true, 6 | allowNull: false, 7 | autoIncrement: true, 8 | }, 9 | name: { 10 | type: DataTypes.STRING, 11 | allowNull: false, 12 | }, 13 | type: { 14 | type: DataTypes.STRING, // {"youtube", "pdf"} 15 | allowNull: false, 16 | }, 17 | link: { 18 | type: DataTypes.STRING, 19 | allowNull: false, 20 | // allowNull: true // uncomment me in case of adding quizzes 👌 21 | }, 22 | courseId: { 23 | type: DataTypes.INTEGER, 24 | allowNull: false, 25 | }, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const cors = require("cors"); 2 | const express = require("express"); 3 | const bodyParser = require("body-parser"); 4 | 5 | const app = express(); 6 | const PORT = process.env.port || 4000; 7 | 8 | let corsOptions = { 9 | origin: "*", 10 | }; 11 | 12 | // ^ Middleware 13 | app.use(cors(corsOptions)); 14 | app.use(bodyParser.json()); 15 | app.use( 16 | express.urlencoded({ 17 | extended: true, 18 | }) 19 | ); 20 | 21 | // ^ Routers 22 | const router = require("./routes"); 23 | app.use("/api", router); 24 | 25 | // ^ Routes 26 | app.get("/", (req, res) => { 27 | res.json({ 28 | works: "fine", 29 | }); 30 | }); 31 | 32 | // ^ Errors 33 | app.use(function (err, req, res, next) { 34 | return res.status(422).send({ 35 | error: err.message, 36 | }); 37 | }); 38 | 39 | // ^ Listen to requests 40 | app.listen(PORT, function () { 41 | console.log(`Listening on port: ${PORT}`); 42 | }); 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adel Rizq 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. 22 | -------------------------------------------------------------------------------- /src/routes/courseRouter.js: -------------------------------------------------------------------------------- 1 | const courseController = require("../controllers/courseController.js"); 2 | const auth = require("../auth/auth"); 3 | 4 | const router = require("express").Router(); 5 | 6 | router 7 | .route("/") 8 | .get(auth.verifyToken, courseController.getCourses) 9 | .post(auth.verifyToken, courseController.addCourse); 10 | 11 | router 12 | .route("/my-courses") 13 | .get(auth.verifyToken, courseController.getMyCourses); 14 | 15 | router 16 | .route("/top-courses") 17 | .get(auth.verifyToken, courseController.getTopCourses); 18 | 19 | router 20 | .route("/:id") 21 | .get(auth.verifyToken, courseController.getCourse) 22 | .post(auth.verifyToken, courseController.enrollLearner) 23 | .delete(auth.verifyToken, courseController.deleteCourse); 24 | 25 | router 26 | .route("/:id/questions") 27 | .get(auth.verifyToken, courseController.getQuestions) 28 | .post(auth.verifyToken, courseController.addQuestion); 29 | 30 | router 31 | .route("/:id/answers") 32 | .get(auth.verifyToken, courseController.getAnswers) 33 | .post(auth.verifyToken, courseController.addAnswer); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | const dbConfig = require("../config/db.config"); 2 | const { Sequelize, DataTypes } = require("sequelize"); 3 | 4 | const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, { 5 | host: dbConfig.HOST, 6 | dialect: dbConfig.dialect, 7 | logging: false, 8 | pool: { 9 | max: dbConfig.pool.max, 10 | min: dbConfig.pool.min, 11 | acquire: dbConfig.pool.acquire, 12 | idle: dbConfig.pool.idle, 13 | }, 14 | }); 15 | 16 | sequelize 17 | .authenticate() 18 | .then(() => { 19 | console.log("connected..."); 20 | }) 21 | .catch((err) => { 22 | console.log("Error" + err); 23 | }); 24 | 25 | const db = {}; 26 | db.Sequelize = Sequelize; 27 | db.sequelize = sequelize; 28 | 29 | db.users = require("./userModel")(sequelize, DataTypes); 30 | db.courses = require("./courseModel")(sequelize, DataTypes); 31 | db.activities = require("./activityModel")(sequelize, DataTypes); 32 | 33 | db.answers = require("./answerModel")(sequelize, DataTypes); 34 | db.questions = require("./questionModel")(sequelize, DataTypes); 35 | db.userCourses = require("./userCoursesModel")(sequelize, DataTypes); 36 | 37 | db.users.belongsToMany(db.courses, { 38 | through: db.userCourses, 39 | onDelete: "RESTRICT", 40 | onUpdate: "RESTRICT", 41 | }); 42 | 43 | db.courses.belongsToMany(db.users, { 44 | through: db.userCourses, 45 | onDelete: "RESTRICT", 46 | onUpdate: "RESTRICT", 47 | }); 48 | 49 | db.courses.belongsTo(db.users, { 50 | as: "instructor", 51 | }); 52 | 53 | db.courses.hasMany(db.activities, { 54 | foreignKey: "courseId", 55 | onDelete: "CASCADE", 56 | onUpdate: "CASCADE", 57 | }); 58 | db.activities.belongsTo(db.courses, { 59 | foreignKey: "courseId", 60 | }); 61 | 62 | db.courses.hasMany(db.questions, { 63 | foreignKey: "courseId", 64 | onDelete: "CASCADE", 65 | onUpdate: "CASCADE", 66 | }); 67 | 68 | db.questions.hasMany(db.answers, { 69 | foreignKey: "questionId", 70 | onDelete: "CASCADE", 71 | onUpdate: "CASCADE", 72 | }); 73 | 74 | db.sequelize 75 | .sync({ 76 | force: false, 77 | }) 78 | .then(() => { 79 | console.log("re-sync done"); 80 | }); 81 | 82 | module.exports = db; 83 | -------------------------------------------------------------------------------- /src/models/userModel.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | let userSchema = sequelize.define( 5 | "user", 6 | { 7 | _id: { 8 | type: DataTypes.INTEGER, 9 | primaryKey: true, 10 | allowNull: false, 11 | autoIncrement: true, 12 | // defaultValue: DataTypes.UUIDV4, 13 | }, 14 | username: { 15 | type: DataTypes.STRING(45), 16 | allowNull: false, 17 | unique: true, 18 | }, 19 | email: { 20 | type: DataTypes.STRING(45), 21 | allowNull: false, 22 | unique: true, 23 | }, 24 | password: { 25 | type: DataTypes.STRING, 26 | allowNull: false, 27 | }, 28 | firstName: { 29 | type: DataTypes.STRING, 30 | allowNull: false, 31 | }, 32 | lastName: { 33 | type: DataTypes.STRING, 34 | allowNull: false, 35 | }, 36 | birthDate: { 37 | type: DataTypes.DATEONLY, 38 | allowNull: false, 39 | }, 40 | type: { 41 | type: DataTypes.STRING, 42 | allowNull: false, 43 | }, 44 | }, 45 | { 46 | hooks: { 47 | beforeCreate: async (user) => { 48 | if (user.password) { 49 | const salt = await bcrypt.genSaltSync(10, "a"); 50 | user.password = bcrypt.hashSync(user.password, salt); 51 | } 52 | }, 53 | beforeUpdate: async (user) => { 54 | if (user.password) { 55 | const salt = await bcrypt.genSaltSync(10, "a"); 56 | user.password = bcrypt.hashSync(user.password, salt); 57 | } 58 | }, 59 | }, 60 | instanceMethods: { 61 | validPassword: (password) => { 62 | return bcrypt.compareSync(password, this.password); 63 | }, 64 | }, 65 | } 66 | ); 67 | 68 | userSchema.prototype.validPassword = async (password, hash) => { 69 | return bcrypt.compareSync(password, hash); 70 | }; 71 | 72 | return userSchema; 73 | }; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | ![Group 231](https://user-images.githubusercontent.com/40190772/153667676-0e1a1059-4aa6-422c-87a2-a7c87e9bb24a.png) 5 | 6 |
7 | 8 |

E-LEARN Backend

9 | 10 |
11 | 12 | [![GitHub contributors](https://img.shields.io/github/contributors/AdelRizq/E-Learn-Backend)](https://github.com/AdelRizq/E-Learn-Backend/contributors) 13 | [![GitHub issues](https://img.shields.io/github/issues/AdelRizq/E-Learn-Backend)](https://github.com/AdelRizq/E-Learn-Backend/issues) 14 | [![GitHub forks](https://img.shields.io/github/forks/AdelRizq/E-Learn-Backend)](https://github.com/AdelRizq/E-Learn-Backend/network) 15 | [![GitHub stars](https://img.shields.io/github/stars/AdelRizq/E-Learn-Backend)](https://github.com/AdelRizq/E-Learn-Backend/stargazers) 16 | [![GitHub license](https://img.shields.io/github/license/AdelRizq/E-Learn-Backend)](https://github.com/AdelRizq/E-Learn-Backend/blob/master/LICENSE) 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | ## About The Project 25 | > **E-Learn** is a learning management system, it has multiple features with different types of users, some of its features are signing up as an instructor and creating courses, uploading materials either PDFs or Videos, Enrolling on a course, post question and more. in a fashion similar to 26 | [Google Classrooms](https://classroom.google.com/) 27 | 28 | > This is the backend part, and you can find [the frontend here](https://github.com/AbdallahHemdan/eLearn) 29 | 30 | ### Build with 31 | - [Node.js](https://nodejs.org) 32 | - [Express](https://expressjs.com) 33 | - [MySQL](https://www.mysql.com/) 34 | - [Sequelize](https://sequelize.org/) 35 | - [Convnetional Commits](https://hemdan.hashnode.dev/conventional-commits) 36 | 37 | ## Contributing 38 | 39 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 40 | 41 | 1. Fork the Project 42 | 2. Create your Feature Branch (`git checkout -b AmazingFeature-Feat`) 43 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 44 | 4. Push to the Branch (`git push origin AmazingFeature-Feat`) 45 | 5. Open a Pull Request 46 | 47 | 48 | ## Contributors 49 | > Thanks goes to these awesome people in the backend team. 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
Adel Rizq
Adel Rizq

Ahmed Mahboub
Ahmed Mahboub

Abdallah Hemdan
Abdallah Hemdan

Eman Othman
Eman Othman

63 | 64 | 65 | 66 | 67 | ## License 68 | 69 | > This software is licensed under MIT License, See [License](https://github.com/AdelRizq/E-Learn-Backend/blob/main/LICENSE) for more information. 70 | -------------------------------------------------------------------------------- /src/controllers/activityController.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require("http-status"); 2 | const jwt = require("jsonwebtoken"); 3 | 4 | const db = require("../models"); 5 | const config = require("./../config/jwt.config"); 6 | const constants = require("./../config/constants.config"); 7 | 8 | const Activity = db.activities; 9 | const User = db.users; 10 | 11 | // 1. Add Activity 12 | const addActivity = async (req, res) => { 13 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 14 | if (err) { 15 | return res.status(httpStatus.UNAUTHORIZED).send({ 16 | message: err.message, 17 | }); 18 | } 19 | const user = await User.findOne({ 20 | where: { 21 | username: authData.username, 22 | }, 23 | }); 24 | 25 | if (!user) { 26 | return res.status(httpStatus.NOT_FOUND).send({ 27 | message: "user Not Found", 28 | }); 29 | } 30 | 31 | if ( 32 | user.type != constants.userType.ADMIN && 33 | user.type != constants.userType.INSTRUCTOR 34 | ) { 35 | return res.status(httpStatus.UNAUTHORIZED).send({ 36 | message: 37 | "you must be an admin or instructor to do this operation ", 38 | }); 39 | } 40 | 41 | const info = { 42 | name: req.body.name, 43 | type: req.body.type, 44 | link: req.body.link, 45 | courseId: req.params.courseId, 46 | }; 47 | 48 | try { 49 | const activity = await Activity.create(info); 50 | return res.status(httpStatus.OK).send(activity); 51 | } catch (error) { 52 | return res.status(httpStatus.BAD_REQUEST).send({ 53 | message: "bad information provided", 54 | }); 55 | } 56 | }); 57 | }; 58 | 59 | // 2. Get Activities 60 | const getActivities = async (req, res) => { 61 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 62 | if (err) { 63 | return res.status(httpStatus.UNAUTHORIZED).send({ 64 | message: err.message, 65 | }); 66 | } 67 | const user = await User.findOne({ 68 | where: { 69 | username: authData.username, 70 | }, 71 | }); 72 | 73 | if (!user) { 74 | return res.status(httpStatus.NOT_FOUND).send({ 75 | message: "user Not Found", 76 | }); 77 | } 78 | 79 | try { 80 | const activities = await Activity.findAll({ 81 | where: { 82 | courseId: req.params.courseId, 83 | }, 84 | }); 85 | 86 | if (!activities) { 87 | return res.status(httpStatus.NO_CONTENT).send({ 88 | message: "No Activities Found", 89 | }); 90 | } 91 | 92 | return res.status(httpStatus.OK).send(activities); 93 | } catch (error) { 94 | return res.status(httpStatus.BAD_REQUEST).send({ 95 | message: "invalid information provieded", 96 | }); 97 | } 98 | }); 99 | }; 100 | 101 | // 3. Delete Activity 102 | const deleteActivity = async (req, res) => { 103 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 104 | if (err) { 105 | return res.status(httpStatus.UNAUTHORIZED).send({ 106 | message: err.message, 107 | }); 108 | } 109 | const user = await User.findOne({ 110 | where: { 111 | username: authData.username, 112 | }, 113 | }); 114 | 115 | const activity = await Activity.findOne({ 116 | where: { 117 | _id: req.params.id, 118 | }, 119 | }); 120 | 121 | if (!user || !activity) { 122 | return res.status(httpStatus.NOT_FOUND).send({ 123 | message: "activity Not Found", 124 | }); 125 | } 126 | 127 | if ( 128 | user.type != constants.userType.ADMIN && 129 | user.type != constants.userType.INSTRUCTOR 130 | ) { 131 | return res.status(httpStatus.UNAUTHORIZED).send({ 132 | message: 133 | "you must be an admin or instructor to do this operation ", 134 | }); 135 | } 136 | 137 | try { 138 | await Activity.destroy({ 139 | where: { 140 | _id: req.params.id, 141 | }, 142 | }); 143 | 144 | return res.status(httpStatus.OK).send({ 145 | message: "Activity Deleted Successfully", 146 | }); 147 | } catch (error) { 148 | return res.status(httpStatus.BAD_REQUEST).send({ 149 | message: "Error during deletion", 150 | }); 151 | } 152 | }); 153 | }; 154 | 155 | module.exports = { 156 | addActivity, 157 | getActivities, 158 | deleteActivity, 159 | }; 160 | -------------------------------------------------------------------------------- /src/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const jwt = require("jsonwebtoken"); 3 | const nodemailer = require("nodemailer"); 4 | const httpStatus = require("http-status"); 5 | 6 | const constants = require("../config/constants.config"); 7 | const config = require("./../config/jwt.config"); 8 | const db = require("../models"); 9 | 10 | const User = db.users; 11 | const UserCourse = db.userCourses; 12 | 13 | // 1. Authentication 14 | const signup = async(req, res) => { 15 | const info = { 16 | username: req.body.username, 17 | email: req.body.email, 18 | password: req.body.password, 19 | firstName: req.body.firstName, 20 | lastName: req.body.lastName, 21 | birthDate: req.body.birthDate, 22 | type: req.body.type, 23 | }; 24 | 25 | try { 26 | let user = await User.create(info); 27 | const userData = { 28 | _id: user._id, 29 | username: user.username, 30 | email: user.email, 31 | firstName: user.firstName, 32 | lastName: user.lastName, 33 | birthDate: user.birthDate, 34 | type: user.type, 35 | }; 36 | 37 | info._id = user._id; 38 | return res.status(httpStatus.CREATED).send({ 39 | token: jwt.sign(userData, config.SECRET_KEY, { 40 | expiresIn: config.JWT_EXPIRES_IN, 41 | }), 42 | userData: userData, 43 | }); 44 | } catch (error) { 45 | return res.status(httpStatus.BAD_REQUEST).send({ 46 | message: "Please try different username or email", 47 | }); 48 | } 49 | }; 50 | 51 | const login = async(req, res) => { 52 | const user = await User.findOne({ 53 | where: { 54 | email: req.body.email, 55 | }, 56 | }); 57 | 58 | if (!user) { 59 | return res.status(httpStatus.UNAUTHORIZED).send({ 60 | message: "Email Not Found", 61 | }); 62 | } 63 | 64 | if (!user.password || !(await user.validPassword(req.body.password, user.dataValues.password))) { 65 | return res.status(httpStatus.UNAUTHORIZED).send({ 66 | message: "Password is not correct, please try again", 67 | }); 68 | } 69 | 70 | const userData = { 71 | _id: user._id, 72 | username: user.username, 73 | email: user.email, 74 | firstName: user.firstName, 75 | lastName: user.lastName, 76 | birthDate: user.birthDate, 77 | type: user.type, 78 | }; 79 | 80 | return res.status(httpStatus.OK).send({ 81 | token: jwt.sign(userData, config.SECRET_KEY, { 82 | expiresIn: config.JWT_EXPIRES_IN, 83 | }), 84 | userData: userData, 85 | }); 86 | }; 87 | 88 | const forgotPassword = async(req, res) => { 89 | const userData = { 90 | email: req.body.email, 91 | }; 92 | 93 | const token = jwt.sign(userData, config.SECRET_KEY_RESET_PASSWORD, { 94 | expiresIn: config.JWT_EXPIRES_IN, 95 | }); 96 | try { 97 | 98 | const user = await User.findOne({ 99 | where: { 100 | email: req.body.email, 101 | }, 102 | }); 103 | 104 | if (!user) { 105 | return res.status(httpStatus.UNAUTHORIZED).send({ 106 | message: "Email Not Found", 107 | }); 108 | } 109 | } catch (error) { 110 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 111 | message: "Internal server error", 112 | }); 113 | } 114 | 115 | const transporter = nodemailer.createTransport({ 116 | service: "gmail", 117 | auth: { 118 | user: "dragonlerarners@gmail.com", 119 | pass: "9_Dragon_Learners_9", 120 | }, 121 | }); 122 | 123 | const mailOptions = { 124 | from: "9_Dragon_Learners_9", 125 | to: req.body.email, 126 | subject: "Reset Password", 127 | html: `

please click on the given link to reset your password

128 |

${constants.env.CLIENT_URL}/reset/${token}

`, 129 | }; 130 | 131 | transporter.sendMail(mailOptions, function(error, info) { 132 | if (error) { 133 | return res.status(httpStatus.OK).send({ 134 | message: "invalid email sent", 135 | }); 136 | } else { 137 | return res.status(httpStatus.OK).send({ 138 | message: "email sent", 139 | }); 140 | } 141 | }); 142 | }; 143 | 144 | const resetPassword = async(req, res) => { 145 | try { 146 | const email = jwt.verify( 147 | req.body.token, 148 | config.SECRET_KEY_RESET_PASSWORD 149 | ).email; 150 | 151 | const salt = await bcrypt.genSaltSync(10, "a"); 152 | const updatedPassword = bcrypt.hashSync(req.body.password, salt); 153 | 154 | const user = await User.update({ 155 | password: updatedPassword, 156 | }, { 157 | where: { 158 | email: email, 159 | }, 160 | }); 161 | 162 | if (!user) { 163 | return res.status(httpStatus.FORBIDDEN).send({ 164 | message: "invalid email", 165 | }); 166 | } 167 | 168 | return res.status(httpStatus.OK).send({ 169 | message: "password updated successfully", 170 | }); 171 | } catch (error) { 172 | return res.status(httpStatus.FORBIDDEN).send({ 173 | message: "invalid token sent", 174 | }); 175 | } 176 | }; 177 | 178 | // 2. Get current user 179 | const getUser = async(req, res) => { 180 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 181 | if (err) { 182 | return res.status(httpStatus.UNAUTHORIZED).send({ 183 | message: err.message, 184 | }); 185 | } 186 | 187 | const user = await User.findOne({ 188 | where: { 189 | _id: authData._id, 190 | }, 191 | attributes: [ 192 | "username", 193 | "email", 194 | "firstName", 195 | "lastName", 196 | "type", 197 | "birthDate", 198 | ], 199 | }); 200 | 201 | if (!user) { 202 | return res.status(httpStatus.NOT_FOUND).send({ 203 | message: "Not Found", 204 | }); 205 | } 206 | 207 | return res.status(httpStatus.OK).send(user); 208 | }); 209 | }; 210 | 211 | // 3. Get Users for manage users page 212 | const getUsers = async(req, res) => { 213 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 214 | if (err) { 215 | return res.status(httpStatus.UNAUTHORIZED).send({ 216 | message: err.message, 217 | }); 218 | } 219 | 220 | const user = await User.findOne({ 221 | where: { 222 | username: authData.username, 223 | }, 224 | }); 225 | 226 | if (!user) { 227 | return res.status(httpStatus.NOT_FOUND).send({ 228 | message: "user Not Found", 229 | }); 230 | } 231 | 232 | if (user.type != constants.userType.ADMIN) { 233 | return res.status(httpStatus.UNAUTHORIZED).send({ 234 | message: "you must be an admin to make this operation ", 235 | }); 236 | } 237 | try { 238 | const users = await User.findAll({ 239 | where: { type: ["learner", "instructor"] }, 240 | attributes: ["username", "type"], 241 | }); 242 | 243 | return res.status(httpStatus.OK).send(users); 244 | } catch (error) { 245 | return res.status(httpStatus.NOT_FOUND).send({ 246 | message: "error fetching users", 247 | }); 248 | } 249 | }); 250 | }; 251 | 252 | const upgradeLearner = async(req, res) => { 253 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 254 | if (err) { 255 | return res.status(httpStatus.UNAUTHORIZED).send({ 256 | message: err.message, 257 | }); 258 | } 259 | 260 | const admin = await User.findOne({ 261 | where: { 262 | username: authData.username, 263 | }, 264 | }); 265 | 266 | if (!admin) { 267 | return res.status(httpStatus.NOT_FOUND).send({ 268 | message: "user Not Found", 269 | }); 270 | } 271 | 272 | if (admin.type != constants.userType.ADMIN) { 273 | return res.status(httpStatus.UNAUTHORIZED).send({ 274 | message: "you must be an admin to make this operation ", 275 | }); 276 | } 277 | 278 | const user = await User.findOne({ 279 | where: { 280 | username: req.params.username, 281 | }, 282 | }); 283 | 284 | if (!user) { 285 | return res.status(httpStatus.NOT_FOUND).send({ 286 | message: "user not found", 287 | }); 288 | } 289 | 290 | if (user.type != constants.userType.LEARNER) { 291 | return res.status(httpStatus.FORBIDDEN).send({ 292 | message: "user is already instructor", 293 | }); 294 | } 295 | try { 296 | await User.update({ 297 | type: constants.userType.INSTRUCTOR, 298 | }, { 299 | where: { 300 | username: req.params.username, 301 | }, 302 | attributes: [ 303 | "username", 304 | "email", 305 | "firstName", 306 | "lastName", 307 | "type", 308 | "birthDate", 309 | ], 310 | }); 311 | await UserCourse.destroy({ 312 | where: { 313 | userId: user._id 314 | } 315 | }); 316 | return res.status(httpStatus.OK).send({ 317 | message: "updated successfully", 318 | }); 319 | } catch (error) { 320 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 321 | message: "error during updating database", 322 | }); 323 | } 324 | }); 325 | }; 326 | 327 | const updateUser = async(req, res) => { 328 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 329 | if (err) { 330 | return res.status(httpStatus.UNAUTHORIZED).send({ 331 | message: err.message, 332 | }); 333 | } 334 | const user = await User.findOne({ 335 | where: { 336 | _id: authData._id, 337 | }, 338 | }); 339 | 340 | if (!user) { 341 | return res.status(httpStatus.NOT_FOUND).send({ 342 | message: "user Not Found", 343 | }); 344 | } 345 | 346 | const updatedData = { 347 | username: req.body.username || user.username, 348 | email: req.body.email || user.email, 349 | firstName: req.body.firstName || user.firstName, 350 | lastName: req.body.lastName || user.lastName, 351 | birthDate: req.body.birthDate || user.birthDate, 352 | }; 353 | 354 | try { 355 | await User.update(updatedData, { 356 | where: { 357 | _id: user._id, 358 | }, 359 | }); 360 | const updatedUser = await User.findOne({ 361 | where: { 362 | _id: user._id, 363 | }, 364 | }); 365 | 366 | const userData = { 367 | username: updatedUser.username, 368 | email: updatedUser.email, 369 | firstName: updatedUser.firstName, 370 | lastName: updatedUser.lastName, 371 | birthDate: updatedUser.birthDate, 372 | type: updatedUser.type, 373 | }; 374 | 375 | return res.status(httpStatus.OK).send({ 376 | message: "updated successfully", 377 | userData: userData, 378 | token: jwt.sign(userData, config.SECRET_KEY, { 379 | expiresIn: config.JWT_EXPIRES_IN, 380 | }), 381 | }); 382 | } catch (error) { 383 | return res.status(httpStatus.BAD_REQUEST).send({ 384 | message: "invalid info provided", 385 | }); 386 | } 387 | }); 388 | }; 389 | 390 | const enrollMe = async(req, res) => { 391 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 392 | if (err) { 393 | return res.status(httpStatus.UNAUTHORIZED).send({ 394 | message: err.message, 395 | }); 396 | } 397 | 398 | const user = await User.findOne({ 399 | where: { 400 | _id: authData._id, 401 | }, 402 | }); 403 | 404 | if (!user) { 405 | return res.status(httpStatus.NOT_FOUND).send({ 406 | message: "user Not Found", 407 | }); 408 | } 409 | if ( 410 | user.type != constants.userType.LEARNER && 411 | user.type != constants.userType.ADMIN 412 | ) { 413 | return res.status(httpStatus.UNAUTHORIZED).send({ 414 | message: "you must be an admin or learner to make this operation", 415 | }); 416 | } 417 | try { 418 | const userCourse = await UserCourse.create({ 419 | userId: authData._id, 420 | courseId: req.body.courseId, 421 | }); 422 | 423 | if (!userCourse) 424 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 425 | message: "failed to enroll, please try again", 426 | }); 427 | 428 | return res.status(httpStatus.OK).send(userCourse); 429 | } catch (error) { 430 | return res.status(httpStatus.ALREADY_REPORTED).send({ 431 | message: "Already enrolled", 432 | }); 433 | } 434 | }); 435 | }; 436 | 437 | // 4. Delete User 438 | const deleteUser = async(req, res) => { 439 | jwt.verify(req.token, config.SECRET_KEY, async(err, authData) => { 440 | if (err) { 441 | return res.status(httpStatus.UNAUTHORIZED).send({ 442 | message: err.message, 443 | }); 444 | } 445 | const user = await User.findOne({ 446 | where: { 447 | username: authData.username, 448 | }, 449 | }); 450 | 451 | if (!user) { 452 | return res.status(httpStatus.NOT_FOUND).send({ 453 | message: "user Not Found", 454 | }); 455 | } 456 | 457 | if (user.type != constants.userType.ADMIN) { 458 | return res.status(httpStatus.UNAUTHORIZED).send({ 459 | message: "you must be an admin to make this operation ", 460 | }); 461 | } 462 | 463 | const userToBeDeleted = await User.findOne({ 464 | where: { 465 | username: req.params.username, 466 | }, 467 | }); 468 | 469 | if (!userToBeDeleted) { 470 | return res.status(httpStatus.NOT_FOUND).send({ 471 | message: "User Not Found", 472 | }); 473 | } 474 | try { 475 | await User.destroy({ 476 | where: { 477 | username: req.params.username, 478 | }, 479 | }); 480 | return res.status(httpStatus.OK).send({ 481 | message: "User Deleted Successfully", 482 | }); 483 | } catch (error) { 484 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 485 | message: "failed to delete user", 486 | }); 487 | } 488 | }); 489 | }; 490 | 491 | module.exports = { 492 | signup, 493 | login, 494 | updateUser, 495 | forgotPassword, 496 | resetPassword, 497 | getUser, 498 | getUsers, 499 | upgradeLearner, 500 | enrollMe, 501 | deleteUser, 502 | }; -------------------------------------------------------------------------------- /src/controllers/courseController.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require("http-status"); 2 | const jwt = require("jsonwebtoken"); 3 | const config = require("./../config/jwt.config"); 4 | 5 | const db = require("../models"); 6 | const constants = require("../config/constants.config"); 7 | 8 | const Course = db.courses; 9 | const User = db.users; 10 | const Question = db.questions; 11 | const Answer = db.answers; 12 | const UserCourse = db.userCourses; 13 | 14 | // 1. Add Course 15 | // instructor or admin accounts 16 | const addCourse = async (req, res) => { 17 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 18 | if (err) { 19 | return res.status(httpStatus.UNAUTHORIZED).send({ 20 | message: err.message, 21 | }); 22 | } 23 | const user = await User.findOne({ 24 | where: { 25 | username: authData.username, 26 | }, 27 | }); 28 | 29 | if (!user) { 30 | return res.status(httpStatus.NOT_FOUND).send({ 31 | message: "user Not Found", 32 | }); 33 | } 34 | 35 | if ( 36 | user.type != constants.userType.ADMIN && 37 | user.type != constants.userType.INSTRUCTOR 38 | ) { 39 | return res.status(httpStatus.UNAUTHORIZED).send({ 40 | message: 41 | "you must be an admin or instructor to do this operation ", 42 | }); 43 | } 44 | 45 | const info = { 46 | name: req.body.name, 47 | syllabus: req.body.syllabus, 48 | instructorId: authData._id, 49 | }; 50 | 51 | try { 52 | const course = await Course.create(info); 53 | return res.status(httpStatus.OK).send(course); 54 | } catch (error) { 55 | return res.status(httpStatus.FORBIDDEN).send({ 56 | message: "Duplicate course name", 57 | }); 58 | } 59 | }); 60 | }; 61 | 62 | const addQuestion = async (req, res) => { 63 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 64 | if (err) { 65 | return res.status(httpStatus.UNAUTHORIZED).send({ 66 | message: err.message, 67 | }); 68 | } 69 | 70 | console.log(req); 71 | const user = await User.findOne({ 72 | where: { 73 | username: authData.username, 74 | }, 75 | }); 76 | 77 | if (user == null) { 78 | return res.status(httpStatus.NOT_FOUND).send({ 79 | message: "user Not Found", 80 | }); 81 | } 82 | 83 | const course = await Course.findOne({ 84 | where: { 85 | _id: req.params.id, 86 | }, 87 | }); 88 | 89 | if (course == null) { 90 | return res.status(httpStatus.NOT_FOUND).send({ 91 | message: "course Not Found", 92 | }); 93 | } 94 | 95 | const info = { 96 | username: authData.username, 97 | courseId: req.params.id, 98 | body: req.body.question, 99 | date: req.body.date, 100 | }; 101 | 102 | console.log(info); 103 | try { 104 | const question = await Question.create(info); 105 | return res.status(httpStatus.OK).send(question); 106 | } catch (error) { 107 | return res.status(httpStatus.FORBIDDEN).send({ 108 | message: "invalid question, please try again", 109 | }); 110 | } 111 | }); 112 | }; 113 | 114 | const addAnswer = async (req, res) => { 115 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 116 | if (err) { 117 | return res.status(httpStatus.UNAUTHORIZED).send({ 118 | message: err.message, 119 | }); 120 | } 121 | 122 | const user = await User.findOne({ 123 | where: { 124 | username: authData.username, 125 | }, 126 | }); 127 | 128 | if (user == null) { 129 | return res.status(httpStatus.NOT_FOUND).send({ 130 | message: "user Not Found", 131 | }); 132 | } 133 | 134 | const question = await Question.findOne({ 135 | where: { 136 | _id: req.params.id, 137 | }, 138 | }); 139 | 140 | if (question == null) { 141 | return res.status(httpStatus.NOT_FOUND).send({ 142 | message: "question Not Found", 143 | }); 144 | } 145 | 146 | const info = { 147 | username: authData.username, 148 | questionId: req.params.id, 149 | body: req.body.answer, 150 | date: req.body.date, 151 | }; 152 | 153 | try { 154 | const answer = await Answer.create(info); 155 | return res.status(httpStatus.OK).send(answer); 156 | } catch (error) { 157 | return res.status(httpStatus.FORBIDDEN).send({ 158 | message: "invalid answer, please try again", 159 | }); 160 | } 161 | }); 162 | }; 163 | 164 | // 2. Get Courses 165 | // any authorized user 166 | const getCourse = async (req, res) => { 167 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 168 | if (err) { 169 | return res.status(httpStatus.UNAUTHORIZED).send({ 170 | message: err.message, 171 | }); 172 | } 173 | const course = await Course.findOne({ 174 | where: { 175 | _id: req.params.id, 176 | }, 177 | attributes: ["name", "syllabus", "instructorId", "createdAt"], 178 | }); 179 | 180 | // course.dataValues.createdAt.substr(0, 10) 181 | 182 | if (!course) { 183 | return res.status(httpStatus.NOT_FOUND).send({ 184 | message: "Course Not Found", 185 | }); 186 | } 187 | 188 | course.dataValues.date = new Date(course.dataValues.createdAt) 189 | .toISOString() 190 | .split("T")[0]; 191 | 192 | course.dataValues.instructorName = ( 193 | await User.findOne({ 194 | where: { 195 | _id: course.dataValues.instructorId, 196 | }, 197 | attributes: ["username"], 198 | }) 199 | ).dataValues.username; 200 | 201 | delete course.dataValues.createdAt; 202 | delete course.dataValues.instructorId; 203 | 204 | return res.status(httpStatus.OK).send(course); 205 | }); 206 | }; 207 | 208 | // any authorized user 209 | const getCourses = async (req, res) => { 210 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 211 | if (err) { 212 | return res.status(httpStatus.UNAUTHORIZED).send({ 213 | message: err.message, 214 | }); 215 | } 216 | 217 | let myCoursesIds = await UserCourse.findAll({ 218 | where: { 219 | userId: authData._id, 220 | }, 221 | attributes: ["courseId"], 222 | }); 223 | myCoursesIds = myCoursesIds.map( 224 | (userCourse) => userCourse.dataValues.courseId 225 | ); 226 | 227 | let courses = await Course.findAll({ 228 | attributes: [ 229 | "_id", 230 | "name", 231 | "syllabus", 232 | "instructorId", 233 | "createdAt", 234 | ], 235 | }); 236 | 237 | if (!courses) { 238 | return res.status(httpStatus.NO_CONTENT).send({ 239 | message: "No Courses Found", 240 | }); 241 | } 242 | 243 | for (let course of courses) { 244 | course.dataValues.date = new Date(course.dataValues.createdAt) 245 | .toISOString() 246 | .split("T")[0]; 247 | 248 | course.dataValues.isEnrolled = myCoursesIds.includes( 249 | course.dataValues._id 250 | ); 251 | 252 | course.dataValues.instructorName = ( 253 | await User.findOne({ 254 | where: { 255 | _id: course.dataValues.instructorId, 256 | }, 257 | attributes: ["username"], 258 | }) 259 | ).dataValues.username; 260 | 261 | delete course.dataValues.createdAt; 262 | delete course.dataValues.instructorId; 263 | } 264 | 265 | return res.status(httpStatus.OK).send(courses); 266 | }); 267 | }; 268 | 269 | const getMyCourses = async (req, res) => { 270 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 271 | if (err) { 272 | return res.status(httpStatus.UNAUTHORIZED).send({ 273 | message: err.message, 274 | }); 275 | } 276 | 277 | let myCoursesIds = await UserCourse.findAll({ 278 | where: { 279 | userId: authData._id, 280 | }, 281 | attributes: ["courseId"], 282 | }); 283 | 284 | myCoursesIds = myCoursesIds.map( 285 | (userCourse) => userCourse.dataValues.courseId 286 | ); 287 | 288 | let courses = await Course.findAll({ 289 | where: { 290 | [Op.or]: [ 291 | { _id: myCoursesIds }, 292 | { instructorId: authData._id }, 293 | ], 294 | }, 295 | attributes: [ 296 | "_id", 297 | "name", 298 | "syllabus", 299 | "instructorId", 300 | "createdAt", 301 | ], 302 | }); 303 | 304 | if (!courses) { 305 | return res.status(httpStatus.NO_CONTENT).send({ 306 | message: "No Courses Found For You", 307 | }); 308 | } 309 | 310 | for (let course of courses) { 311 | course.dataValues.date = new Date(course.dataValues.createdAt) 312 | .toISOString() 313 | .split("T")[0]; 314 | 315 | course.dataValues.isEnrolled = myCoursesIds.includes( 316 | course.dataValues._id 317 | ); 318 | 319 | course.dataValues.instructorName = ( 320 | await User.findOne({ 321 | where: { 322 | _id: course.dataValues.instructorId, 323 | }, 324 | attributes: ["username"], 325 | }) 326 | ).dataValues.username; 327 | 328 | delete course.dataValues.createdAt; 329 | delete course.dataValues.instructorId; 330 | } 331 | 332 | return res.status(httpStatus.OK).send(courses); 333 | }); 334 | }; 335 | 336 | // any authorized user 337 | const getTopCourses = async (req, res) => { 338 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 339 | if (err) { 340 | return res.status(httpStatus.UNAUTHORIZED).send({ 341 | message: err.message, 342 | }); 343 | } 344 | 345 | let courses = await Course.findAll({ 346 | attributes: [ 347 | "_id", 348 | "name", 349 | "syllabus", 350 | "instructorId", 351 | "createdAt", 352 | ], 353 | limit: 3, 354 | }); 355 | 356 | if (!courses) { 357 | return res.status(httpStatus.NO_CONTENT).send({ 358 | message: "No Courses Found", 359 | }); 360 | } 361 | 362 | for (let course of courses) { 363 | course.dataValues.date = new Date(course.dataValues.createdAt) 364 | .toISOString() 365 | .split("T")[0]; 366 | 367 | course.dataValues.instructorName = ( 368 | await User.findOne({ 369 | where: { 370 | _id: course.dataValues.instructorId, 371 | }, 372 | attributes: ["username"], 373 | }) 374 | ).dataValues.username; 375 | 376 | delete course.dataValues.createdAt; 377 | delete course.dataValues.instructorId; 378 | } 379 | 380 | return res.status(httpStatus.OK).send(courses); 381 | }); 382 | }; 383 | 384 | // any authorized user 385 | const getQuestions = async (req, res) => { 386 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 387 | if (err) { 388 | return res.status(httpStatus.UNAUTHORIZED).send({ 389 | message: err.message, 390 | }); 391 | } 392 | let questions = await Question.findAll({ 393 | where: { 394 | courseId: req.params.id, 395 | }, 396 | attributes: ["_id", "username", "body", "createdAt"], 397 | }); 398 | 399 | if (!questions) { 400 | return res.status(httpStatus.NO_CONTENT).send({ 401 | message: "No Questions Found", 402 | }); 403 | } 404 | 405 | for (let question of questions) { 406 | const answers = await Answer.findAll({ 407 | where: { 408 | questionId: question.dataValues._id, 409 | }, 410 | attributes: ["username", "body", "createdAt"], 411 | }); 412 | 413 | question.dataValues.date = new Date(question.dataValues.createdAt) 414 | .toISOString() 415 | .split("T")[0]; 416 | 417 | for (let answer of answers) { 418 | answer.date = new Date(answer.createdAt) 419 | .toISOString() 420 | .split("T")[0]; 421 | 422 | delete answer.createdAt; 423 | } 424 | 425 | question.dataValues.answers = answers; 426 | 427 | delete question.dataValues.createdAt; 428 | } 429 | 430 | return res.status(httpStatus.OK).send(questions); 431 | }); 432 | }; 433 | 434 | // any authorized user 435 | const getAnswers = async (req, res) => { 436 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 437 | if (err) { 438 | return res.status(httpStatus.UNAUTHORIZED).send({ 439 | message: err.message, 440 | }); 441 | } 442 | try { 443 | const answers = await Answer.findAll({ 444 | where: { 445 | questionId: req.params.id, 446 | }, 447 | attributes: ["username", "body", "createdAt"], 448 | }); 449 | 450 | if (!answers) { 451 | return res.status(httpStatus.NO_CONTENT).send({ 452 | message: "No Answers Found", 453 | }); 454 | } 455 | return res.status(httpStatus.OK).send(answers); 456 | } catch (error) { 457 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 458 | message: "error getting answers", 459 | }); 460 | } 461 | }); 462 | }; 463 | 464 | const enrollLearner = async (req, res) => { 465 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 466 | if (err) { 467 | return res.status(httpStatus.UNAUTHORIZED).send({ 468 | message: err.message, 469 | }); 470 | } 471 | 472 | const user = await User.findOne({ 473 | where: { 474 | _id: authData._id, 475 | }, 476 | }); 477 | 478 | if (user == null) { 479 | return res.status(httpStatus.NOT_FOUND).send({ 480 | message: "user Not Found", 481 | }); 482 | } 483 | 484 | if ( 485 | user.type != constants.userType.INSTRUCTOR && 486 | user.type != constants.userType.ADMIN 487 | ) { 488 | return res.status(httpStatus.UNAUTHORIZED).send({ 489 | message: 490 | "you must be an admin or learner to make this operation", 491 | }); 492 | } 493 | 494 | try { 495 | if (!req.body.email) { 496 | return res.status(httpStatus.NOT_FOUND).send({ 497 | message: "Email should be sent at the request body", 498 | }); 499 | } 500 | 501 | const learner = await User.findOne({ 502 | where: { 503 | email: req.body.email, 504 | }, 505 | attributes: ["_id"], 506 | }); 507 | 508 | if (!learner) { 509 | return res.status(httpStatus.NOT_FOUND).send({ 510 | message: "User Not Found", 511 | }); 512 | } 513 | 514 | const course = await Course.findOne({ 515 | where: { 516 | _id: req.params.id, 517 | }, 518 | attributes: ["_id"], 519 | }); 520 | 521 | if (!course) { 522 | return res.status(httpStatus.NOT_FOUND).send({ 523 | message: "Course Not Found", 524 | }); 525 | } 526 | 527 | const userCourse = await UserCourse.create({ 528 | userId: learner._id, 529 | courseId: req.params.id, 530 | }); 531 | 532 | if (!userCourse) 533 | return res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ 534 | message: "failed to enroll, please try again", 535 | }); 536 | 537 | return res.status(httpStatus.OK).send(userCourse); 538 | } catch (error) { 539 | return res.status(httpStatus.ALREADY_REPORTED).send({ 540 | message: "Already enrolled", 541 | }); 542 | } 543 | }); 544 | }; 545 | 546 | // 3. Delete Course 547 | const deleteCourse = async (req, res) => { 548 | jwt.verify(req.token, config.SECRET_KEY, async (err, authData) => { 549 | if (err) { 550 | return res.status(httpStatus.UNAUTHORIZED).send({ 551 | message: err.message, 552 | }); 553 | } 554 | const user = await User.findOne({ 555 | where: { 556 | username: authData.username, 557 | }, 558 | }); 559 | 560 | const course = await Course.findOne({ 561 | where: { 562 | _id: req.params.id, 563 | }, 564 | }); 565 | 566 | if (!user || !course) { 567 | return res.status(httpStatus.UNAUTHORIZED).send({ 568 | message: 569 | "you must be an admin or instructor this course to do this operation ", 570 | }); 571 | } 572 | 573 | if ( 574 | user.type != constants.userType.ADMIN && 575 | user._id != course.instructorId 576 | ) { 577 | return res.status(httpStatus.NOT_FOUND).send({ 578 | message: "course Not Found", 579 | }); 580 | } 581 | 582 | try { 583 | await Course.destroy({ 584 | where: { 585 | _id: req.params.id, 586 | }, 587 | }); 588 | 589 | return res.status(httpStatus.OK).send({ 590 | message: "Course Deleted Successfully", 591 | }); 592 | } catch (error) { 593 | return res.status(httpStatus.FORBIDDEN).send({ 594 | message: "error during deletion", 595 | }); 596 | } 597 | }); 598 | }; 599 | 600 | module.exports = { 601 | addCourse, 602 | getCourse, 603 | getCourses, 604 | getMyCourses, 605 | getTopCourses, 606 | addQuestion, 607 | getQuestions, 608 | addAnswer, 609 | getAnswers, 610 | enrollLearner, 611 | deleteCourse, 612 | }; 613 | --------------------------------------------------------------------------------