Skip to content
Open

02 #11

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6b6dc7b
first commit
Mar 27, 2026
73bd601
createStudent controller function added
Mar 27, 2026
f23a110
student.json to student.js
Mar 27, 2026
0802a15
from node run dev to nodemon
Mar 27, 2026
2db9224
added "03" branch changes
Mar 27, 2026
17321b2
rename studentRoute to studentsRoute
Mar 27, 2026
720b829
fixed variable typo
Mar 27, 2026
f95116a
fix missing import, infinite loop, and typo on console.log
Mar 27, 2026
4e2d6a3
uncommented the middleware part
Mar 27, 2026
b40f917
index.js removed from solut
Mar 27, 2026
c5fe291
updateStudent Service added
Mar 27, 2026
eb9b025
deleteStudent Service added
Mar 27, 2026
08f12a2
updateStudent added to controller
Mar 27, 2026
87b1038
deleteStudent service updated to return deleted student
Mar 27, 2026
1ae151c
deleteStudent controller aded
Mar 27, 2026
2a4059a
routes added for update and delete Student
Mar 27, 2026
cb392b7
Routes + comm cleaned
Apr 30, 2026
d9fcea2
mongoDB connection
Apr 30, 2026
0f288d6
loginStudent function
Apr 30, 2026
cc3f558
authentication middleware
Apr 30, 2026
11a4cc0
logger middleware
Apr 30, 2026
eb96be5
multer config middleware
Apr 30, 2026
cd3b753
validate Student format + values
Apr 30, 2026
01dbdc3
User Model instead of base one
Apr 30, 2026
b5cf0a2
routes call to middlewares
Apr 30, 2026
9f7331a
MongoDB Student services
Apr 30, 2026
98fa17c
Service moved to mongo db file
Apr 30, 2026
d200f01
Course Model with Mongoose
May 4, 2026
640bac4
Course Controller
May 4, 2026
860e467
Course Routes CRUD
May 4, 2026
570a72c
Course Service
May 4, 2026
216c626
course path added
May 4, 2026
3cb4b62
Typo fix
May 4, 2026
aaadc97
Package reinstalled
May 4, 2026
f674c6e
async fix (forgot to commit sry sir)
May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import mongoose from "mongoose";
import "dotenv/config";

// TODO: move connection string to .env and load with dotenv
const MONGO_URI = process.env.MONGO_URI;

export const connectToMongoDB = async () => {
try {
await mongoose.connect(MONGO_URI);
console.log("Connected to MongoDB");
} catch (err) {
console.error("Could not connect to MongoDB", err);
process.exit(1);
}
};
55 changes: 55 additions & 0 deletions controllers/CourseController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
findAllCourses,
findCourseById,
createCourseService,
updateCourseService,
deleteCourseService,
} from "../services/CourseService.js"

export const getAllCourses = async (req, res) => {
try {
const courses = await findAllCourses()
res.status(200).json(courses)
} catch (error) {
res.status(500).json({ message: error.message })
}
}

export const getCourseById = async (req, res) => {
try {
const course = await findCourseById(req.params.id)
if (!course) return res.status(404).json({ message: "Course not found" })
res.status(200).json(course)
} catch (error) {
res.status(500).json({ message: error.message })
}
}

export const createCourse = async (req, res) => {
try {
const newCourse = await createCourseService(req.body)
res.status(201).json(newCourse)
} catch (error) {
res.status(400).json({ message: error.message })
}
}

export const updateCourse = async (req, res) => {
try {
const updatedCourse = await updateCourseService(req.params.id, req.body)
if (!updatedCourse) return res.status(404).json({ message: "Course not found" })
res.status(200).json(updatedCourse)
} catch (error) {
res.status(500).json({ message: error.message })
}
}

export const deleteCourse = async (req, res) => {
try {
const deletedCourse = await deleteCourseService(req.params.id)
if (!deletedCourse) return res.status(404).json({ message: "Course not found" })
res.status(200).json({ message: "Course deleted successfully" })
} catch (error) {
res.status(500).json({ message: error.message })
}
}
95 changes: 95 additions & 0 deletions controllers/studentsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import jwt from "jsonwebtoken"
import { findAllStudents,
findStudentById,
createStudentService,
loginStudentService,
updateStudentService,
deleteStudentService
} from "../services/studentServiceMongoDB.js"

export const getAllStudents = async (req, res) => {
try {
const students = await findAllStudents()
const toStudentDTO = (student) => ({
id: student._id,
email: student.email,
major: student.major,
gpa: student.gpa,
})
const studentsDTO = students.map(toStudentDTO)
res.status(200).json(studentsDTO)
} catch {
res.status(404).json({message: error.message})
}
}

export const getStudentById = async (req, res) => {
const id = parseInt(req.params.id)
try {
const student = await findStudentById(id)
console.log(student)
res.status(200).json(student)
} catch (error) {
res.status(404).json({message: "Student not found"})
return
}
}

export const createStudent = async (req, res) => {
try {
const { name, email, password, gpa, major } = req.body
const newStudent = { name, email, password, gpa, major}
const loggedUser = await createStudentService(newStudent);
const token = jwt.sign({ id: loggedUser._id, forReals: true}, process.env.JWT_SECRET, {
expiresIn: "24h",
})
const toStudentDTO = (student) => ({
id: student._id,
email: student.email,
})
res.status(201).json({ token, user: toStudentDTO(loggedUser) })
} catch (error) {
res.status(500).json({ message: error })
}
}

export const updateStudent = async (req, res) => {
const id = req.params.id
try {
const updated = await updateStudentService(id, req.body)
res.status(200).json(updated)
} catch (error) {
if (error.message === "User not found") {
res.status(404).json({ message: "Student not found" })
} else {
res.status(500).json({ message: "Internal server error" })
}
}
}

export const deleteStudent = async (req, res) => {
const id = req.params.id
try {
await deleteStudentService(id)
res.status(200).json({ message: "Student deleted successfully"}) // ...so we can send it here
} catch (error) {
if (error.message === "User not found") {
res.status(404).json({ message: "Student not found" })
} else {
res.status(500).json({ message: "Internal server error" })
}
}
}

export const loginStudent = async (req, res) => {
try {
const { email, password } = req.body;
const user = await loginStudentService(email, password);
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, {
expiresIn: "24h",
});
res.status(200).json({ token, user: { id: user._id, email: user.email } });
} catch (error) {
res.status(401).json({ message: error.message });
}
}
62 changes: 24 additions & 38 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,34 @@
import express from "express" // new js
import express from "express"
import cors from "cors";

const express = require("express") // old js
import { connectToMongoDB } from "./config/db.js";
import studentRouter from "./routes/studentsRoute.js";
import courseRouter from "./routes/CourseRoute.js";
import { logger } from "./middleware/middleware.js";

// const express = require("express") // old js

const app = express()
const port = 3000

app.get("/", (req, res) => {
res.json({ msg: "Hello World!" })
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

// NODEMON

// send data to the exposed endpoints
// import data from students.json and send it to the client when they hit the endpoint
// use postman to send data to endpoints
// DB connection
connectToMongoDB();

// GET - retrieve data
// POST - create new data
// PUT - update existing data
// DELETE - remove data
// CRUD - create, read, update, delete
// middleware
app.use(express.json()); // parse JSON request body
app.use(express.urlencoded({ extended: true })); // parse URL-encoded request body
app.use(cors()); // enable CORS for all routes
app.use(express.static("public")); // serve static files

// SEND DATA vs SEND ERROR
// STATUS CODES - 200, 201, 400, 404
// JSON - JavaScript Object Notation
// res.json() - send JSON response
// res.status() - set status code
// res.send() - send response
// routes
app.use(logger);
app.use("/api/students", studentRouter);
app.use("/api/courses", courseRouter)

// TRY FRONTEND NOW before moving on
// go to FONT folder and open index.html in the browser, check console for errors, fix them, and see the data being displayed

// CORS - Cross-Origin Resource Sharing
// npm i cors
// app.use(cors())

// REFACTORING
// 1. Create a separate file for routes (e.g., routes.js)
// 2. Create a separate file for controllers (e.g., controllers.js)
// 3. Change commonJs to ES6 modules (e.g., import/export) - this will require adding "type": "module" in package.json
app.get("/", (req, res) => {
res.send("Server is running ...");
});

// push to github and share the link
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
22 changes: 22 additions & 0 deletions middleware/auth-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import jwt from "jsonwebtoken";
import dotenv from "dotenv"
dotenv.config()

export const authCheck = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: "Missing Authorization header" });
}

const token = authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "Token missing from Bearer string" });
}

const decodedToken = jwt.verify(token, process.env.JWT_SECRET);

const userId = decodedToken.userId
req.auth = { userId };

next()
}
17 changes: 17 additions & 0 deletions middleware/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const logger = (req, res, next) => {
const start = Date.now();

res.on("finish", () => {
console.table( {
timestamp: new Date().toISOString(),
method: req.method,
url: req.originalUrl,
status: res.statusCode,
responseTime: `${Date.now() - start}ms`,
ip: req.ip,
userAgent: req.headers["user-agent"],
});
});

next();
};
21 changes: 21 additions & 0 deletions middleware/multer-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import multer from "multer";

const MIME_TYPES = {
"image/jpg": "jpg",
"image/jpeg": "jpg",
"image/png": "png",
};

// where to store the files
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, "public/uploads");
},
filename: (req, file, callback) => {
const name = file.originalname.split(" ").join("_");
const extension = MIME_TYPES[file.mimetype];
callback(null, name + Date.now() + "." + extension);
},
});

export default multer({ storage: storage }).single("image");
25 changes: 25 additions & 0 deletions middleware/validateStudent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from "fs"

export const validateStudent = (req, res, next) => {
const { name, email, password, gpa, major } = req.body;

const reject = (message) => {
if (req.file) fs.unlink(req.file.path, () => {})
return res.status(400).json({message})
}

if (!name) return reject("Name is required");
if (!email) return reject("Email is required");
if (!password) return reject("Password is required");
if (!gpa) return reject("Gpa is required");
if (!major) return reject("Major is required");

if (password.length < 8) return reject("Password must be at least 8 characters");

if (gpa < 0 || gpa > 4) return reject("GPA must be between 0 and 4");

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) return reject("Email is not valid");

next();
};
27 changes: 27 additions & 0 deletions models/CourseModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import mongoose from "mongoose"

const courseSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
credits: {
type: Number,
required: true,
},
instructor: {
type: String,
required: true,
},
},
{
timestamps: true,
}
)

export default mongoose.model("Course", courseSchema)
32 changes: 32 additions & 0 deletions models/UserModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import mongoose from "mongoose";

const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
gpa: {
type: Number,
required: true,
},
password: {
type: String,
required: true,
},
major: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);

export default mongoose.model("User", userSchema);
Loading