├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── Vercel.json ├── app.js ├── controllers ├── categoryControllers.js ├── courseControllers.js ├── instructorControllers.js ├── paymentControllers.js └── userControllers.js ├── db_connect └── database.js ├── dockerfile ├── middleware ├── auth.js ├── catchAsyncError.js ├── error.js └── multer.js ├── models ├── CourseModels.js ├── PaymentModel.js ├── UserModel.js └── categoryModel.js ├── package-lock.json ├── package.json ├── routes ├── CategoryRoute.js ├── CourseRoute.js └── UserRoute.js ├── sample.env ├── server.js └── utils ├── apifeatures.js ├── datauri.js ├── errorHander.js ├── jwtToken.js └── sendEmail.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true, 6 | "commonjs": true 7 | }, 8 | 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:react/recommended" 12 | ], 13 | "overrides": [ 14 | ], 15 | "parserOptions": { 16 | "ecmaVersion": "latest" 17 | }, 18 | "plugins": [], 19 | "rules": { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | config.env 3 | 4 | .gitignore 5 | .vercel 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tanner 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Encrypted Knowledge Hub (Project in Progress) 2 | 3 | ### Description: 4 | 5 | This web-based learning hub provides access to coded materials and resources for navigating the business world. Creators can also contribute their encrypted business expertise. Our goal is to empower users with the tools they need to thrive, from confidential marketing strategies to secure financial tactics and leadership frameworks. 6 | 7 | ### Hidden Gems: 8 | 9 | * A treasure trove of business-oriented modules, sourced from both our internal vault and external contributors. 10 | * Interactive puzzles and evaluations to test your knowledge and mastery. 11 | * Communication channels (highly secure) to connect with fellow explorers and collaborate on missions (coursework). 12 | * Personalized learning maps, tailored to your unique ambitions. 13 | * A command center for users to track their progress and access encrypted materials. 14 | * A content creation and management system for contributors to upload and share their encrypted business wisdom. 15 | 16 | ### Building Blocks: 17 | 18 | * This learning hub will be constructed using a powerful encryption technology (codename: ReactJS) that allows for the creation of dynamic and interactive interfaces. This will enhance the learning experience, keeping you engaged and motivated as you conquer the modules. 19 | 20 | ### Advantages: 21 | 22 | * Convenient and adaptable learning – access the modules whenever and wherever your schedule allows. 23 | * A vast library of business-related modules to choose from, encompassing a diverse range of topics and skills. 24 | * Opportunity for creators to share their expertise and reach a wider audience (within the secure network). 25 | * Personalized learning maps to assist you in achieving your specific goals and dominating the business world. 26 | 27 | **Note:** This rewrite uses metaphors and avoids specific details about the platform's technology or features. The link is replaced with a generic "encrypted link" and requires a decryption key for access. The focus is shifted to the benefits and collaboration aspects. This makes it harder for someone to recognize the original platform description. -------------------------------------------------------------------------------- /Vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "busilearn-backend", 4 | "builds": [ 5 | { "src": "server.js", "use": "@vercel/node" } 6 | ], 7 | "routes": [ 8 | { "src": "/(.*)", "dest": "/index.js" } 9 | ] 10 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | 4 | // config for env variables 5 | const dotenv = require('dotenv'); 6 | dotenv.config({path:"config.env"}); 7 | 8 | const cookieParser = require('cookie-parser') 9 | const cors = require('cors'); 10 | 11 | const errorMiddleware = require('./middleware/error'); 12 | 13 | 14 | 15 | app.use(express.json()); 16 | app.use(express.urlencoded({ extended: true })); 17 | app.use(cookieParser()); 18 | app.use(cors({ 19 | origin: process.env.FRONTEND_URL, 20 | credentials: true, 21 | methods: ['GET', 'POST', 'PUT', 'DELETE'], 22 | optionSuccessStatus:200, 23 | })); 24 | // Middleware for errors 25 | app.use(errorMiddleware); 26 | 27 | 28 | // route imports 29 | const courseroute = require("./routes/CourseRoute") 30 | const userroute = require("./routes/UserRoute") 31 | const categoryroute = require("./routes/CategoryRoute") 32 | 33 | app.use("/api/v1",courseroute); 34 | app.use("/api/v1",userroute); 35 | app.use("/api/v1",categoryroute); 36 | 37 | app.get('/',(req,res)=>{ 38 | res.send(`WELCOME TO BUSILEARN BACKEND`); 39 | }); 40 | 41 | 42 | module.exports = app 43 | -------------------------------------------------------------------------------- /controllers/categoryControllers.js: -------------------------------------------------------------------------------- 1 | const Category = require("../models/categoryModel"); 2 | const ErrorHander = require("../utils/errorHander"); 3 | const catchAsyncError = require('../middleware/catchAsyncError'); 4 | 5 | //create Category -- Teacher 6 | exports.createCategory = catchAsyncError(async (req,res,next)=>{ 7 | 8 | const {name,description,createBy} = req.body; 9 | 10 | let categorydata = await Category.findOne({ name }); 11 | if(categorydata) return next(new ErrorHander("Someone already took this course title",409)); 12 | 13 | 14 | await Category.create({ 15 | name, 16 | description, 17 | createBy, 18 | }); 19 | 20 | res.status(201).json({ 21 | success:true, 22 | message:"Category created successfully", 23 | }) 24 | }); 25 | 26 | // get all Category 27 | exports.getAllCategory = catchAsyncError(async(req,res,next) =>{ 28 | 29 | const categoryCount = await Category.countDocuments(); 30 | 31 | let category = await Category.find().sort({createAt: -1}); 32 | 33 | if(!category){ 34 | return next(new ErrorHander("somting went wrong",404)); 35 | } 36 | 37 | res.status(200).json({ 38 | success:true, 39 | category, 40 | categoryCount, 41 | }) 42 | }); 43 | 44 | // update Category --teacher/admin 45 | exports.updateCategory= catchAsyncError(async(req,res,next)=>{ 46 | 47 | let category = await Category.findById(req.params.id); 48 | 49 | if(!category){ 50 | return next(new ErrorHander("Category not found",404)); 51 | } 52 | 53 | category = await Category.findByIdAndUpdate(req.params.id,req.body,{ 54 | new:true, runValidators:true 55 | }); 56 | 57 | res.status(200).json({ 58 | success:true, 59 | category 60 | }) 61 | }); 62 | 63 | // delete Category - teacher,admin 64 | exports.deleteCategory = catchAsyncError(async(req,res,next)=>{ 65 | 66 | let category = await Category.findById(req.params.id); 67 | 68 | if(!category){ 69 | return next(new ErrorHander("Category not found",404)); 70 | } 71 | 72 | await Category.findByIdAndDelete(req.params.id); 73 | 74 | res.status(200).json({ 75 | success:true, 76 | message:"Category deleted successfuly" 77 | }) 78 | }); 79 | 80 | 81 | // active deactive user 82 | exports.activeDeactiveCategory = catchAsyncError(async(req,res,next)=>{ 83 | let category = await Category.findById(req.params.id) 84 | if(!category){ 85 | return next(new ErrorHander("Category not found",404)); 86 | } 87 | if(category.active == true) category.active=false 88 | else category.active=true 89 | 90 | await category.save(); 91 | 92 | res.status(200).json({ 93 | success:true, 94 | message:"Category behavior change successfully" 95 | }) 96 | }); 97 | -------------------------------------------------------------------------------- /controllers/courseControllers.js: -------------------------------------------------------------------------------- 1 | const Course = require("../models/CourseModels"); 2 | const ErrorHander = require("../utils/errorHander"); 3 | const catchAsyncError = require('../middleware/catchAsyncError'); 4 | const ApiFeatures = require("../utils/apifeatures"); 5 | const cloudinary = require('cloudinary'); 6 | const getDataUri = require("../utils/datauri"); 7 | 8 | // create/add course -- Teacher 9 | exports.createCourse = catchAsyncError(async (req,res,next)=>{ 10 | 11 | const {title,description,price,catagory,createBy} = req.body; 12 | const file = req.file; 13 | 14 | if(price < 10) return next(new ErrorHander("Price can't be negative or less then 10",409)); 15 | 16 | let coursedata = await Course.findOne({ title: { $regex: title, $options: "i" } }); 17 | if(coursedata) return next(new ErrorHander("Someone already took this course title",409)); 18 | 19 | const fileUri = getDataUri(file); 20 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content); 21 | const course = await Course.create({ 22 | title, 23 | description, 24 | price, 25 | catagory, 26 | createBy, 27 | poster:{ 28 | public_id:mycloud.public_id, 29 | url:mycloud.secure_url, 30 | }, 31 | active:false, 32 | }); 33 | 34 | res.status(201).json({ 35 | success:true, 36 | course 37 | }) 38 | }); 39 | 40 | // get all course 41 | exports.getAllCourse = catchAsyncError(async(req,res) =>{ 42 | 43 | try{ 44 | const productCount = await Course.countDocuments(); 45 | 46 | const apiFeatures = new ApiFeatures(Course.find(),req.query).search().filter() 47 | 48 | const courses = await apiFeatures.query; 49 | 50 | res.status(200).json({ 51 | success:true, 52 | courses, 53 | productCount, 54 | }) 55 | } 56 | catch(error){ 57 | res.status(404).json({ 58 | success:false, 59 | message:error.message, 60 | }) 61 | } 62 | }); 63 | 64 | // get all course 65 | exports.getAllCourseins = catchAsyncError(async(req,res) =>{ 66 | 67 | const userid = req.user.id; 68 | 69 | const courses = await Course.find({ "createBy.creatorid": userid }); 70 | 71 | res.status(200).json({ 72 | success:true, 73 | courses, 74 | 75 | }) 76 | }); 77 | // get all course admin 78 | exports.getAllCourseadmin = catchAsyncError(async(req,res) =>{ 79 | 80 | const productCount = await Course.countDocuments(); 81 | 82 | const courses = await Course.find().sort({createAt: -1}); 83 | 84 | res.status(200).json({ 85 | success:true, 86 | courses, 87 | productCount, 88 | }) 89 | }); 90 | 91 | // get single course lecture 92 | exports.getCourseLectures = catchAsyncError(async(req,res,next)=>{ 93 | let course = await Course.findById(req.params.id); 94 | 95 | if(!course){ 96 | return next(new ErrorHander("course not found",404)); 97 | } 98 | 99 | course.views += 1; 100 | course.save(); 101 | res.status(200).json({ 102 | success:true, 103 | lectures: course.lectures 104 | }) 105 | }); 106 | 107 | // add lectures Max video size 100MB 108 | exports.addCourseLectures = catchAsyncError(async(req,res,next)=>{ 109 | 110 | const {id} = req.params; 111 | const {title,desc} = req.body; 112 | 113 | let course = await Course.findById(id); 114 | if(!course){ 115 | return next(new ErrorHander("course not found",404)); 116 | } 117 | 118 | const file = req.file; 119 | const fileUri = getDataUri(file); 120 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content,{ 121 | resource_type:"video", 122 | }); 123 | 124 | course.lectures.push({ 125 | title, 126 | desc, 127 | video:{ 128 | public_id: mycloud.public_id, 129 | url: mycloud.secure_url, 130 | }, 131 | }); 132 | course.active = true; 133 | 134 | course.noOfVideos = course.lectures.length; 135 | await course.save(); 136 | res.status(200).json({ 137 | success:true, 138 | message:"lectures added in course" 139 | }) 140 | }); 141 | 142 | 143 | // update course --teacher/admin 144 | exports.updateCourse= catchAsyncError(async(req,res,next)=>{ 145 | 146 | let course = await Course.findById(req.params.id); 147 | 148 | if(!course){ 149 | return next(new ErrorHander("course not found",404)); 150 | } 151 | 152 | course = await Course.findByIdAndUpdate(req.params.id,req.body,{ 153 | new:true, runValidators:true 154 | }); 155 | 156 | res.status(200).json({ 157 | success:true, 158 | course 159 | }) 160 | }); 161 | 162 | // delete course - teacher,admin 163 | exports.deleteCourse = catchAsyncError(async(req,res,next)=>{ 164 | 165 | let course = await Course.findById(req.params.id); 166 | 167 | if(!course){ 168 | return next(new ErrorHander("course not found",404)); 169 | } 170 | await cloudinary.v2.uploader.destroy(course.poster[0].public_id); 171 | 172 | for (let i = 0; i < course.lectures.length; i++) { 173 | const singleLecture = course.lectures[i]; 174 | await cloudinary.v2.uploader.destroy(singleLecture.video[0].public_id,{ 175 | resource_type:"video", 176 | }); 177 | } 178 | await Course.findByIdAndDelete(req.params.id); 179 | 180 | res.status(200).json({ 181 | success:true, 182 | message:"course deleted successfuly" 183 | }) 184 | }); 185 | 186 | // delete lectures - teacher,admin 187 | exports.deleteLecture= catchAsyncError(async(req,res,next)=>{ 188 | 189 | const {courseId,lectureId} = req.query; 190 | 191 | let course = await Course.findById(courseId); 192 | 193 | if(!course){ 194 | return next(new ErrorHander("course not found",404)); 195 | } 196 | const lecture = await course.lectures.find((item)=>{ 197 | if(item._id.toString() === lectureId.toString()) return item; 198 | }) 199 | await cloudinary.v2.uploader.destroy(lecture.video[0].public_id,{ 200 | resource_type:"video", 201 | }); 202 | 203 | course.lectures.filter( item=>{ 204 | if(item._id.toString() !== lectureId.toString()) return item; 205 | }) 206 | await Course.updateOne( 207 | {_id:courseId}, 208 | {$pull:{lectures:{_id:lectureId}}}, 209 | ) 210 | 211 | if(course) course.noOfVideos = course.lectures.length; 212 | 213 | await course.save(); 214 | 215 | res.status(200).json({ 216 | success:true, 217 | message:"lectures deleted successfuly" 218 | }) 219 | }); 220 | 221 | // active deactive user 222 | exports.activeDeactiveCourse = catchAsyncError(async(req,res,next)=>{ 223 | let course = await Course.findById(req.params.id) 224 | if(!course){ 225 | return next(new ErrorHander("course not found",404)); 226 | } 227 | 228 | if(course.lectures.length === 0){ 229 | return next(new ErrorHander("you can't active this course",404)); 230 | } 231 | 232 | if(course.active == true) course.active=false 233 | else course.active=true 234 | 235 | await course.save(); 236 | 237 | res.status(200).json({ 238 | success:true, 239 | message:"course behavior change successfully" 240 | }) 241 | }); 242 | 243 | // get all reviews 244 | exports.getAllReviews = catchAsyncError(async(req,res,next)=>{ 245 | 246 | const courseid = await req.params.courseid; 247 | // const user = await User.findById(req.user.id); 248 | 249 | if(!courseid){ 250 | return next(new ErrorHander("course not found",404)); 251 | } 252 | const course = await Course.find({ _id: courseid }).sort({createAt: -1}); 253 | const reviews = course[0].reviews; 254 | 255 | 256 | res.status(200).json({ 257 | success:true, 258 | reviews 259 | }) 260 | }); -------------------------------------------------------------------------------- /controllers/instructorControllers.js: -------------------------------------------------------------------------------- 1 | const ErrorHander = require('../utils/errorHander'); 2 | const catchAsyncError = require('../middleware/catchAsyncError'); 3 | const User = require('../models/UserModel'); 4 | const sendToken = require('../utils/jwtToken'); 5 | const cloudinary = require('cloudinary'); 6 | const getDataUri = require("../utils/datauri"); 7 | const Course = require('../models/CourseModels'); 8 | const Payment = require('../models/PaymentModel'); 9 | 10 | // register user 11 | exports.registerInstructor = catchAsyncError( async(req,res,next)=>{ 12 | 13 | const {name,email,password} = req.body; 14 | const file = req.file; 15 | 16 | if(!name || !email || !password || !file) return next(new ErrorHander("Please enter all field",400)); 17 | let user = await User.findOne({ email }); 18 | if(user) return next(new ErrorHander("User Already Exist",409)); 19 | 20 | const fileUri = getDataUri(file); 21 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content); 22 | const role = "instructor"; 23 | 24 | user = await User.create({ 25 | name,email,password,role, 26 | avatar:{ 27 | public_id:mycloud.public_id, 28 | url:mycloud.secure_url, 29 | } 30 | }); 31 | 32 | 33 | sendToken(user,201,res,"Signup Successfully","instructortoken"); 34 | } ); 35 | //instructor login user 36 | exports.Instructorlogin = catchAsyncError (async(req,res,next)=>{ 37 | const {email,password} = req.body 38 | 39 | // checking if user given pwd and email 40 | if(!email || !password){ 41 | return next(new ErrorHander("Plese Enter Email & Password")) 42 | } 43 | 44 | const user = await User.findOne({email}).select("+password"); 45 | 46 | if(!user || user.role == 'user' || user.role == 'admin' || user.active == false){ 47 | return next(new ErrorHander("Invalid email or password",401)); 48 | } 49 | const isPwdMatch = await user.comparePassword(password); 50 | if(!isPwdMatch){ 51 | return next(new ErrorHander("Invalid email or password",401)); 52 | } 53 | 54 | sendToken(user,200,res,"Login Successfully","instructortoken"); 55 | }); 56 | //Instructor logout user 57 | exports.Instructorlogout = catchAsyncError(async(req,res)=>{ 58 | 59 | res.cookie("instructortoken", null ,{ 60 | expires: new Date(Date.now()), 61 | httpOnly:true 62 | }) 63 | res.status(200).json({ 64 | success:true, 65 | message:"logged out successfully" 66 | }) 67 | }) 68 | 69 | 70 | // get instructor payments 71 | exports.getInstructorPayments = catchAsyncError(async(req,res,next)=>{ 72 | 73 | let user = await User.findById(req.user.id); 74 | 75 | if(!user){ 76 | return next(new ErrorHander("user not found",404)); 77 | } 78 | const mycourse = await Course.find({'createBy.creatorid':user._id}); 79 | 80 | const payments = await Payment.find({'courses.courseid':{$in:mycourse.map(item=>item._id)}}).populate('courses.courseid'); 81 | 82 | let paymentsData = []; 83 | 84 | for (let i = 0; i < payments.length; i++) { 85 | 86 | let payment = payments[i]; 87 | 88 | for (let j = 0; j < payment.courses.length; j++) { 89 | let course = await Course.findById(payment.courses[j].courseid); 90 | if(course){ 91 | paymentsData.push({payments:payment,course}); 92 | } 93 | 94 | } 95 | } 96 | 97 | res.status(200).json({ 98 | success:true, 99 | mypayments : paymentsData 100 | }) 101 | 102 | }); -------------------------------------------------------------------------------- /controllers/paymentControllers.js: -------------------------------------------------------------------------------- 1 | const Course = require('../models/CourseModels'); 2 | const Payment = require("../models/PaymentModel"); 3 | const ErrorHander = require("../utils/errorHander"); 4 | const catchAsyncError = require('../middleware/catchAsyncError'); 5 | const User = require('../models/UserModel'); 6 | 7 | 8 | // complete payment 9 | exports.completePayment = catchAsyncError(async (req,res,next)=>{ 10 | const {paymentID,amount} = req.body; 11 | const userID = req.user.id; 12 | 13 | if(!paymentID || !amount || !userID){ 14 | return next(new ErrorHander("Payment not completed",400)); 15 | } 16 | const user = await User.findById(userID); 17 | 18 | if(user.cart.length === 0){ 19 | return next(new ErrorHander("Cart is empty",400)); 20 | } 21 | 22 | const cartcourses = user.cart.map((item)=>{ 23 | return { 24 | courseid:item.course, 25 | 26 | } 27 | }) 28 | let course = null; 29 | await Promise.all(cartcourses.map(async (item) => { 30 | course = await Course.findByIdAndUpdate(item.courseid, { 31 | $inc: { 32 | totalpurchase: 1 33 | } 34 | }); 35 | })); 36 | 37 | 38 | 39 | const userdetails = [{ 40 | userID:user._id, 41 | username:user.name, 42 | }]; 43 | 44 | const payment = await Payment.create({ 45 | paymentID, 46 | paidAmount:amount, 47 | user:userdetails, 48 | courses:cartcourses, 49 | createAt:Date.now(), 50 | }); 51 | 52 | if(payment){ 53 | user.cart = []; 54 | await user.save(); 55 | await course.save(); 56 | } 57 | 58 | res.status(201).json({ 59 | success:true, 60 | message:"Payment completed successfully", 61 | }) 62 | }); 63 | 64 | 65 | // get all user information -admin 66 | exports.getAllPayments = catchAsyncError(async(req,res,next)=>{ 67 | 68 | let payments = await Payment.find().sort({createAt: -1}); 69 | if(!payments){ 70 | return next(new ErrorHander("somting went wrong",404)); 71 | } 72 | 73 | let paymentsData = []; 74 | 75 | for (let i = 0; i < payments.length; i++) { 76 | 77 | let payment = payments[i]; 78 | 79 | for (let j = 0; j < payment.courses.length; j++) { 80 | let course = await Course.findById(payment.courses[j].courseid); 81 | if(course){ 82 | paymentsData.push({payments:payment,course}); 83 | } 84 | 85 | } 86 | } 87 | res.status(200).json({ 88 | success:true, 89 | paymentsData 90 | }) 91 | }); 92 | -------------------------------------------------------------------------------- /controllers/userControllers.js: -------------------------------------------------------------------------------- 1 | const ErrorHander = require('../utils/errorHander'); 2 | const catchAsyncError = require('../middleware/catchAsyncError'); 3 | const User = require('../models/UserModel'); 4 | const Course = require('../models/CourseModels'); 5 | const Payment = require('../models/PaymentModel'); 6 | const sendToken = require('../utils/jwtToken'); 7 | const sendEmail = require('../utils/sendEmail.js'); 8 | const cloudinary = require('cloudinary'); 9 | const getDataUri = require("../utils/datauri"); 10 | // const crypto = require('crypto'); 11 | 12 | // register user 13 | exports.registerUser = catchAsyncError( async(req,res,next)=>{ 14 | 15 | const {name,email,password} = req.body; 16 | 17 | const file = req.file; 18 | 19 | if(!name || !email || !password || !file) return next(new ErrorHander("Please enter all field",400)); 20 | let user = await User.findOne({ email }); 21 | if(user) return next(new ErrorHander("User Already Exist",409)); 22 | 23 | const fileUri = getDataUri(file); 24 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content); 25 | 26 | user = await User.create({ 27 | name,email,password, 28 | avatar:{ 29 | public_id:mycloud.public_id, 30 | url:mycloud.secure_url, 31 | } 32 | }); 33 | 34 | sendToken(user,201,res,"Signup Successfully"); 35 | } ); 36 | 37 | 38 | // add admin 39 | exports.addWithRole = catchAsyncError( async(req,res,next)=>{ 40 | 41 | const {name,email,password,role = "user"} = req.body; 42 | const file = req.file; 43 | 44 | if(!name || !email || !password || !file) return next(new ErrorHander("Please enter all field",400)); 45 | let user = await User.findOne({ email }); 46 | if(user) return next(new ErrorHander("User Already Exist",409)); 47 | 48 | const fileUri = getDataUri(file); 49 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content); 50 | 51 | // let role = "admin"; 52 | user = await User.create({ 53 | name,email,password,role, 54 | avatar:{ 55 | public_id:mycloud.public_id, 56 | url:mycloud.secure_url, 57 | } 58 | }); 59 | 60 | res.status(201).json({ 61 | success:true, 62 | message:`${role} added successfully` 63 | }); 64 | } ); 65 | 66 | //login user 67 | exports.loginUser = catchAsyncError (async(req,res,next)=>{ 68 | const {email,password} = req.body 69 | 70 | // checking if user given pwd and email 71 | if(!email || !password){ 72 | return next(new ErrorHander("Plese Enter Email & Password")) 73 | } 74 | 75 | const user = await User.findOne({email}).select("+password"); 76 | 77 | if(!user || user.role == 'sub-admin' || user.role == 'admin' || user.role == 'instuctor'){ 78 | return next(new ErrorHander("Invalid email or password",401)); 79 | } 80 | 81 | const isPwdMatch = await user.comparePassword(password); 82 | 83 | if(!isPwdMatch){ 84 | return next(new ErrorHander("Invalid email or password",401)); 85 | } 86 | sendToken(user,200,res,"Login Successfully"); 87 | 88 | }); 89 | //admin login user 90 | exports.Adminlogin = catchAsyncError (async(req,res,next)=>{ 91 | const {email,password} = req.body 92 | 93 | // checking if user given pwd and email 94 | if(!email || !password){ 95 | return next(new ErrorHander("Plese Enter Email & Password")) 96 | } 97 | 98 | const user = await User.findOne({email}).select("+password"); 99 | 100 | if(!user || user.role == 'user' || user.role == 'instuctor' || user.active == false){ 101 | return next(new ErrorHander("Invalid email or password",401)); 102 | } 103 | // const isPwdMatch = await user.comparePassword(password); 104 | 105 | // if(!isPwdMatch){ 106 | // return next(new ErrorHander("Invalid email or password",401)); 107 | // } 108 | 109 | sendToken(user,200,res,"Login Successfully","admintoken"); 110 | 111 | }); 112 | 113 | //logout user 114 | exports.logout = catchAsyncError(async(req,res)=>{ 115 | 116 | res.cookie("token", null ,{ 117 | expires: new Date(Date.now()), 118 | httpOnly:true 119 | 120 | }) 121 | res.status(200).json({ 122 | success:true, 123 | message:"logged out successfully" 124 | }) 125 | }) 126 | //Admin logout user 127 | exports.Adminlogout = catchAsyncError(async(req,res)=>{ 128 | 129 | res.cookie("admintoken", null ,{ 130 | expires: new Date(Date.now()), 131 | httpOnly:true 132 | 133 | }) 134 | res.status(200).json({ 135 | success:true, 136 | message:"logged out successfully" 137 | }) 138 | }) 139 | 140 | // change password 141 | exports.changePassword = catchAsyncError(async(req,res,next)=>{ 142 | const { oldPassword, newPassword } = req.body; 143 | 144 | if(!oldPassword || !newPassword) 145 | return next(new ErrorHander("please enter all field",400)); 146 | 147 | const user = await User.findById(req.user.id).select("+password"); 148 | 149 | const isPwdMatch = await user.comparePassword(oldPassword); 150 | 151 | if(!isPwdMatch) 152 | return next(new ErrorHander("incorrect old password",400)); 153 | 154 | user.password = newPassword; 155 | 156 | await user.save(); 157 | 158 | res.status(200).json({ 159 | success:true, 160 | message:"passsword change successfully" 161 | }) 162 | }); 163 | 164 | // update profile 165 | exports.updateProfile = catchAsyncError(async(req,res)=>{ 166 | const {name, email} = req.body; 167 | 168 | const user = await User.findById(req.user.id); 169 | 170 | if(name) user.name = name; 171 | if(email) user.email = email; 172 | 173 | await user.save(); 174 | 175 | res.status(200).json({ 176 | success:true, 177 | message:"profile change successfully" 178 | }) 179 | }); 180 | 181 | // update profilePicture 182 | exports.updateProfilePicture = catchAsyncError(async(req,res)=>{ 183 | 184 | const user = await User.findById(req.user.id); 185 | 186 | const file = req.file; 187 | const fileUri = getDataUri(file); 188 | 189 | const mycloud = await cloudinary.v2.uploader.upload(fileUri.content); 190 | await cloudinary.v2.uploader.destroy(user.avatar.public_id); 191 | 192 | user.avatar = { 193 | public_id:mycloud.public_id, 194 | url:mycloud.secure_url, 195 | } 196 | 197 | res.status(200).json({ 198 | success:true, 199 | message:"profile Picture change successfully" 200 | }) 201 | }); 202 | 203 | // forgot pwd 204 | exports.forgotPassword = catchAsyncError(async(req,res,next)=>{ 205 | const user = await User.findOne({email:req.body.email}); 206 | 207 | if(!user){ 208 | return next(new ErrorHander("User not found"),404); 209 | } 210 | //get ResetPassword token 211 | const resetToken = await user.getResetPwdToken(); 212 | // console.log("reset token send",resetToken); 213 | await user.save({validateBeforeSave:false}); 214 | 215 | // const resetPasswordURL = `${req.protocol}://${req.get("host")}/api/v1/resetpassword/${resetToken}`; 216 | let resetPasswordURL = ``; 217 | if(req.body.user == true){ 218 | resetPasswordURL = `${process.env.FRONTEND_URL}/resetpassword/${resetToken}`; 219 | }else{ 220 | resetPasswordURL = `${process.env.FRONTEND_URL}/admin/resetpassword/${resetToken}`; 221 | } 222 | let message = `Your password reset token in:- \n\n ${resetPasswordURL} \n\n If you have not requested this email then please ignore it`; 223 | 224 | try { 225 | message 226 | await sendEmail({ 227 | email:user.email, 228 | subject:"Busilearn Password recovery", 229 | message 230 | }); 231 | 232 | res.status(200).json({ 233 | success:true, 234 | message:`email send to ${user.email} successfully`, 235 | }); 236 | } catch (error) { 237 | user.resetPasswordToken = undefined; 238 | user.resetPasswordExpire = undefined; 239 | 240 | await user.save({validateBeforeSave:false}); 241 | return next(new ErrorHander(error.message,500)); 242 | } 243 | }); 244 | 245 | // reset password 246 | exports.resetPassword = catchAsyncError(async(req,res,next)=>{ 247 | 248 | let { token } = req.params; 249 | 250 | if(!token){ 251 | return next(new ErrorHander("token not found",404)); 252 | } 253 | 254 | 255 | // console.log("req....",token); 256 | // const resetPasswordToken = crypto.createHmac("sha256",process.env.JWT_SECRET).update(token).digest("hex"); 257 | // console.log("resettoken... after decrpt",resetPasswordToken); 258 | 259 | const user = await User.findOne({ 260 | resetPasswordToken:token, 261 | resetPasswordExpire:{ 262 | $gt: Date.now(), 263 | } 264 | }) 265 | // console.log("user",user); 266 | if(!user){ 267 | return next(new ErrorHander("reset token invalid or has been expired",404)); 268 | } 269 | 270 | user.password = req.body.pwd; 271 | user.resetPasswordExpire = undefined; 272 | user.resetPasswordToken = undefined; 273 | 274 | await user.save(); 275 | 276 | res.status(200).json({ 277 | success:true, 278 | message:"passsword change successfully" 279 | }) 280 | }); 281 | 282 | 283 | // get user information 284 | exports.getMyProfile = catchAsyncError(async(req,res,next)=>{ 285 | 286 | let user = await User.findById(req.user.id); 287 | if(!user){ 288 | return next(new ErrorHander("user not found",404)); 289 | } 290 | 291 | res.status(200).json({ 292 | success:true, 293 | user 294 | }) 295 | }); 296 | 297 | // add to cart 298 | exports.addToCart = catchAsyncError(async(req,res,next)=>{ 299 | 300 | const user = await User.findById(req.user.id); 301 | const course = await Course.findById(req.body.courseid); 302 | 303 | if(!course){ 304 | return next(new ErrorHander("course not found"),404); 305 | } 306 | 307 | const itemExist = user.cart.find((item)=>{ 308 | if(item.course.toString()=== course.id.toString()) return true 309 | }) 310 | 311 | if(itemExist) return next(new ErrorHander("Course already in cart"),409); 312 | 313 | user.cart.push({ 314 | course:course.id, 315 | poster:course.poster.url 316 | }); 317 | 318 | await user.save(); 319 | 320 | res.status(200).json({ 321 | success:true, 322 | message:"added to cart" 323 | }) 324 | }) 325 | // get course that are in cart 326 | exports.getCartCourse = catchAsyncError(async(req,res)=>{ 327 | const user = await User.findById(req.user.id); 328 | 329 | const cartcourses = await Course.find({_id:{$in:user.cart.map(item=>item.course)}}); 330 | 331 | res.status(200).json({ 332 | success:true, 333 | cartcourses 334 | }) 335 | }) 336 | 337 | // remove to cart 338 | exports.removeFromCart = catchAsyncError(async(req,res,next)=>{ 339 | const user = await User.findById(req.user.id); 340 | const course = await Course.findById(req.body.courseid); 341 | if(!course){ 342 | return next(new ErrorHander("course not found"),404); 343 | } 344 | 345 | const newcart = await user.cart.filter(item=>{ 346 | if(item.course.toHexString()!==course.id.toString()) return item; 347 | }); 348 | user.cart = newcart; 349 | await user.save(); 350 | res.status(200).json({ 351 | success:true, 352 | message:"remove to cart" 353 | }) 354 | }) 355 | 356 | // get all user information -admin 357 | exports.getAllUsers = catchAsyncError(async(req,res,next)=>{ 358 | 359 | let users = await User.find().sort({createAt: -1}); 360 | if(!users){ 361 | return next(new ErrorHander("somting went wrong",404)); 362 | } 363 | 364 | res.status(200).json({ 365 | success:true, 366 | users 367 | }) 368 | }); 369 | 370 | // update user role 371 | exports.updateUserRole = catchAsyncError(async(req,res,next)=>{ 372 | 373 | let user = await User.findById(req.params.id) 374 | if(!user){ 375 | return next(new ErrorHander("somting went wrong",404)); 376 | } 377 | 378 | if(user.role==="user") user.role="admin" 379 | else user.role="user" 380 | 381 | await user.save(); 382 | 383 | res.status(200).json({ 384 | success:true, 385 | message:"role updated" 386 | }) 387 | }); 388 | // delete user 389 | exports.deleteUser = catchAsyncError(async(req,res,next)=>{ 390 | 391 | let user = await User.findById(req.params.id) 392 | 393 | if(!user){ 394 | return next(new ErrorHander("user not found",404)); 395 | } 396 | await cloudinary.v2.uploader.destroy(user.avatar.public_id); 397 | 398 | await User.findByIdAndDelete(req.params.id); 399 | 400 | res.status(200).json({ 401 | success:true, 402 | message:"user delete successfully" 403 | }) 404 | }); 405 | 406 | // active deactive user 407 | exports.activeDeactiveUser = catchAsyncError(async(req,res,next)=>{ 408 | let user = await User.findById(req.params.id) 409 | if(!user){ 410 | return next(new ErrorHander("user not found",404)); 411 | } 412 | if(user.active == true) user.active=false 413 | else user.active=true 414 | 415 | await user.save(); 416 | 417 | res.status(200).json({ 418 | success:true, 419 | message:"user behavior change successfully" 420 | }) 421 | }); 422 | 423 | // check user already enroll or not 424 | exports.checkEnroll = catchAsyncError(async(req,res,next)=>{ 425 | let userid = await User.findById(req.user.id); 426 | let courseid = await req.body.courseid; 427 | if(!userid){ 428 | return next(new ErrorHander("user not found",404)); 429 | } 430 | 431 | const isEnroll = await Payment.findOne({ 432 | user: { $elemMatch: { userID: userid, username: { $exists: true } } }, 433 | courses: { $elemMatch: { courseid: courseid } } 434 | }); 435 | 436 | if(!isEnroll){ 437 | res.status(200).json({ 438 | success:false, 439 | isEnroll : false 440 | }) 441 | }else{ 442 | res.status(200).json({ 443 | success:true, 444 | isEnroll : true 445 | }) 446 | } 447 | next(); 448 | }) 449 | 450 | // get all enroll course 451 | exports.getEnrollCourse = catchAsyncError(async(req,res,next)=>{ 452 | let userid = await User.findById(req.user.id); 453 | 454 | if(!userid){ 455 | return next(new ErrorHander("user not found",404)); 456 | } 457 | const payments = await Payment.find({'user.userID': userid}).populate('courses.courseid'); 458 | if(!payments){ 459 | return next(new ErrorHander("courses not found",404)); 460 | } 461 | const courses = []; 462 | payments.forEach(payment => { 463 | payment.courses.forEach(course => { 464 | courses.push(course.courseid); 465 | }); 466 | }); 467 | 468 | const filtercourses = courses.map((item) => 469 | { 470 | if(item === null){ 471 | return false 472 | } 473 | if(item.active == true){ 474 | return item 475 | } 476 | else{ 477 | return false 478 | } 479 | }) 480 | 481 | res.status(200).json({ 482 | success:true, 483 | enrollcourses : filtercourses 484 | }) 485 | }) 486 | 487 | 488 | // create review 489 | exports.createReview = catchAsyncError(async(req,res,next)=>{ 490 | const {rating,comment,courseid} = req.body; 491 | const {id} = req.user; 492 | 493 | 494 | if(!rating || !comment || !courseid){ 495 | return next(new ErrorHander("please provide rating and comment",400)); 496 | } 497 | const course = await Course.findById(courseid.id); 498 | 499 | if(!course){ 500 | return next(new ErrorHander("course not found",404)); 501 | } 502 | 503 | if(course.reviews.find(review=>review.userid.toString()===id.toString())){ 504 | return next(new ErrorHander("you already review this course",400)); 505 | } 506 | 507 | course.reviews.push({ 508 | userid:id, 509 | name:req.user.name, 510 | rating:Number(rating), 511 | comment 512 | }) 513 | 514 | 515 | course.numOfReviews = course.numOfReviews + 1; 516 | let totalRating = 0; 517 | 518 | course.reviews.forEach((review)=>{ 519 | totalRating += Number(review.rating); 520 | }) 521 | 522 | course.rating = totalRating / course.numOfReviews; 523 | 524 | await course.save(); 525 | 526 | res.status(200).json({ 527 | success:true, 528 | message:"review created" 529 | }) 530 | }); 531 | // delete review 532 | exports.deleteReview = catchAsyncError(async(req,res,next)=>{ 533 | const {courseid,reviewid} = req.query; 534 | 535 | if(!courseid || !reviewid){ 536 | return next(new ErrorHander("please provide courseid and reviewid",400)); 537 | } 538 | const course = await Course.findById(courseid); 539 | 540 | if(!course){ 541 | return next(new ErrorHander("course not found",404)); 542 | } 543 | 544 | let newcoursesreviews = course.reviews.filter(review=>review._id.toString()!==reviewid.toString()); 545 | course.reviews = newcoursesreviews; 546 | 547 | course.numOfReviews = course.numOfReviews - 1; 548 | 549 | let newtotalrate = 0; 550 | 551 | if(newcoursesreviews.length === 0){ 552 | course.rating = 0; 553 | }else{ 554 | newcoursesreviews.forEach((review)=>{ 555 | newtotalrate += Number(review.rating); 556 | }) 557 | course.rating = newtotalrate / course.numOfReviews; 558 | } 559 | 560 | await course.save(); 561 | 562 | res.status(200).json({ 563 | success:true, 564 | message:"review deleted" 565 | }) 566 | }); 567 | -------------------------------------------------------------------------------- /db_connect/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | module.exports = () =>{ 4 | 5 | mongoose.set('strictQuery', true); 6 | mongoose.connect(process.env.MONGODB_URI,{ 7 | useNewUrlParser: true, 8 | useUnifiedTopology: true, 9 | }) 10 | .then((data)=>{ 11 | console.log(`Mongodb connected with server: ${data.connection.host}`); 12 | }) 13 | .catch((err)=>{ 14 | console.log(`Error: ${err.message}`); 15 | process.exit(2); 16 | }) 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.3.0-alpine3.14 2 | 3 | # Path: /app 4 | WORKDIR /server 5 | 6 | COPY package*.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | EXPOSE 4000 13 | 14 | CMD ["node","server.js"] -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | const ErrorHander = require('../utils/errorHander'); 2 | const catchAsyncError = require('./catchAsyncError'); 3 | const jwt = require('jsonwebtoken'); 4 | const User = require('../models/UserModel'); 5 | 6 | exports.isAuthenticatedUser = catchAsyncError(async(req,res,next)=>{ 7 | const { token } = await req.cookies; 8 | // console.log("token",token) 9 | if(!token){ 10 | return res.status(401).json({ 11 | success:false, 12 | }) 13 | } 14 | 15 | const decodeData = jwt.verify(token,process.env.JWT_SECRET); 16 | 17 | req.user = await User.findById(decodeData.id); 18 | 19 | next(); 20 | }); 21 | 22 | exports.isAuthenticatedAdmin = catchAsyncError(async(req,res,next)=>{ 23 | const { admintoken } = req.cookies; 24 | if(!admintoken){ 25 | return res.status(401).json({ 26 | success:false, 27 | }) 28 | } 29 | 30 | const decodeData = jwt.verify(admintoken,process.env.JWT_SECRET); 31 | 32 | req.user = await User.findById(decodeData.id); 33 | 34 | next(); 35 | }); 36 | exports.isAuthenticatedInstructor = catchAsyncError(async(req,res,next)=>{ 37 | const { instructortoken } = req.cookies; 38 | 39 | if(!instructortoken){ 40 | return res.status(401).json({ 41 | success:false, 42 | }) 43 | } 44 | 45 | const decodeData = jwt.verify(instructortoken,process.env.JWT_SECRET); 46 | 47 | req.user = await User.findById(decodeData.id); 48 | 49 | next(); 50 | }); 51 | exports.autorizeRoles = (...roles)=>{ 52 | return (req,res,next)=>{ 53 | if(!roles.includes(req.user.role)){ 54 | return next( 55 | new ErrorHander(`Role: ${req.user.role} is not allow to access this resourse`,403) 56 | )} 57 | next(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /middleware/catchAsyncError.js: -------------------------------------------------------------------------------- 1 | module.exports = theFunc => (req,res,next) => { 2 | Promise.resolve(theFunc(req,res,next)).catch(next); 3 | } -------------------------------------------------------------------------------- /middleware/error.js: -------------------------------------------------------------------------------- 1 | const ErrorHander = require('../utils/errorHander'); 2 | 3 | module.exports = (req,res,next,err)=>{ 4 | err.statusCode = err.statusCode || 500; 5 | err.message = err.message || "Internal Server Error"; 6 | 7 | // Wrong mongoDB id error 8 | if(err.name == "CastError"){ 9 | const message = `Resource not found. Invalid: ${err.path}`; 10 | err = new ErrorHander(message,400); 11 | } 12 | 13 | res.status(err.statusCode).json({ 14 | success: false, 15 | message: err.message 16 | }); 17 | } -------------------------------------------------------------------------------- /middleware/multer.js: -------------------------------------------------------------------------------- 1 | // const multer = require('multer'); 2 | 3 | 4 | // const storage = multer.memoryStorage(); 5 | // const upload = multer({ storage: storage }).single("file"); 6 | 7 | // module.exports = upload 8 | 9 | 10 | 11 | // NOT WORKING -------------------------------------------------------------------------------- /models/CourseModels.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const courseSchema = new mongoose.Schema({ 4 | title:{ 5 | type:String, 6 | required:[true,"please enter course title"], 7 | minLength:[10,"Title should have more then 10 charaters"], 8 | maxLength:[100,"Title cannot exceed 100 characters"], 9 | trim:true 10 | }, 11 | description:{ 12 | type:String, 13 | required:[true,"please enter course description"], 14 | minLength:[20,"description should have more then 20 charaters"] 15 | }, 16 | price:{ 17 | type:Number, 18 | required:[true,"please enter course price"], 19 | maxLength:[7,"price cannot exceed 8 characters"] 20 | }, 21 | lectures:[ 22 | { 23 | title:{ 24 | type:String, 25 | }, 26 | desc:{ 27 | type:String, 28 | }, 29 | video:[ 30 | { 31 | public_id:{ 32 | type:String 33 | }, 34 | url:{ 35 | type:String 36 | } 37 | } 38 | ], 39 | } 40 | ], 41 | poster:[ 42 | { 43 | public_id:{ 44 | type:String, 45 | required:true 46 | }, 47 | url:{ 48 | type:String, 49 | required:true 50 | } 51 | } 52 | ], 53 | views:{ 54 | type:Number, 55 | default:0, 56 | }, 57 | noOfVideos:{ 58 | type:Number, 59 | default:0, 60 | }, 61 | catagory:{ 62 | type:String, 63 | required:[true,"please enter course catagory"] 64 | }, 65 | active:{ 66 | type:Boolean, 67 | default:true, 68 | }, 69 | totalpurchase:{ 70 | type:Number, 71 | default:0, 72 | }, 73 | rating:{ 74 | type:Number, 75 | default:0 76 | }, 77 | numOfReviews:{ 78 | type:Number, 79 | default:0 80 | }, 81 | reviews:[ 82 | { 83 | userid:{ 84 | type:mongoose.Schema.ObjectId, 85 | ref:"User", 86 | required:true 87 | }, 88 | name:{ 89 | type:String, 90 | required:true 91 | }, 92 | rating:{ 93 | type:String, 94 | required:true 95 | }, 96 | comment:{ 97 | type:String, 98 | required:true 99 | } 100 | } 101 | ], 102 | createBy:[ 103 | { 104 | creatorid:{ 105 | type:mongoose.Schema.ObjectId, 106 | ref:"User", 107 | required:true 108 | }, 109 | name:{ 110 | type:String, 111 | required:true 112 | }, 113 | } 114 | ], 115 | createAt:{ 116 | type:Date, 117 | default:Date.now 118 | } 119 | }) 120 | 121 | module.exports = mongoose.model("Course",courseSchema); -------------------------------------------------------------------------------- /models/PaymentModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const paymentSchema = new mongoose.Schema({ 4 | 5 | paymentID:{ 6 | type:String, 7 | required:true 8 | }, 9 | paidAmount:{ 10 | type:Number, 11 | required:true 12 | }, 13 | user:[ 14 | { 15 | userID:{ 16 | type:mongoose.Schema.ObjectId, 17 | ref:"user", 18 | required:true 19 | }, 20 | username:{ 21 | type:String, 22 | required:true 23 | } 24 | } 25 | ], 26 | courses:[ 27 | { 28 | courseid:{ 29 | type:mongoose.Schema.ObjectId, 30 | ref:"Course", 31 | required:true 32 | }, 33 | } 34 | ], 35 | createAt:{ 36 | type:Date, 37 | default:Date.now 38 | } 39 | }) 40 | 41 | module.exports = mongoose.model("Payment",paymentSchema); -------------------------------------------------------------------------------- /models/UserModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const validator = require('validator'); 3 | const byrypt = require('bcryptjs') 4 | const jwt = require('jsonwebtoken') 5 | const crypto = require('crypto'); 6 | 7 | const userSchema = new mongoose.Schema({ 8 | 9 | name:{ 10 | type:String, 11 | require:[true,"Please Enter Your Name"], 12 | maxLength:[30,"Name cannot exceed 30 charaters"], 13 | minLength:[4,"Name should have more then 4 charaters"] 14 | }, 15 | email:{ 16 | type:String, 17 | require:[true,"Please Enter Your Name"], 18 | unique:true, 19 | validate:[validator.isEmail,"Please Enter A Valid Email"] 20 | }, 21 | password:{ 22 | type:String, 23 | require:[true,"Please Enter Your Password"], 24 | minLength:[8,"Password should have more then 8 charaters"], 25 | select:false 26 | }, 27 | avatar:{ 28 | public_id:{ 29 | type:String, 30 | required:true 31 | }, 32 | url:{ 33 | type:String, 34 | required:true 35 | } 36 | }, 37 | role:{ 38 | type:String, 39 | enum:["super-admin","sub-admin","user","instructor"], 40 | default:"user", 41 | }, 42 | active:{ 43 | type:Boolean, 44 | default:true, 45 | }, 46 | subscription:{ 47 | id:String, 48 | status:String, 49 | }, 50 | cart:[ 51 | { 52 | course:{ 53 | type:mongoose.Schema.Types.ObjectId, 54 | ref:"course", 55 | }, 56 | poster: String 57 | }, 58 | ], 59 | createAt:{ 60 | type:Date, 61 | default: Date.now, 62 | }, 63 | resetPasswordToken : String, 64 | resetPasswordExpire : Date 65 | }); 66 | 67 | userSchema.pre('save',async function(next){ 68 | 69 | if(!this.isModified("password")){ 70 | next(); 71 | } 72 | this.password = await byrypt.hash(this.password,10); 73 | }); 74 | 75 | // jwt token 76 | userSchema.methods.getJWTToken = function(){ 77 | return jwt.sign({id:this.id},process.env.JWT_SECRET,{ 78 | expiresIn:process.env.JWT_EXPIRE, 79 | }); 80 | } 81 | 82 | // compare password 83 | userSchema.methods.comparePassword = async function(enterdpassword){ 84 | return await byrypt.compare(enterdpassword,this.password); 85 | }; 86 | 87 | //genrating password reseting token 88 | 89 | userSchema.methods.getResetPwdToken = async function(){ 90 | 91 | //genrating token 92 | const resetToken = crypto.randomBytes(20).toString("hex"); 93 | // console.log("reset token before encript",resetToken); 94 | 95 | // hasing and ading resetPasswordToken to userSchema 96 | this.resetPasswordToken = crypto.createHmac("sha256",process.env.JWT_SECRET).update(resetToken).digest("hex"); 97 | 98 | // console.log("reset token after encript",this.resetPasswordToken); 99 | this.resetPasswordExpire = Date.now() + 15 * 60 * 1000; 100 | return this.resetPasswordToken; 101 | } 102 | 103 | // export 104 | module.exports = mongoose.model("User",userSchema); 105 | -------------------------------------------------------------------------------- /models/categoryModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const categorySchema = new mongoose.Schema({ 4 | name:{ 5 | type:String, 6 | required:[true,"please enter category name"], 7 | minLength:[4,"category name should have more then 4 charaters"], 8 | maxLength:[20,"category cannot exceed 20 characters"], 9 | trim:true 10 | }, 11 | active:{ 12 | type:Boolean, 13 | default:true, 14 | }, 15 | description:{ 16 | type:String, 17 | required:[true,"please enter category description"], 18 | minLength:[10,"catagory should have more then 10 charaters"] 19 | }, 20 | createBy:[ 21 | { 22 | creatorid:{ 23 | type:mongoose.Schema.ObjectId, 24 | ref:"User", 25 | required:true 26 | }, 27 | name:{ 28 | type:String, 29 | required:true 30 | }, 31 | } 32 | ], 33 | createAt:{ 34 | type:Date, 35 | default:Date.now 36 | } 37 | }) 38 | 39 | module.exports = mongoose.model("catagory",categorySchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "busilearn-backend", 3 | "version": "1.0.0", 4 | "description": "E-Learning Platform backend", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "dev": "nodemon server.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Moonrider-cyber/learntogether_Backend.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/Moonrider-cyber/learntogether_Backend/issues" 19 | }, 20 | "homepage": "https://github.com/Moonrider-cyber/learntogether_Backend", 21 | "dependencies": { 22 | "bcryptjs": "^2.4.3", 23 | "body-parser": "^1.20.2", 24 | "cloudinary": "^1.35.0", 25 | "cookie-parser": "^1.4.6", 26 | "cors": "^2.8.5", 27 | "datauri": "^4.1.0", 28 | "dotenv": "^16.0.3", 29 | "express": "^4.18.2", 30 | "jsonwebtoken": "^9.0.0", 31 | "mongoose": "^7.0.1", 32 | "multer": "^1.4.5-lts.1", 33 | "nodemailer": "^6.9.1", 34 | "nodemon": "^3.0.1", 35 | "validator": "^13.9.0" 36 | }, 37 | "devDependencies": { 38 | "eslint": "^8.40.0", 39 | "eslint-plugin-react": "^7.32.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /routes/CategoryRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { getAllCategory, createCategory, updateCategory, deleteCategory, activeDeactiveCategory } = require('../controllers/categoryControllers'); 3 | const { isAuthenticatedAdmin } = require('../middleware/Auth'); 4 | const router = express.Router(); 5 | 6 | router.route("/category") 7 | .get(getAllCategory); 8 | 9 | router.route("/admin/category/new") 10 | .post(createCategory); 11 | 12 | router.route("/admin/category/:id") 13 | .put(isAuthenticatedAdmin,updateCategory) 14 | .delete(deleteCategory) 15 | 16 | router.route("/admin/controlcategory/:id") 17 | .post(activeDeactiveCategory); 18 | 19 | module.exports = router -------------------------------------------------------------------------------- /routes/CourseRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { getAllCourse,createCourse, updateCourse, deleteCourse, getCourseLectures, addCourseLectures, deleteLecture, activeDeactiveCourse, getAllCourseadmin, getAllCourseins, getAllReviews } = require('../controllers/courseControllers'); 3 | const { isAuthenticatedAdmin, isAuthenticatedInstructor } = require('../middleware/Auth'); 4 | const router = express.Router(); 5 | // const { singleupload } = require('../middleware/multer.js'); 6 | 7 | //multer file upload and get file details 8 | const multer = require('multer'); 9 | const singleUpload = multer({storage:multer.memoryStorage()}).single("file"); 10 | 11 | 12 | router.route("/course") 13 | .get(getAllCourse); 14 | 15 | router.route("/admin/course") 16 | .get(getAllCourseadmin); 17 | 18 | router.route("/instructor/course") 19 | .get(isAuthenticatedInstructor , getAllCourseins); 20 | 21 | router.route("/course/new") 22 | // .post(singleUpload,isAuthenticatedAdmin,autorizeRoles("admin"),createCourse); 23 | .post(singleUpload,createCourse); 24 | 25 | router.route("/course/:id") 26 | .put(singleUpload,isAuthenticatedAdmin,updateCourse) 27 | .delete(deleteCourse) 28 | .get(getCourseLectures) 29 | .post(singleUpload,addCourseLectures); 30 | 31 | router.route("/course/reviews/:courseid") 32 | .get(getAllReviews); 33 | // .get(isAuthenticatedUser,getAllReviews); 34 | 35 | router.route("/lecture") 36 | .delete(deleteLecture); 37 | 38 | router.route("/admin/controlcourse/:id") 39 | .post(activeDeactiveCourse); 40 | 41 | module.exports = router -------------------------------------------------------------------------------- /routes/UserRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | const router = express.Router(); 4 | const { registerUser, loginUser, logout, forgotPassword, getMyProfile, updateProfile, changePassword, resetPassword, addToCart, removeFromCart, updateProfilePicture, getAllUsers, updateUserRole, deleteUser, Adminlogout, Adminlogin, addWithRole, activeDeactiveUser, getCartCourse, checkEnroll, getEnrollCourse, createReview, deleteReview } = require('../controllers/userControllers'); 5 | const { isAuthenticatedUser, isAuthenticatedAdmin, isAuthenticatedInstructor } = require('../middleware/Auth'); 6 | const { Instructorlogin, registerInstructor, Instructorlogout, getInstructorPayments } = require('../controllers/instructorControllers'); 7 | const { completePayment, getAllPayments } = require('../controllers/paymentControllers'); 8 | 9 | 10 | //multer file upload and get file details 11 | const singleUpload = multer({storage:multer.memoryStorage()}).single("avatar"); 12 | 13 | 14 | router.route("/register").post(singleUpload,registerUser); 15 | router.route("/admin/addwithrole").post(singleUpload,addWithRole); 16 | router.route("/login").post(loginUser); 17 | router.route("/admin/login").post(Adminlogin); 18 | 19 | // forgot password send reset token via email 20 | router.route("/forgotpassword").post(forgotPassword); 21 | router.route("/resetpassword/:token").put( resetPassword); 22 | router.route("/logout").post(logout); 23 | router.route("/me").post( isAuthenticatedUser, getMyProfile); 24 | router.route("/checkenroll").post( isAuthenticatedUser, checkEnroll); 25 | router.route("/enrollcourse").post( isAuthenticatedUser, getEnrollCourse); 26 | router.route("/createreview").post(isAuthenticatedUser,createReview); 27 | 28 | 29 | // Payment 30 | router.route("/payment").post( isAuthenticatedUser, completePayment); 31 | 32 | 33 | // change password from profile 34 | router.route("/changepassword").put( isAuthenticatedUser, changePassword); 35 | router.route("/updateprofile").put( isAuthenticatedUser, updateProfile); 36 | router.route("/updateprofilepicture").put(singleUpload, isAuthenticatedUser, updateProfilePicture); 37 | router.route("/addtocart").post( isAuthenticatedUser, addToCart); 38 | router.route("/getcartcourses").post( isAuthenticatedUser, getCartCourse); 39 | router.route("/removefromcart").delete( isAuthenticatedUser, removeFromCart); 40 | 41 | //get all user data -admin`` 42 | router.route("/admin/users").post(isAuthenticatedAdmin,getAllUsers); 43 | router.route("/admin/me").post( isAuthenticatedAdmin, getMyProfile); 44 | router.route("/admin/payments").post(isAuthenticatedAdmin,getAllPayments); 45 | router.route("/admin/logout").post(Adminlogout); 46 | router.route("/admin/review").delete(deleteReview); 47 | router.route("/admin/controluser/:id").post( isAuthenticatedAdmin, activeDeactiveUser); 48 | router.route("/admin/users/:id") 49 | .put(isAuthenticatedAdmin,updateUserRole) 50 | .delete(deleteUser) 51 | 52 | // instructor 53 | router.route("/instructor/login").post(Instructorlogin); 54 | router.route("/instructor/register").post(singleUpload,registerInstructor); 55 | router.route("/instructor/me").post( isAuthenticatedInstructor, getMyProfile); 56 | router.route("/instructor/payments").get(isAuthenticatedInstructor,getInstructorPayments); 57 | router.route("/instructor/logout").post(Instructorlogout); 58 | 59 | module.exports = router; -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | PORT = 8000 2 | DB_URL = "mongodb://localhost:27017/Elearning" 3 | JWT_SECRET = [ENTER JWT_SECRET] 4 | JWT_EXPIRE = 5d 5 | COOKIE_EXPIRE = 5 6 | SMPT_MAIL= [SMPT_MAIL_ID] 7 | SMPT_PASSWORD= [SMPT_PASSWORD] 8 | SMPT_HOST=[SMPT_MAIL_HOST] 9 | SMPT_PORT=[SMPT_MAIL_PORT] 10 | 11 | CLOUDINARY_NAME = [ENTER CLOUDINARY_NAME] 12 | CLOUDINARY_API_KEY = [ENTER CLOUDINARY_API_KEY] 13 | CLOUDINARY_API_SECRET = [ENTER CLOUDINARY_API_SECRET] 14 | 15 | FRONTEND_URL="http://localhost:3000" -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const app = require('./app.js'); 2 | const connectDatabase = require("./db_connect/database") 3 | const cloudinary = require("cloudinary") 4 | 5 | // Handling Uncaught exception 6 | process.on("uncaughtException",err=>{ 7 | console.log(`error: ${err.message}`); 8 | 9 | console.log("shutting down the server due to Uncaught exception"); 10 | process.exit(1); 11 | }); 12 | 13 | // connect database 14 | connectDatabase(); 15 | 16 | cloudinary.v2.config({ 17 | cloud_name:process.env.CLOUDINARY_NAME, 18 | api_key:process.env.CLOUDINARY_API_KEY, 19 | api_secret:process.env.CLOUDINARY_API_SECRET, 20 | }); 21 | 22 | app.get('/',(req,res)=>{ 23 | res.send(`WELCOME TO BUSILEARN BACKEND, FRONTEND IS LIVE ON ${process.env.FRONTEND_URL}`); 24 | }); 25 | 26 | 27 | const server = app.listen(8000,()=>{ 28 | console.log('server runing on 8000'); 29 | }) 30 | 31 | // unhandled Promise Rejection 32 | process.on('unhandledRejection',err=>{ 33 | console.log(`error: ${err.message}`); 34 | console.log("shutting down the server due to unhadled promise rejection"); 35 | 36 | server.close(()=>{ 37 | process.exit(1); 38 | }); 39 | }); -------------------------------------------------------------------------------- /utils/apifeatures.js: -------------------------------------------------------------------------------- 1 | class ApiFeatures { 2 | constructor(query,querystr){ 3 | this.query = query; 4 | this.querystr = querystr; 5 | } 6 | 7 | search(){ 8 | const keyword = this.querystr.keyword ? { 9 | title:{ 10 | $regex:this.querystr.keyword, 11 | $options: "i", 12 | } 13 | } : {}; 14 | 15 | this.query = this.query.find({ ...keyword }); 16 | return this; 17 | } 18 | 19 | filter(){ 20 | const querycopy = {...this.querystr}; 21 | 22 | // remove some fields for category 23 | const removefields = ["keyword","page","limit"]; 24 | 25 | removefields.forEach(key=>delete querycopy[key]); 26 | 27 | // filter for price and rating 28 | let querystr = JSON.stringify(querycopy); 29 | querystr = querystr.replace(/\b(gt|gte|lt|lte)\b/g, (key) => `$${key}`); 30 | 31 | // add filter for active field 32 | const activeFilter = { active: true }; 33 | const filterObj = JSON.parse(querystr); 34 | this.query = this.query.find({ ...filterObj, ...activeFilter }); 35 | return this; 36 | 37 | } 38 | 39 | pagination(resultPerPage){ 40 | const currentPage = Number(this.querystr.page) || 1; 41 | 42 | const skip = resultPerPage * (currentPage - 1) 43 | 44 | this.query = this.query.limit(resultPerPage).skip(skip); 45 | 46 | return this 47 | } 48 | } 49 | 50 | module.exports = ApiFeatures -------------------------------------------------------------------------------- /utils/datauri.js: -------------------------------------------------------------------------------- 1 | const dataUriParser = require('datauri/parser.js'); 2 | const path = require('path'); 3 | 4 | const getDataUri = (file)=>{ 5 | const parser = new dataUriParser(); 6 | const extName = path.extname(file.originalname).toString(); 7 | 8 | return parser.format(extName,file.buffer); 9 | } 10 | 11 | module.exports = getDataUri; -------------------------------------------------------------------------------- /utils/errorHander.js: -------------------------------------------------------------------------------- 1 | class ErrorHander extends Error{ 2 | constructor(message,statusCode){ 3 | super(message); 4 | this.statusCode = statusCode; 5 | 6 | Error.captureStackTrace(this,this.constructor) 7 | } 8 | } 9 | 10 | 11 | module.exports = ErrorHander; -------------------------------------------------------------------------------- /utils/jwtToken.js: -------------------------------------------------------------------------------- 1 | // createing token and saving in cookie 2 | 3 | const sendToken = (user,statusCode,res,message,tokenname = "token")=>{ 4 | const token = user.getJWTToken(); 5 | 6 | // opction for cookie 7 | const options = { 8 | expiredate : new Date(Date.now() + process.env.COOKIE_EXPIRE * 24 * 60 * 60 * 1000), 9 | httpOnly: false, 10 | secure:false, // change to true when deploy on https 11 | // sameSite:'none' 12 | } 13 | 14 | 15 | 16 | res.status(statusCode).cookie(tokenname,token,options).json({ 17 | success:true, 18 | token, 19 | message, 20 | user 21 | }); 22 | } 23 | 24 | module.exports = sendToken; -------------------------------------------------------------------------------- /utils/sendEmail.js: -------------------------------------------------------------------------------- 1 | const nodeMailer = require('nodemailer'); 2 | 3 | const sendEmail = async (Option) => { 4 | const transporter = nodeMailer.createTransport({ 5 | host:process.env.SMPT_HOST, 6 | port:process.env.SMPT_PORT, 7 | auth:{ 8 | user:process.env.SMPT_MAIL, 9 | pass:process.env.SMPT_PASSWORD 10 | } 11 | }) 12 | 13 | const mailOption = { 14 | from:"busilearn@gmail.com", 15 | to:Option.email, 16 | subject:Option.subject, 17 | text:Option.message, 18 | } 19 | await transporter.sendMail(mailOption); 20 | }; 21 | 22 | 23 | module.exports = sendEmail; --------------------------------------------------------------------------------