├── .gitignore ├── package.json ├── routes ├── contributors.js ├── logout.js ├── youtube.js ├── students.js └── login.js ├── config └── dbConn.js ├── models ├── student.model.js ├── login.model.js └── contributor.model.js ├── server.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "nodemon server.js", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "Ramy Benkov", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.4.5", 17 | "express": "^4.21.1", 18 | "express-session": "^1.18.1", 19 | "googleapis": "^144.0.0", 20 | "mongoose": "^8.7.2", 21 | "morgan": "^1.10.0" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^3.1.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /routes/contributors.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import Contributor from '../models/contributor.model.js'; 3 | 4 | const contributorRouter = express.Router(); 5 | 6 | // Route to get all contributors 7 | contributorRouter.get('/', async (req, res) => { 8 | try { 9 | // Retrieve and return a list of active contributors, sorted by name in ascending order 10 | const contributors = await Contributor.find({ isActive: true }).sort('name'); 11 | res.json(contributors); 12 | } catch (error) { 13 | res.status(500).json({ message: 'Server error', error: error.message }); 14 | } 15 | }); 16 | 17 | export default contributorRouter; -------------------------------------------------------------------------------- /config/dbConn.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // This function connects to the MongoDB database using Mongoose. 4 | // It includes error handling to manage any connection issues that may arise. 5 | export const connectDB = async () => { 6 | try { 7 | // Set strictQuery option for Mongoose (to only the fields defined in the schema) 8 | mongoose.set("strictQuery", true); 9 | 10 | // Connect to MongoDB database 11 | const conn = await mongoose.connect(process.env.MONGODB_URI); 12 | console.log(`MongoDB connected: ${conn.connection.host}`); 13 | } catch (error) { 14 | console.error(`error:${error.message}`); 15 | process.exit(1); // Process code 1 means exit with failure, 0 means success 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /routes/logout.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | // Create a new router for handling logout requests 4 | const logoutRouter = express.Router(); 5 | 6 | // Define a POST route for logging out 7 | logoutRouter.post('/', (req, res) => { 8 | // Attempt to terminate the user's session 9 | req.session.destroy((err) => { 10 | if (err) { // If there is an error during session destruction 11 | console.error('Logout error:', err); 12 | return res.status(500).json({ message: 'Could not log out, please try again' }); 13 | 14 | }// Clear the session cookie from the user's browser 15 | res.clearCookie('connect.sid'); 16 | return res.json({ message: 'Logout successful' }); 17 | }); 18 | }); 19 | 20 | // Export the logout router for use in other parts of the application 21 | export default logoutRouter; -------------------------------------------------------------------------------- /models/student.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // Schema for students 4 | const studentSchema = new mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: true, 9 | trim: true, // Removes whitespace from the beginning and end 10 | }, 11 | email: { 12 | type: String, 13 | required: true, 14 | unique: true, // Ensures each email is unique 15 | trim: true, 16 | lowercase: true, 17 | }, 18 | password: { 19 | type: String, 20 | required: true, 21 | minlength: 6, 22 | }, 23 | age: { 24 | type: Number, 25 | required: true, 26 | min: 0, // Ensures age is a positive number 27 | }, 28 | }, 29 | { timestamps: true } // Automatically manage 'createdAt' and 'updatedAt' fields 30 | ); 31 | 32 | // Indexing for email 33 | studentSchema.index({ email: 1 }); // Index on email for faster lookup 34 | 35 | // Create a model for students 36 | const Student = mongoose.model("Student", studentSchema, "students"); 37 | 38 | // Export the model for use in other files 39 | export default Student; -------------------------------------------------------------------------------- /models/login.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // Schema for admin login 4 | const loginSchema = new mongoose.Schema( 5 | { 6 | userName: { 7 | type: String, 8 | required: true, 9 | unique: true, 10 | trim: true, // Removes any white space from the beginning and end of the username preventing issues with spaces 11 | }, 12 | password: { 13 | type: String, 14 | required: true, 15 | minlength: 6, 16 | }, 17 | name: { 18 | type: String, 19 | required: true, 20 | trim: true, // Remove extra spaces 21 | }, 22 | picture: { 23 | type: String, 24 | required: true, 25 | }, 26 | isActive: { 27 | // To check whether the admin account is still active (still has access) 28 | type: Boolean, 29 | default: true, 30 | }, 31 | }, 32 | { timestamps: true } // Enable timestamps to automatically manage 'createdAt' and 'updatedAt' fields 33 | ); 34 | 35 | // Index to optimize queries by userName and isActive 36 | loginSchema.index({ userName: 1, isActive: 1 }); // Compound index 37 | 38 | // Create a model for the admin login 39 | const Login = mongoose.model("Login", loginSchema, "adminLogin"); 40 | 41 | // Export the model for use in other files 42 | export default Login; -------------------------------------------------------------------------------- /models/contributor.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // Schema for contributors 4 | export const contributorSchema = new mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: true, 9 | trim: true, // Remove extra spaces 10 | }, 11 | picture: { 12 | type: String, // Stores the URL of the contributor's image as a string, since URLs are represented as text 13 | required: true, 14 | }, 15 | jobTitle: { 16 | type: String, 17 | required: true, 18 | trim: true, 19 | }, 20 | bio: { 21 | type: String, 22 | trim: true, 23 | }, 24 | isActive: { 25 | // To check if the contributor is still active 26 | type: Boolean, 27 | default: true, 28 | }, 29 | }, 30 | { timestamps: true }// Enable timestamps to automatically manage 'createdAt' and 'updatedAt' fields. 31 | 32 | 33 | ); 34 | 35 | // Indexes 36 | contributorSchema.index({ name: 1 }); 37 | contributorSchema.index({ jobTitle: 1 }); 38 | contributorSchema.index({ isActive: 1 }); 39 | contributorSchema.index({ isActive: 1, name: 1 }); // Compound index for active contributors by name 40 | 41 | // Create a model for contributors 42 | const Contributor = mongoose.model("Contributor", contributorSchema, "contributors"); 43 | 44 | export default Contributor; // Export the model for use in other files 45 | -------------------------------------------------------------------------------- /routes/youtube.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { google } from 'googleapis'; 3 | 4 | const youtubeRouter = express.Router(); // Create router for YouTube API routes 5 | const youtube = google.youtube('v3'); // Initialize YouTube API client 6 | 7 | // GET route to fetch videos 8 | youtubeRouter.get('/videos', async (req, res) => { 9 | const keyword = req.query.keyword; // Get keyword from the search query 10 | const maxResults = req.query.maxResults || 10; 11 | 12 | try { 13 | let response; 14 | 15 | if (keyword) { 16 | // Search for videos based on the keyword 17 | response = await youtube.search.list({ 18 | part: 'snippet', 19 | maxResults: maxResults, 20 | q: keyword, // Search query using the keyword 21 | type: 'video', 22 | order: 'relevance', // Sort by relevance to the keyword 23 | key: process.env.YOUTUBE_API_KEY, 24 | }); 25 | } else { 26 | // Default to fetching the latest videos from the freeCodeCamp channel 27 | response = await youtube.search.list({ 28 | part: 'snippet', 29 | maxResults: maxResults, 30 | channelId: 'UC8butISFwT-Wl7EV0hUK0BQ', // freeCodeCamp channel ID 31 | type: 'video', 32 | order: 'date', // Sort by the newest videos 33 | key: process.env.YOUTUBE_API_KEY, 34 | }); 35 | } 36 | 37 | res.json(response.data); // Send fetched video data as JSON 38 | } catch (error) { 39 | console.error("YouTube API error:", error); 40 | res.status(500).json({ error: error.message }); // Send error message if the API call fails 41 | } 42 | }); 43 | 44 | // Export the router for use in other files 45 | export default youtubeRouter; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import cors from "cors"; 4 | import { connectDB } from "./config/dbConn.js"; 5 | import contributorRouter from './routes/contributors.js'; 6 | import youtubeRouter from './routes/youtube.js'; 7 | import loginRouter from './routes/login.js'; 8 | import session from "express-session"; 9 | import studentRouter from './routes/students.js'; 10 | import logoutRouter from './routes/logout.js'; 11 | 12 | 13 | // Load environment variable from .env file 14 | dotenv.config(); 15 | 16 | 17 | // Create an Express application 18 | const app = express(); 19 | 20 | // Define PORT variable from environment variables, default to 4000 21 | const PORT = process.env.PORT || 4000; 22 | 23 | // Middleware to parse JSON bodies 24 | app.use(express.json()); 25 | 26 | // Enable CORS 27 | app.use(cors({ 28 | origin: 'http://localhost:5173', // my frontend URL 29 | credentials: true // Allow credentials 30 | })); 31 | 32 | // Configure and use express-session 33 | app.use(session({ 34 | secret: process.env.SESSION_SECRET || 'skillstream2024', 35 | resave: false, 36 | saveUninitialized: true, 37 | cookie: { secure: false } // Set to true in production if using HTTPS 38 | })); 39 | 40 | // Use the contributors route prefixed with /api 41 | app.use('/api/contributors', contributorRouter); 42 | app.use('/api/youtube', youtubeRouter); 43 | app.use('/api/login', loginRouter); 44 | app.use('/api/students', studentRouter); 45 | app.use('/api/logout', logoutRouter); 46 | 47 | 48 | // Error handling middleware 49 | app.use((err, req, res, next) => { 50 | console.error(err.stack); 51 | res.status(500).send('Something broke!'); 52 | }); 53 | 54 | // Connect to database and start the server 55 | connectDB(); 56 | app.listen(PORT, () => { 57 | console.log(`Server is running on port: ${PORT}`); 58 | }); -------------------------------------------------------------------------------- /routes/students.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import Student from '../models/student.model.js'; 3 | 4 | // Create a new router instance for handling student-related routes 5 | const studentRouter = express.Router(); 6 | 7 | // Route to get all students 8 | studentRouter.get('/', async (req, res) => { 9 | try { 10 | const students = await Student.find({}); // Fetch all students from the database 11 | res.json(students); 12 | } catch (error) { 13 | res.status(500).json({ message: 'Error fetching students', error: error.message }); 14 | } 15 | }); 16 | 17 | // Route to add a new student; create and save to the database 18 | studentRouter.post('/', async (req, res) => { 19 | try { 20 | const newStudent = new Student(req.body); 21 | await newStudent.save(); 22 | res.status(201).json(newStudent); 23 | } catch (error) { 24 | res.status(400).json({ message: 'Error creating student', error: error.message }); 25 | } 26 | }); 27 | 28 | // Route to update an existing student by ID 29 | studentRouter.put('/:id', async (req, res) => { 30 | try { 31 | const updatedStudent = await Student.findByIdAndUpdate( 32 | req.params.id, // Find the student by ID 33 | req.body, // Update with the data provided in request body 34 | { new: true } // Return the updated student document 35 | ); 36 | // Respond with the updated student data 37 | res.json(updatedStudent); 38 | } catch (error) { 39 | res.status(400).json({ message: 'Error updating student', error: error.message }); 40 | } 41 | }); 42 | 43 | // Route to delete a student by ID 44 | studentRouter.delete('/:id', async (req, res) => { 45 | try { 46 | // Find and delete the student by ID 47 | await Student.findByIdAndDelete(req.params.id); 48 | res.json({ message: 'Student deleted successfully' }); 49 | } catch (error) { 50 | res.status(400).json({ message: 'Error deleting student', error: error.message }); 51 | } 52 | }); 53 | 54 | // Export the studentRouter to use in other parts of the application 55 | export default studentRouter; -------------------------------------------------------------------------------- /routes/login.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import Login from "../models/login.model.js"; 3 | 4 | // Create a new router instance for login-related routes 5 | const loginRouter = express.Router(); 6 | 7 | // Define a POST route at '/' for handling user login requests 8 | loginRouter.post("/", async (req, res) => { 9 | console.log("Login attempt:", req.body); // Log details of the login attempt for debugging 10 | const { userName, password } = req.body; // Extract username and password from request body 11 | 12 | try { 13 | // Search for a user in the database with the provided username 14 | const user = await Login.findOne({ userName, isActive: true }); 15 | console.log("Database user:", user); // Log the retrieved user data for debugging 16 | 17 | // Log input and database credentials to verify the data being compared 18 | console.log("Comparing:"); 19 | console.log("Input userName:", userName); 20 | console.log("Input password:", password); 21 | console.log("DB userName:", user?.userName); 22 | console.log("DB password:", user?.password); 23 | 24 | // Check if the user exists and password matches 25 | if (!user || user.password !== password) { 26 | console.log("Invalid credentials"); 27 | return res.status(401).json({ message: "Invalid username or password" }); 28 | } 29 | 30 | // Save the logged-in user's ID in the session for tracking and maintaining login status 31 | req.session.userId = user._id; 32 | 33 | // Log the session details to verify the session data after login 34 | console.log("Session after login:", req.session); 35 | 36 | // Respond with a success status (200) and include the user's ID to confirm login was successful 37 | res.status(200).json({ message: "Login successful", userId: user._id }); 38 | } catch (error) { 39 | console.error("Error during login:", error); 40 | res.status(500).json({ message: "Internal server error" }); // Respond with a 500 status code for server errors 41 | } 42 | }); 43 | // Export the loginRouter to use in other parts of the application 44 | export default loginRouter; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkillStream Backend 2 | 3 | This is the backend server for the SkillStream project, a platform for online learning and skill development. 4 | 5 | ## Features 6 | 7 | - RESTful API built with Node.js and Express 8 | - MongoDB database integration using Mongoose 9 | - YouTube API integration for fetching educational content 10 | - User authentication system with session management 11 | - CRUD operations for students 12 | - Contributor management 13 | 14 | ## API Endpoints 15 | 16 | - GET `/api/contributors`: Fetch all active contributors 17 | - GET `/api/youtube/videos`: Fetch YouTube videos (with optional keyword search) 18 | - POST `/api/login`: User login 19 | - GET `/api/students`: Fetch all students 20 | - POST `/api/students`: Add a new student 21 | - PUT `/api/students/:id`: Update a student 22 | - DELETE `/api/students/:id`: Delete a student 23 | - POST `/api/logout`: User logout 24 | 25 | ## Technologies Used 26 | 27 | - Node.js 28 | - Express.js 29 | - MongoDB 30 | - Mongoose 31 | - YouTube Data API v3 32 | - express-session for session management 33 | - dotenv for environment variable management 34 | - cors for Cross-Origin Resource Sharing 35 | 36 | ## Future Improvements 37 | 38 | - Enhance security features (e.g., password hashing) 39 | - Implement user roles and permissions 40 | - Add more CRUD operations for courses 41 | - Implement email verification for user registration 42 | 43 | ## Resources 44 | 45 | - [MERN Stack Tutorial with Deployment – Beginner's Course](https://www.youtube.com/watch?v=O3BUHwfHf84) 46 | - [Fetching All Videos of a channel | Youtube Data API V3](https://www.youtube.com/watch?v=DuudSp4sHmg) 47 | - [How to Get YouTube API Key 2024](https://www.youtube.com/watch?v=LLAZUTbc97I) 48 | - [Add YouTube functionality to your app](https://developers.google.com/youtube/v3) 49 | - [YouTube Data API Tutorial - Search for Videos](https://www.youtube.com/watch?app=desktop&v=QY8dhl1EQfI) 50 | - [Register and Login Tutorial | ReactJS, NodeJS, MySQL - Cookies, Sessions, Hashing](https://www.youtube.com/watch?v=sTHWNPVNvm8&t=381s) 51 | - [Cookie and Session (II): How session works in express-session](https://medium.com/@alysachan830/cookie-and-session-ii-how-session-works-in-express-session-7e08d102deb8) 52 | - [Session Secret Value Error](https://forum.freecodecamp.org/t/session-secret-value-error/457249) 53 | - [Learn w/ Leon & Friends] (Discord Channel) 54 | 55 | ## Thanks 56 | 57 | I would like to express my deepest gratitude to: 58 | 59 | Per Scholas for providing me with this incredible opportunity to learn and grow as a developer. This transformative experience has opened new doors and possibilities in my tech career. 60 | 61 | Kevin Dang, my classmate and friend, for his constant support and assistance throughout this project and many other projects. Your willingness to help whenever needed made a huge difference. 62 | 63 | My instructors Colton and Abraham, whose expertise, patience, and dedication to teaching have been instrumental in my growth as a developer. Thank you for sharing your knowledge and guiding me through this journey. 64 | 65 | All my classmates who have been an incredible source of support, inspiration, and collaboration throughout this program. Your camaraderie and willingness to help one another has created a truly enriching learning environment. 66 | 67 | And special thanks to my dear friend of 28 years overseas, whose encouragement and help throughout my learning journey and especially with this project has been invaluable. 68 | 69 | Your support made this possible. Thank you! --------------------------------------------------------------------------------