diff --git a/config/database.js b/config/database.js index 24102b6d5..5df80ec6b 100644 --- a/config/database.js +++ b/config/database.js @@ -1,3 +1,4 @@ +//mongoDB connection made consolidated const mongoose = require("mongoose"); const connectDB = async () => { diff --git a/config/passport.js b/config/passport.js index 6c058d1b8..2df895a74 100644 --- a/config/passport.js +++ b/config/passport.js @@ -1,3 +1,4 @@ +//using passport's local strategy const LocalStrategy = require("passport-local").Strategy; const mongoose = require("mongoose"); const User = require("../models/User"); diff --git a/controllers/auth.js b/controllers/auth.js index 43f893aed..7c4c62db7 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -1,115 +1,160 @@ +//modules required for auth controller const passport = require("passport"); const validator = require("validator"); const User = require("../models/User"); +//login route, GET request exports.getLogin = (req, res) => { + //if the user is already logged in (check via passport) if (req.user) { + //redirect the user to the profile route return res.redirect("/profile"); } + //if the user is not already logged in, render the login ejs res.render("login", { title: "Login", }); }; +//login route, POST request exports.postLogin = (req, res, next) => { + //check for errors in the login const validationErrors = []; + //is the email valid? if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: "Please enter a valid email address." }); + //is the password populated? if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: "Password cannot be blank." }); - + //are there any errors at all? if (validationErrors.length) { + //flash the errors req.flash("errors", validationErrors); + //redirect to try again return res.redirect("/login"); } + + //format email given by user req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false, }); + //user local passport authentication strategy passport.authenticate("local", (err, user, info) => { if (err) { return next(err); } + //if there is no user available if (!user) { req.flash("errors", info); return res.redirect("/login"); } + //use passport to login the user req.logIn(user, (err) => { if (err) { return next(err); } + //if successful, flash that messsage req.flash("success", { msg: "Success! You are logged in." }); + //redirect user to profile route res.redirect(req.session.returnTo || "/profile"); }); })(req, res, next); }; +//logout route, GET request exports.logout = (req, res) => { + //logout the user req.logout(() => { console.log('User has logged out.') }) + //destroy the user session req.session.destroy((err) => { if (err) console.log("Error : Failed to destroy the session during logout.", err); + //there is no user in the current session req.user = null; + //redirect user to base route res.redirect("/"); }); }; +//signup route, GET request exports.getSignup = (req, res) => { + //if the user is already logged in (check via passport) if (req.user) { + //redirect the user to the profile route return res.redirect("/profile"); } + //if the user is not logged in yet, render the signup page res.render("signup", { title: "Create Account", }); }; +//signup route, POST request exports.postSignup = (req, res, next) => { + //check if there are errors signup up const validationErrors = []; + //is the email valid? if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: "Please enter a valid email address." }); + //is the password the correct length? if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: "Password must be at least 8 characters long", }); + //do the two passwords given match exactly? if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: "Passwords do not match" }); - + //are there any errors at all? if (validationErrors.length) { + //flash the errors req.flash("errors", validationErrors); + //redirect to try again return res.redirect("../signup"); } + + //format email req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false, }); + //make new User instance of the model const user = new User({ userName: req.body.userName, email: req.body.email, password: req.body.password, }); + //try to find an existing user User.findOne( + //that shares the same email or user name as the POST requestor { $or: [{ email: req.body.email }, { userName: req.body.userName }] }, (err, existingUser) => { if (err) { return next(err); } + //if there is an existing user if (existingUser) { + //flash the error req.flash("errors", { msg: "Account with that email address or username already exists.", }); + //redirect to try again return res.redirect("../signup"); } + //if there is no existing user with that email or username, save the user into the database user.save((err) => { if (err) { return next(err); } + //log the user in req.logIn(user, (err) => { if (err) { return next(err); } + //after successful login, redirect to profile route GET request res.redirect("/profile"); }); }); diff --git a/controllers/home.js b/controllers/home.js index 49ba26578..6bc0f98fc 100644 --- a/controllers/home.js +++ b/controllers/home.js @@ -1,5 +1,7 @@ module.exports = { + //main route, GET request getIndex: (req, res) => { + //serve the base ejs file res.render("index.ejs"); }, }; diff --git a/controllers/posts.js b/controllers/posts.js index a3e2dab5d..bd0f0266b 100644 --- a/controllers/posts.js +++ b/controllers/posts.js @@ -2,63 +2,81 @@ const cloudinary = require("../middleware/cloudinary"); const Post = require("../models/Post"); module.exports = { + //profile rotue, GET request getProfile: async (req, res) => { try { + //find all the posts made by the logged in user const posts = await Post.find({ user: req.user.id }); + //render the profile view with the user and the user's post as parameters res.render("profile.ejs", { posts: posts, user: req.user }); } catch (err) { console.log(err); } }, + //feed route, GET request getFeed: async (req, res) => { try { + //get all the posts in the db, and sort them with the mode recent being first const posts = await Post.find().sort({ createdAt: "desc" }).lean(); + //render the feed view with all the posts in the db res.render("feed.ejs", { posts: posts }); } catch (err) { console.log(err); } }, + //posts/postID route, GET request getPost: async (req, res) => { try { + //get the post from the database by the id passed as a parameter in the path const post = await Post.findById(req.params.id); + //render the view for an induvidual post with the post information and the user's information res.render("post.ejs", { post: post, user: req.user }); } catch (err) { console.log(err); } }, + + //posts/createPost route, POST request createPost: async (req, res) => { try { // Upload image to cloudinary const result = await cloudinary.uploader.upload(req.file.path); + //make new Post instance of the model await Post.create({ - title: req.body.title, - image: result.secure_url, - cloudinaryId: result.public_id, - caption: req.body.caption, - likes: 0, - user: req.user.id, + title: req.body.title, //post title + image: result.secure_url, //post image url on cloudinary + cloudinaryId: result.public_id, //cloudindary ID + caption: req.body.caption, //post caption + likes: 0, //post likes start at 0 + user: req.user.id, //user who posted it }); console.log("Post has been added!"); + //after post was made, redirect the user to their profile res.redirect("/profile"); } catch (err) { console.log(err); } }, + //posts/likePost/postID route, PUT request likePost: async (req, res) => { try { + //find the post that is liked by the id on the url parameter await Post.findOneAndUpdate( { _id: req.params.id }, - { + { //increment the likes of that post by 1 $inc: { likes: 1 }, } ); console.log("Likes +1"); + //redirect the user to that specific post res.redirect(`/post/${req.params.id}`); } catch (err) { console.log(err); } }, + + //posts/delete/postID route, DELETE request deletePost: async (req, res) => { try { // Find post by id @@ -68,6 +86,7 @@ module.exports = { // Delete post from db await Post.remove({ _id: req.params.id }); console.log("Deleted Post"); + //redirect user to the profile page res.redirect("/profile"); } catch (err) { res.redirect("/profile"); diff --git a/middleware/auth.js b/middleware/auth.js index f646630da..91d14ed2b 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -1,4 +1,5 @@ module.exports = { + //make sure user on the request is authenticated(using passport) ensureAuth: function (req, res, next) { if (req.isAuthenticated()) { return next(); @@ -6,6 +7,7 @@ module.exports = { res.redirect("/"); } }, + //make sure the user is a guest by seeing if they are a signed in user ensureGuest: function (req, res, next) { if (!req.isAuthenticated()) { return next(); diff --git a/middleware/cloudinary.js b/middleware/cloudinary.js index 0960c5b6f..0a1e96749 100644 --- a/middleware/cloudinary.js +++ b/middleware/cloudinary.js @@ -2,6 +2,7 @@ const cloudinary = require("cloudinary").v2; require("dotenv").config({ path: "./config/.env" }); +//set up cloudinary with cloud name, and secrets from .env cloudinary.config({ cloud_name: process.env.CLOUD_NAME, api_key: process.env.API_KEY, diff --git a/middleware/multer.js b/middleware/multer.js index c012afe58..ff4fe6d1e 100644 --- a/middleware/multer.js +++ b/middleware/multer.js @@ -2,7 +2,10 @@ const multer = require("multer"); const path = require("path"); module.exports = multer({ + //multer briefly using local disk storage storage: multer.diskStorage({}), + + //process file uploaded fileFilter: (req, file, cb) => { let ext = path.extname(file.originalname); if (ext !== ".jpg" && ext !== ".jpeg" && ext !== ".png") { diff --git a/models/Post.js b/models/Post.js index f7d14c981..bf9f3156b 100644 --- a/models/Post.js +++ b/models/Post.js @@ -1,30 +1,40 @@ const mongoose = require("mongoose"); +//Post schema for all users made const PostSchema = new mongoose.Schema({ + //need a post title title: { type: String, required: true, }, + //need a post image url image: { type: String, require: true, }, + //need a cloudinary Id cloudinaryId: { type: String, require: true, }, + //need a post caption caption: { type: String, required: true, }, + //need a likes number likes: { type: Number, required: true, }, + //need a user who made the post + //filled in by model user: { type: mongoose.Schema.Types.ObjectId, ref: "User", }, + //need a created at date + //filled in by model createdAt: { type: Date, default: Date.now, diff --git a/models/User.js b/models/User.js index afe8afc36..3233e3142 100644 --- a/models/User.js +++ b/models/User.js @@ -1,10 +1,11 @@ const bcrypt = require("bcrypt"); const mongoose = require("mongoose"); +//User schema for all users made const UserSchema = new mongoose.Schema({ - userName: { type: String, unique: true }, - email: { type: String, unique: true }, - password: String, + userName: { type: String, unique: true }, //need a unique username + email: { type: String, unique: true }, //need a unique email + password: String, //need a password }); // Password hash middleware. diff --git a/routes/main.js b/routes/main.js index d6883000e..af73d2bea 100644 --- a/routes/main.js +++ b/routes/main.js @@ -1,3 +1,4 @@ +//modules needed for main routes const express = require("express"); const router = express.Router(); const authController = require("../controllers/auth"); @@ -6,13 +7,13 @@ const postsController = require("../controllers/posts"); const { ensureAuth, ensureGuest } = require("../middleware/auth"); //Main Routes - simplified for now -router.get("/", homeController.getIndex); -router.get("/profile", ensureAuth, postsController.getProfile); -router.get("/feed", ensureAuth, postsController.getFeed); -router.get("/login", authController.getLogin); -router.post("/login", authController.postLogin); -router.get("/logout", authController.logout); -router.get("/signup", authController.getSignup); -router.post("/signup", authController.postSignup); +router.get("/", homeController.getIndex); //base route, GET request +router.get("/profile", ensureAuth, postsController.getProfile); //profile route, GET request, ensure user is logged in first +router.get("/feed", ensureAuth, postsController.getFeed); //feed route, GET request, ensure user is logged in first +router.get("/login", authController.getLogin); //login route, GET request +router.post("/login", authController.postLogin); //login route, POST request +router.get("/logout", authController.logout); //logout route, GET request +router.get("/signup", authController.getSignup); //signup route, GET request +router.post("/signup", authController.postSignup); //signup route, POST request module.exports = router; diff --git a/routes/posts.js b/routes/posts.js index aa463ac90..ecf449d20 100644 --- a/routes/posts.js +++ b/routes/posts.js @@ -1,3 +1,4 @@ +//modules needed for posts routes const express = require("express"); const router = express.Router(); const upload = require("../middleware/multer"); @@ -5,12 +6,12 @@ const postsController = require("../controllers/posts"); const { ensureAuth, ensureGuest } = require("../middleware/auth"); //Post Routes - simplified for now -router.get("/:id", ensureAuth, postsController.getPost); +router.get("/:id", ensureAuth, postsController.getPost); //posts/:id route, GET request based on a post ID, ensure user is logged in first -router.post("/createPost", upload.single("file"), postsController.createPost); +router.post("/createPost", upload.single("file"), postsController.createPost); //posts/createPost route, POST request using the file uploaded -router.put("/likePost/:id", postsController.likePost); +router.put("/likePost/:id", postsController.likePost); //posts/likePost/:id, PUT request liking a post -router.delete("/deletePost/:id", postsController.deletePost); +router.delete("/deletePost/:id", postsController.deletePost); //posts/deletePost/:id, DELETE request deleting a post module.exports = router; diff --git a/server.js b/server.js index 1718db010..748770d91 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,4 @@ +//modules needed at top level const express = require("express"); const app = express(); const mongoose = require("mongoose"); @@ -11,32 +12,32 @@ const connectDB = require("./config/database"); const mainRoutes = require("./routes/main"); const postRoutes = require("./routes/posts"); -//Use .env file in config folder +//.env file for secrets require("dotenv").config({ path: "./config/.env" }); -// Passport config +//passport configuration require("./config/passport")(passport); -//Connect To Database +//connect to mongoDB connectDB(); -//Using EJS for views +//ejs for views app.set("view engine", "ejs"); -//Static Folder +//static files app.use(express.static("public")); -//Body Parsing +//sending and receiving json data app.use(express.urlencoded({ extended: true })); app.use(express.json()); -//Logging +//logger app.use(logger("dev")); -//Use forms for put / delete +//use forms for PUT and DELETE requests app.use(methodOverride("_method")); -// Setup Sessions - stored in MongoDB +//session middleware app.use( session({ secret: "keyboard cat", @@ -46,18 +47,18 @@ app.use( }) ); -// Passport middleware +//passport middleware app.use(passport.initialize()); app.use(passport.session()); -//Use flash messages for errors, info, ect... +//error messages app.use(flash()); -//Setup Routes For Which The Server Is Listening +//receiving requests and allocating routers app.use("/", mainRoutes); app.use("/post", postRoutes); -//Server Running +//start the server app.listen(process.env.PORT, () => { console.log("Server is running, you better catch it!"); }); diff --git a/views/feed.ejs b/views/feed.ejs index 0ded94809..f05892faa 100644 --- a/views/feed.ejs +++ b/views/feed.ejs @@ -2,9 +2,12 @@