From ff6027b5fe9343802827054172b1e75e0c73fce3 Mon Sep 17 00:00:00 2001 From: slyncrafty <101752232+slyncrafty@users.noreply.github.com> Date: Wed, 3 Sep 2025 16:13:37 +0100 Subject: [PATCH 1/3] Add Comment Feature --- config/database.js | 2 ++ config/passport.js | 10 +++++++-- controllers/auth.js | 19 +++++++++------- controllers/comments.js | 47 ++++++++++++++++++++++++++++++++++++++++ controllers/posts.js | 12 ++++++++-- middleware/auth.js | 9 +++++++- middleware/cloudinary.js | 3 ++- middleware/multer.js | 2 ++ models/Comments.js | 26 ++++++++++++++++++++++ routes/comments.js | 13 +++++++++++ routes/main.js | 1 + routes/posts.js | 1 + server.js | 2 ++ views/feed.ejs | 10 +++++---- views/post.ejs | 21 ++++++++++++++++++ 15 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 controllers/comments.js create mode 100644 models/Comments.js create mode 100644 routes/comments.js diff --git a/config/database.js b/config/database.js index 24102b6d5..8dd7d2610 100644 --- a/config/database.js +++ b/config/database.js @@ -1,5 +1,6 @@ const mongoose = require("mongoose"); +// Connect to MongoDB using connection string from env variable const connectDB = async () => { try { const conn = await mongoose.connect(process.env.DB_STRING, { @@ -16,4 +17,5 @@ const connectDB = async () => { } }; +// Export the function so it can be used in our app module.exports = connectDB; diff --git a/config/passport.js b/config/passport.js index 6c058d1b8..9dfbaff74 100644 --- a/config/passport.js +++ b/config/passport.js @@ -2,13 +2,18 @@ const LocalStrategy = require("passport-local").Strategy; const mongoose = require("mongoose"); const User = require("../models/User"); +// Export the following passport configuration functions so it can be used in our app module.exports = function (passport) { + // Configure a local authentication strategy (email/password) passport.use( + // local Strategy to use "email" new LocalStrategy({ usernameField: "email" }, (email, password, done) => { + // Find a user in the database by email User.findOne({ email: email.toLowerCase() }, (err, user) => { if (err) { return done(err); } + // if no user found, auth fails if (!user) { return done(null, false, { msg: `Email ${email} not found.` }); } @@ -18,6 +23,7 @@ module.exports = function (passport) { "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.", }); } + // Compare provided password wth hashed password in DB user.comparePassword(password, (err, isMatch) => { if (err) { return done(err); @@ -30,11 +36,11 @@ module.exports = function (passport) { }); }) ); - + // Serialize user to session (store user id) passport.serializeUser((user, done) => { done(null, user.id); }); - + // Deserialize user from session by user id passport.deserializeUser((id, done) => { User.findById(id, (err, user) => done(err, user)); }); diff --git a/controllers/auth.js b/controllers/auth.js index 43f893aed..86bc9d85b 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"); - +// Export function so app can use them + // Show login page if logged in redirect to /profile exports.getLogin = (req, res) => { if (req.user) { return res.redirect("/profile"); @@ -10,8 +11,9 @@ exports.getLogin = (req, res) => { title: "Login", }); }; - + // Handle POST login exports.postLogin = (req, res, next) => { + // validate email const validationErrors = []; if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: "Please enter a valid email address." }); @@ -22,10 +24,11 @@ exports.postLogin = (req, res, next) => { req.flash("errors", validationErrors); return res.redirect("/login"); } + // Normalize email req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false, }); - + // Use passport local strategy to authenticate and handle fail/error passport.authenticate("local", (err, user, info) => { if (err) { return next(err); @@ -43,7 +46,7 @@ exports.postLogin = (req, res, next) => { }); })(req, res, next); }; - +// Handle logout exports.logout = (req, res) => { req.logout(() => { console.log('User has logged out.') @@ -55,7 +58,7 @@ exports.logout = (req, res) => { res.redirect("/"); }); }; - +// Handle GET signup exports.getSignup = (req, res) => { if (req.user) { return res.redirect("/profile"); @@ -64,7 +67,7 @@ exports.getSignup = (req, res) => { title: "Create Account", }); }; - +// Handle POST signup exports.postSignup = (req, res, next) => { const validationErrors = []; if (!validator.isEmail(req.body.email)) @@ -83,13 +86,13 @@ exports.postSignup = (req, res, next) => { 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 email || username already exists User.findOne( { $or: [{ email: req.body.email }, { userName: req.body.userName }] }, (err, existingUser) => { diff --git a/controllers/comments.js b/controllers/comments.js new file mode 100644 index 000000000..bbfa49406 --- /dev/null +++ b/controllers/comments.js @@ -0,0 +1,47 @@ +const Comment = require("../models/Comments"); +// Export functions +module.exports = { + // Create an instance of comment for the given post + createComment: async (req, res) => { + try { + await Comment.create({ + comment: req.body.comment, + likes: 0, + post: req.params.id, + user: req.user.id, + }); + console.log("Comment has been added!"); + res.redirect("/post/"+req.params.id); + } catch (err) { + console.log(err); + } + }, + // increment the like count of the given post + likeComment: async (req, res) => { + try { + await Comment.findOneAndUpdate( + { _id: req.params.id }, + { + $inc: { likes: 1 }, + } + ); + console.log("Likes +1"); + res.redirect(`/post/${req.params.id}`); + } catch (err) { + console.log(err); + } + }, + // delete a comment by id from the given post + deleteComment: async (req, res) => { + try { + // Find Comment by id + let comment = await Comment.findById({ _id: req.params.id }); + // Delete comment from db + await Comment.remove({ _id: req.params.id }); + console.log("Deleted Comment"); + res.redirect(`/post/${req.params.id}`); + } catch (err) { + res.redirect(`/post/${req.params.id}`); + } + }, +}; diff --git a/controllers/posts.js b/controllers/posts.js index a3e2dab5d..23ffd979e 100644 --- a/controllers/posts.js +++ b/controllers/posts.js @@ -1,7 +1,9 @@ const cloudinary = require("../middleware/cloudinary"); const Post = require("../models/Post"); - +const Comment = require("../models/Comments"); +// Export functions module.exports = { + // render profile for logged in user getProfile: async (req, res) => { try { const posts = await Post.find({ user: req.user.id }); @@ -10,6 +12,7 @@ module.exports = { console.log(err); } }, + // Render feed with all posts getFeed: async (req, res) => { try { const posts = await Post.find().sort({ createdAt: "desc" }).lean(); @@ -18,14 +21,17 @@ module.exports = { console.log(err); } }, + // Render a post with its comments getPost: async (req, res) => { try { const post = await Post.findById(req.params.id); - res.render("post.ejs", { post: post, user: req.user }); + const comments = await Comment.find({post: req.params.id}).sort({ createdAt: "desc" }).populate("user", "userName").lean(); + res.render("post.ejs", { post: post, user: req.user, comments: comments }); } catch (err) { console.log(err); } }, + // Create a new post createPost: async (req, res) => { try { // Upload image to cloudinary @@ -45,6 +51,7 @@ module.exports = { console.log(err); } }, + // increment the post's like count likePost: async (req, res) => { try { await Post.findOneAndUpdate( @@ -59,6 +66,7 @@ module.exports = { console.log(err); } }, + // Delete the post deletePost: async (req, res) => { try { // Find post by id diff --git a/middleware/auth.js b/middleware/auth.js index f646630da..067b0998c 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -1,13 +1,20 @@ +// Export functions for the app module.exports = { + // Middleware to ensure the user is Authenticated. ensureAuth: function (req, res, next) { + // passport to add isAuthenticated() to req object + // protect the pages that require login if (req.isAuthenticated()) { return next(); } else { res.redirect("/"); } }, + // Middleware to ensure the user is not Authenticated(guest) ensureGuest: function (req, res, next) { - if (!req.isAuthenticated()) { + // if user is not authenticated/guest, allow access + // protect the pages that only visible to guests + if (!req.isAuthenticated()) { return next(); } else { res.redirect("/dashboard"); diff --git a/middleware/cloudinary.js b/middleware/cloudinary.js index 0960c5b6f..e54313443 100644 --- a/middleware/cloudinary.js +++ b/middleware/cloudinary.js @@ -1,11 +1,12 @@ const cloudinary = require("cloudinary").v2; require("dotenv").config({ path: "./config/.env" }); - +// cloudinary config using env variables cloudinary.config({ cloud_name: process.env.CLOUD_NAME, api_key: process.env.API_KEY, api_secret: process.env.API_SECRET, }); +// Export functions for the app module.exports = cloudinary; diff --git a/middleware/multer.js b/middleware/multer.js index c012afe58..cc2f809f0 100644 --- a/middleware/multer.js +++ b/middleware/multer.js @@ -1,3 +1,5 @@ +// Multer is a middleware for handling multipart/form-data, for uploading files. +// Multer will not process any form which is not multipart (multipart/form-data). const multer = require("multer"); const path = require("path"); diff --git a/models/Comments.js b/models/Comments.js new file mode 100644 index 000000000..23417c6e1 --- /dev/null +++ b/models/Comments.js @@ -0,0 +1,26 @@ +const mongoose = require("mongoose"); + +const CommentSchema = new mongoose.Schema({ + comment: { + type: String, + required: true, + }, + likes: { + type: Number, + required: true, + }, + post: { + type: mongoose.Schema.Types.ObjectId, + ref: "Post", + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +module.exports = mongoose.model("Comment", CommentSchema); diff --git a/routes/comments.js b/routes/comments.js new file mode 100644 index 000000000..307af4196 --- /dev/null +++ b/routes/comments.js @@ -0,0 +1,13 @@ +const express = require("express"); +const router = express.Router(); +const commentsController = require("../controllers/comments"); +const { ensureAuth, ensureGuest } = require("../middleware/auth"); + +//Comment Routes - simplified for now +router.post("/createComment/:id", commentsController.createComment); + +router.put("/likeComment/:id", commentsController.likeComment); + +router.delete("/deleteComment/:id", commentsController.deleteComment); + +module.exports = router; diff --git a/routes/main.js b/routes/main.js index d6883000e..92838901e 100644 --- a/routes/main.js +++ b/routes/main.js @@ -15,4 +15,5 @@ router.get("/logout", authController.logout); router.get("/signup", authController.getSignup); router.post("/signup", authController.postSignup); +// Export functions for the app module.exports = router; diff --git a/routes/posts.js b/routes/posts.js index aa463ac90..51864be8f 100644 --- a/routes/posts.js +++ b/routes/posts.js @@ -13,4 +13,5 @@ router.put("/likePost/:id", postsController.likePost); router.delete("/deletePost/:id", postsController.deletePost); +// Export functions for the app module.exports = router; diff --git a/server.js b/server.js index 1718db010..a2d4541c5 100644 --- a/server.js +++ b/server.js @@ -10,6 +10,7 @@ const logger = require("morgan"); const connectDB = require("./config/database"); const mainRoutes = require("./routes/main"); const postRoutes = require("./routes/posts"); +const commentRoutes = require("./routes/comments"); //Use .env file in config folder require("dotenv").config({ path: "./config/.env" }); @@ -56,6 +57,7 @@ app.use(flash()); //Setup Routes For Which The Server Is Listening app.use("/", mainRoutes); app.use("/post", postRoutes); +app.use("/comment", commentRoutes); //Server Running app.listen(process.env.PORT, () => { diff --git a/views/feed.ejs b/views/feed.ejs index 0ded94809..e9bc512b4 100644 --- a/views/feed.ejs +++ b/views/feed.ejs @@ -2,13 +2,15 @@
diff --git a/views/post.ejs b/views/post.ejs index c36a1c946..de6eac4cf 100644 --- a/views/post.ejs +++ b/views/post.ejs @@ -27,6 +27,27 @@

<%= post.caption %>

+
+

Add a comment

+
+
+ + +
+ +
+
+ +
+ +
Return to Profile Return to Feed From 373f1b2542457000d7f730b35850bc03552f1de3 Mon Sep 17 00:00:00 2001 From: slyncrafty <101752232+slyncrafty@users.noreply.github.com> Date: Wed, 3 Sep 2025 18:14:03 +0100 Subject: [PATCH 2/3] Fix likeComment and deleteComment controller and add these functionalities to post view --- controllers/comments.js | 29 ++++++++++++++++------------- views/post.ejs | 32 ++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/controllers/comments.js b/controllers/comments.js index bbfa49406..426d28434 100644 --- a/controllers/comments.js +++ b/controllers/comments.js @@ -19,29 +19,32 @@ module.exports = { // increment the like count of the given post likeComment: async (req, res) => { try { - await Comment.findOneAndUpdate( - { _id: req.params.id }, - { - $inc: { likes: 1 }, - } - ); + const comment = await Comment.findByIdAndUpdate( + req.params.id, + { $inc: { likes: 1 } }, + { new: false } // we only need comment.post to redirect + ); console.log("Likes +1"); - res.redirect(`/post/${req.params.id}`); + res.redirect(`/post/${comment.post}`); } catch (err) { console.log(err); + res.redirect('back'); } }, - // delete a comment by id from the given post + // delete a comment by id from the given post with authorization deleteComment: async (req, res) => { try { - // Find Comment by id - let comment = await Comment.findById({ _id: req.params.id }); + // Find Comment by id and authorization + const comment = await Comment.findById({ _id: req.params.id }).populate({ path: 'post', select: 'user' }).lean(); + const isCommentAuthor = String(comment.user) === String(req.user._id); + const isPostAuthor = String(comment.post.user) === String(req.user._id); // Delete comment from db - await Comment.remove({ _id: req.params.id }); + await Comment.deleteOne({ _id: req.params.id }); console.log("Deleted Comment"); - res.redirect(`/post/${req.params.id}`); + const postId = comment.post?._id || comment.post; + return res.redirect(`/post/${postId}`); } catch (err) { - res.redirect(`/post/${req.params.id}`); + res.redirect('back'); } }, }; diff --git a/views/post.ejs b/views/post.ejs index de6eac4cf..d7edd1eab 100644 --- a/views/post.ejs +++ b/views/post.ejs @@ -39,11 +39,35 @@
-