diff --git a/config/database.js b/config/database.js index 24102b6d5..dd760e19d 100644 --- a/config/database.js +++ b/config/database.js @@ -1,19 +1,30 @@ +// Import the mongoose library to interact with MongoDB const mongoose = require("mongoose"); +// Define an asynchronous function to connect to MongoDB const connectDB = async () => { try { + // Attempt to connect to the database using the connection string from the .env file const conn = await mongoose.connect(process.env.DB_STRING, { + // Use the new URL parser to handle MongoDB connection strings useNewUrlParser: true, + // Use the new unified topology engine for better server discovery and monitoring useUnifiedTopology: true, + // Avoid deprecation warning for findOneAndUpdate() and similar methods useFindAndModify: false, + // Avoid deprecation warning for ensureIndex() by using createIndex() instead useCreateIndex: true, }); + // Log a message if the connection is successful console.log(`MongoDB Connected: ${conn.connection.host}`); } catch (err) { + // Log any connection errors console.error(err); + // Exit the process with failure code if connection fails process.exit(1); } }; +// Export the connectDB function so it can be used in server.js or other files module.exports = connectDB; diff --git a/config/passport.js b/config/passport.js index 6c058d1b8..996580694 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,41 +1,59 @@ +// Import the LocalStrategy constructor from the passport-local module const LocalStrategy = require("passport-local").Strategy; +// Import mongoose to interact with the MongoDB database const mongoose = require("mongoose"); +// Import the User model which contains schema and methods for authentication const User = require("../models/User"); +// Export a function to configure Passport.js module.exports = function (passport) { + // Configure the local strategy for username/password authentication passport.use( - new LocalStrategy({ usernameField: "email" }, (email, password, done) => { - User.findOne({ email: email.toLowerCase() }, (err, user) => { - if (err) { - return done(err); - } - if (!user) { - return done(null, false, { msg: `Email ${email} not found.` }); - } - if (!user.password) { - return done(null, false, { - msg: - "Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.", - }); - } - user.comparePassword(password, (err, isMatch) => { + new LocalStrategy( + { usernameField: "email" }, // Use "email" instead of default "username" + (email, password, done) => { + // Find the user in the database by email (converted to lowercase) + User.findOne({ email: email.toLowerCase() }, (err, user) => { if (err) { - return done(err); + return done(err); // Return any DB error + } + if (!user) { + // No user found with that email + return done(null, false, { msg: `Email ${email} not found.` }); } - if (isMatch) { - return done(null, user); + if (!user.password) { + // User exists but doesn't have a local password set (used a third-party login) + return done(null, false, { + msg: + "Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.", + }); } - return done(null, false, { msg: "Invalid email or password." }); + + // If user found, compare the entered password with the hashed one in DB + user.comparePassword(password, (err, isMatch) => { + if (err) { + return done(err); // Return error if comparison fails + } + if (isMatch) { + // Password matches, login successful + return done(null, user); + } + // Password does not match + return done(null, false, { msg: "Invalid email or password." }); + }); }); - }); - }) + } + ) ); + // Serialize the user ID to store in session cookie passport.serializeUser((user, done) => { - done(null, user.id); + done(null, user.id); // Store only the user's ID in the session }); + // Deserialize the user based on ID stored in the session passport.deserializeUser((id, done) => { + // Look up the user in the database by their ID User.findById(id, (err, user) => done(err, user)); }); }; diff --git a/controllers/auth.js b/controllers/auth.js index 43f893aed..eecee98f7 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -1,7 +1,8 @@ -const passport = require("passport"); -const validator = require("validator"); -const User = require("../models/User"); +const passport = require("passport"); // Passport.js for authentication +const validator = require("validator"); // To validate user input like email, password +const User = require("../models/User"); // Your User model for database interaction +// Render login page if user is not logged in, else redirect to profile exports.getLogin = (req, res) => { if (req.user) { return res.redirect("/profile"); @@ -11,43 +12,56 @@ exports.getLogin = (req, res) => { }); }; +// Handle login POST request with validation and passport authentication exports.postLogin = (req, res, next) => { const validationErrors = []; + + // Validate email format if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: "Please enter a valid email address." }); + + // Ensure password is not blank if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: "Password cannot be blank." }); + // If validation errors exist, flash errors and redirect to login page if (validationErrors.length) { req.flash("errors", validationErrors); return res.redirect("/login"); } + + // Normalize the email to a standard format req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false, }); + // Authenticate using Passport local strategy passport.authenticate("local", (err, user, info) => { if (err) { return next(err); } if (!user) { + // If authentication fails, flash error messages and redirect req.flash("errors", info); return res.redirect("/login"); } + // Log in the user if authentication is successful req.logIn(user, (err) => { if (err) { return next(err); } + // Flash success message and redirect to originally requested page or profile req.flash("success", { msg: "Success! You are logged in." }); res.redirect(req.session.returnTo || "/profile"); }); })(req, res, next); }; +// Log out the user, destroy session and redirect to homepage exports.logout = (req, res) => { req.logout(() => { console.log('User has logged out.') - }) + }); req.session.destroy((err) => { if (err) console.log("Error : Failed to destroy the session during logout.", err); @@ -56,6 +70,7 @@ exports.logout = (req, res) => { }); }; +// Render signup page if user is not logged in, else redirect to profile exports.getSignup = (req, res) => { if (req.user) { return res.redirect("/profile"); @@ -65,31 +80,43 @@ exports.getSignup = (req, res) => { }); }; +// Handle signup POST request with validation, user creation, and login exports.postSignup = (req, res, next) => { const validationErrors = []; + + // Validate email format if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: "Please enter a valid email address." }); + + // Validate password length (min 8 characters) if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: "Password must be at least 8 characters long", }); + + // Confirm password and confirmPassword match if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: "Passwords do not match" }); + // If validation errors exist, flash errors and redirect to signup page if (validationErrors.length) { req.flash("errors", validationErrors); return res.redirect("../signup"); } + + // Normalize email req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false, }); + // Create a new user instance const user = new User({ userName: req.body.userName, email: req.body.email, password: req.body.password, }); + // Check if user with same email or username already exists User.findOne( { $or: [{ email: req.body.email }, { userName: req.body.userName }] }, (err, existingUser) => { @@ -97,15 +124,18 @@ exports.postSignup = (req, res, next) => { return next(err); } if (existingUser) { + // If user exists, flash error and redirect to signup page req.flash("errors", { msg: "Account with that email address or username already exists.", }); return res.redirect("../signup"); } + // Save the new user to database user.save((err) => { if (err) { return next(err); } + // Automatically log in the newly registered user req.logIn(user, (err) => { if (err) { return next(err); diff --git a/controllers/posts.js b/controllers/posts.js index a3e2dab5d..1edb0a9a3 100644 --- a/controllers/posts.js +++ b/controllers/posts.js @@ -1,75 +1,92 @@ -const cloudinary = require("../middleware/cloudinary"); -const Post = require("../models/Post"); +const cloudinary = require("../middleware/cloudinary"); // Cloudinary middleware for image upload & deletion +const Post = require("../models/Post"); // Post model to interact with posts collection in DB module.exports = { + // Get all posts created by the logged-in user and render profile page getProfile: async (req, res) => { try { - const posts = await Post.find({ user: req.user.id }); - res.render("profile.ejs", { posts: posts, user: req.user }); + const posts = await Post.find({ user: req.user.id }); // Find posts by user ID + res.render("profile.ejs", { posts: posts, user: req.user }); // Render profile with posts and user data } catch (err) { console.log(err); } }, + + // Get all posts for the global feed, sorted by newest first, and render feed page getFeed: async (req, res) => { try { - const posts = await Post.find().sort({ createdAt: "desc" }).lean(); - res.render("feed.ejs", { posts: posts }); + const posts = await Post.find().sort({ createdAt: "desc" }).lean(); // Find all posts, sorted descending + res.render("feed.ejs", { posts: posts }); // Render feed with posts } catch (err) { console.log(err); } }, + + // Get a single post by its ID and render the post details page getPost: async (req, res) => { try { - const post = await Post.findById(req.params.id); - res.render("post.ejs", { post: post, user: req.user }); + const post = await Post.findById(req.params.id); // Find post by ID from URL param + res.render("post.ejs", { post: post, user: req.user }); // Render post page with post and user info } catch (err) { console.log(err); } }, + + // Create a new post with image upload via Cloudinary and save to database createPost: async (req, res) => { try { - // Upload image to cloudinary + // Upload image to Cloudinary using the file path from multer const result = await cloudinary.uploader.upload(req.file.path); + // Create new post document in MongoDB with Cloudinary image details await Post.create({ title: req.body.title, - image: result.secure_url, - cloudinaryId: result.public_id, + image: result.secure_url, // Cloudinary URL for the uploaded image + cloudinaryId: result.public_id, // Cloudinary's public ID (needed for deletion) caption: req.body.caption, - likes: 0, - user: req.user.id, + likes: 0, // Initialize likes count to zero + user: req.user.id, // Associate post with logged-in user }); + console.log("Post has been added!"); - res.redirect("/profile"); + res.redirect("/profile"); // Redirect back to profile page after post creation } catch (err) { console.log(err); } }, + + // Increment the like count of a post by 1 likePost: async (req, res) => { try { await Post.findOneAndUpdate( - { _id: req.params.id }, + { _id: req.params.id }, // Find the post by ID from URL param { - $inc: { likes: 1 }, + $inc: { likes: 1 }, // Increment likes field by 1 } ); console.log("Likes +1"); - res.redirect(`/post/${req.params.id}`); + res.redirect(`/post/${req.params.id}`); // Redirect back to the post page } catch (err) { console.log(err); } }, + + // Delete a post and its associated image from Cloudinary and database deletePost: async (req, res) => { try { - // Find post by id + // Find the post by ID let post = await Post.findById({ _id: req.params.id }); - // Delete image from cloudinary + + // Delete the image from Cloudinary using its public ID await cloudinary.uploader.destroy(post.cloudinaryId); - // Delete post from db + + // Remove the post from MongoDB await Post.remove({ _id: req.params.id }); + console.log("Deleted Post"); - res.redirect("/profile"); + res.redirect("/profile"); // Redirect back to profile after deletion } catch (err) { + // On error, still redirect back to profile res.redirect("/profile"); } },