diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a0792 --- /dev/null +++ b/.gitignore @@ -0,0 +1,96 @@ +# Logs +logs +.log +npm-debug.log +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9].[0-9].[0-9].[0-9].json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in t \ No newline at end of file diff --git a/backend/api/ChallengesAPI.js b/backend/api/ChallengesAPI.js new file mode 100644 index 0000000..3d6c05b --- /dev/null +++ b/backend/api/ChallengesAPI.js @@ -0,0 +1,81 @@ +const Challenges = require("../models/Challenges"); +const User = require("../models/Users"); + +const fetchChallenges = async () => { + const challenges = await Challenges.find({}) + .sort({ timestamp: -1 }) + .populate("creatorId") + .exec(); + return challenges; +}; + +const createChallenges = async (challengeData, userId) => { + try { + const challenge = new Challenges(challengeData); + await Promise.all([await challenge.save(), User.updateOne( + { _id: userId }, + { + $push: { participatedChallenges: challenge._id }, + $addToSet: { challenges: challenge._id }, + $inc: { points: 5 } + } + )]) + return challenge; + } catch (err) { + console.log(err); + throw new Error(err); + } +}; + +const fetchOneChallenge = async (challengeId) => { + try { + const challenge = await Challenges.findOne({ _id: challengeId }) + .populate("creatorId") + .populate("participators") + .exec(); + return challenge; + } catch (err) { + throw new Error(err); + } +}; + +const participateInChallenge = async(challengeId, userId)=>{ + try { + const challenge = await Challenges.findOne({ _id: challengeId }); + if (challenge.participators.includes(userId)) { + throw new Error(`You have already participated`); + } + const participatePromise = Challenges.updateOne( + { _id: challengeId }, + { $addToSet: { participators: userId } } + ); + const challengeCreator = await User.findById(challenge.creatorId); + challengeCreator.points += 2; + const foundUserPromise = User.updateOne({ _id: userId }, { $addToSet: { participatedChallenges: challengeId } }); + const [user, resultOfParticipation] = await Promise.all([foundUserPromise, participatePromise, challengeCreator.save()]); + return resultOfParticipation; + } catch (error) { + throw new Error(error.message); + } +} + +const deleteChallenge = async (challengeId, user) => { + try { + await Challenges.findByIdAndDelete(challengeId); + user.challenges.pull(challengeId) + user.points -= 5; + await user.save(); + }catch{ + console.log(err); + } +} + + +module.exports = { fetchChallenges, createChallenges, fetchOneChallenge, participateInChallenge, deleteChallenge }; + + +// const userPromise = User.findById(userId); +// const challengesPromise = Challenge.find({ participants: userId }).sort({ +// createdAt: -1 +// }); +// const [user, challenges] = await Promise.all([userPromise, challengesPromise]); \ No newline at end of file diff --git a/backend/api/commentAPI.js b/backend/api/commentAPI.js new file mode 100644 index 0000000..aab4c5b --- /dev/null +++ b/backend/api/commentAPI.js @@ -0,0 +1,45 @@ +const Comment = require("../models/Comments"); +const Posts = require("../models/Posts"); +const User = require("../models/Users"); + +const addComment = async (commentDetails, postId) => { + try { + const newComment = new Comment(commentDetails); + await newComment.save(); + + const post = await Posts.findById(postId); + const postCreator = await User.findById(post.creator); + postCreator.points += 2; + await post.stats.comments.push(newComment._id); + Promise.all([postCreator.save(), post.save()]) + .then(() => { + return; + }) + .catch((error) => { + throw new Error(error); + }); + } catch (err) { + throw new Error(err); + } +}; + +const deleteComment = async (commentId, postId) => { + const post = await Posts.findById(postId); + post.stats.comments.pull(commentId); + const postCreator = await User.findById(post.creator); + postCreator.points -= 2; + Promise.all([ + postCreator.save(), + post.save(), + Comment.findByIdAndDelete(commentId), + ]) + .then(() => { + return; + }) + .catch((error) => { + throw new Error(error); + }); + }; + + +module.exports = {addComment, deleteComment}; diff --git a/backend/api/deleteImage.js b/backend/api/deleteImage.js new file mode 100644 index 0000000..7787a3e --- /dev/null +++ b/backend/api/deleteImage.js @@ -0,0 +1,17 @@ +const cloudinary = require('cloudinary').v2; + +// Configuration +cloudinary.config({ + cloud_name: process.env.CLOUD_NAME, + api_key: process.env.API_KEY, + api_secret: process.env.API_SECRET + }); + + +// Delete image from cloudinary +const deleteImage = async (imageId) => { + await cloudinary.uploader.destroy(imageId); + return true; +} + +module.exports = deleteImage; \ No newline at end of file diff --git a/backend/api/devAPI.js b/backend/api/devAPI.js new file mode 100644 index 0000000..0109bc0 --- /dev/null +++ b/backend/api/devAPI.js @@ -0,0 +1,55 @@ +const userModel = require("../models/Users"); +const postModel = require("../models/Posts"); + +async function increaseLike(postId, currentLike, currentDisLike, userId) { + const postFound = await postModel.findById(postId).then((post)=>{ + if(post){ + let indexInDownvotes = post.stats.downvoted_by.indexOf(userId); + if(indexInDownvotes > -1){ + post.stats.downvoted_by.splice(indexInDownvotes, 1); + } + post.stats.upvoted_by.push(userId); + post.stats.upvotes = currentLike + 1; + post.stats.downvotes = currentDisLike; + post.save(); + userModel.findById(post.creator).then((user)=>{ + user.points += 1; + user.save(); + }) + return post; + } + + }); + if (postFound) { + return postFound.posts; + } else { + console.log("posts not found"); + } +} +async function decreaseLike(postId, currentLike, currentDisLike, userId) { + const postFound = await postModel.findById(postId).then((post)=>{ + if(post){ + let indexInUpvotes = post.stats.upvoted_by.indexOf(userId); + if(indexInUpvotes > -1){ + post.stats.upvoted_by.splice(indexInUpvotes, 1); + } + post.stats.downvoted_by.push(userId); + post.stats.upvotes = currentLike; + post.stats.downvotes = currentDisLike + 1; + post.save(); + userModel.findById(post.creator).then((user)=>{ + user.points -= 1; + user.save(); + }) + return post; + } + + }); + if (postFound) { + return postFound.posts; + } else { + console.log("posts not found"); + } +} + +module.exports = { increaseLike, decreaseLike }; diff --git a/backend/api/getSignature.js b/backend/api/getSignature.js new file mode 100644 index 0000000..5dcfa16 --- /dev/null +++ b/backend/api/getSignature.js @@ -0,0 +1,16 @@ +const cloudinary = require('cloudinary').v2; + +// Configuration +cloudinary.config({ + cloud_name: process.env.CLOUD_NAME, + api_key: process.env.API_KEY, + api_secret: process.env.API_SECRET + }); + +const getSignature = async (req, res) => { + const timestamp = Math.round(new Date().getTime() / 1000); + const signature = cloudinary.utils.api_sign_request( { timestamp: timestamp }, process.env.API_SECRET); + res.json({signature: signature, timestamp: timestamp}); +} + +module.exports = getSignature; \ No newline at end of file diff --git a/backend/api/pollsApi.js b/backend/api/pollsApi.js new file mode 100644 index 0000000..b3c2789 --- /dev/null +++ b/backend/api/pollsApi.js @@ -0,0 +1,52 @@ +const Polls = require("../models/Polls"); +const User = require("../models/Users"); + +const voteCount = async (pollId, optionId, userId) => { + try { + const poll = await Polls.findById(pollId); + let option = poll.options.find((option) => option._id == optionId); + for (optionCheck of poll.options) { + if (optionCheck.voted_by.includes(userId)) { + return; + } + } + const pollCreator = await User.findById(poll.creatorId); + pollCreator.points += 1; + option.voted_by.push(userId); + await Promise.all([pollCreator.save(), poll.save()]); + } catch (err) { + console.log(err); + } +}; + +const deletePoll = async (pollId, user) => { + try { + user.polls.pull(pollId); + user.points -= 5; + await Promise.all([user.save(), Polls.findByIdAndDelete(pollId)]); + } catch { + console.log(err); + } +}; + +const fetchPolls = async () => { + const polls = await Polls.find({}).sort({timestamp: -1}).populate("creatorId").exec(); + const totalVotes = polls.map(poll => { + return poll.options.reduce((acc, option) => { + return acc + option.voted_by.length; + }, 0); + }); + return [polls, totalVotes]; + } + + + async function getPolls(user) { + const userFound = await User.findById(user._id).populate("polls").exec(); + if (userFound) { + return userFound.polls; + } else { + console.log("poll not found"); + } + } + +module.exports = { voteCount, deletePoll, fetchPolls, getPolls }; diff --git a/backend/api/postAPI.js b/backend/api/postAPI.js new file mode 100644 index 0000000..e5011c0 --- /dev/null +++ b/backend/api/postAPI.js @@ -0,0 +1,96 @@ +const Post = require("../models/Posts"); +const userModel = require("../models/Users"); +const deleteImage = require("./deleteImage"); +const userAPI = require("./userAPI"); + +const fetchPosts = async () => { + // userModel.createIndexes({ firstname: "text", lastname: "text", username: "text" }).then(()=>{ + // console.log("Indexes created"); + // }).catch((err)=>{ + // console.log("Error creating indexes"); + // }); + //this needs to be called after droping db + const posts = await Post.find({}) + .sort({ createdAt: -1 }) + .populate("creator") + .exec(); + return posts; +}; + +async function findPosts(user) { + const userFound = await userModel.findById(user._id).populate("posts").exec(); + if (userFound) { + return userFound.posts.reverse(); + } else { + console.log("posts not found"); + } +} + +const deletePost = async (postId, user) => { + const post = await Post.findById(postId); + if (post.creator.toString() === user._id.toString()) { + await Post.findByIdAndDelete(postId); + const imageId = await Post.findById(postId).select("imageId"); + if (imageId !== null) { + await deleteImage(imageId); + } + const foundUser = await userModel.findById(user._id.toString()); + foundUser.points -= 3; + foundUser.posts.pull(postId); + await foundUser.save(); + + return; + } else { + const foundUser = await userModel.findById(user._id.toString()); + foundUser.posts.pull(postId); + await foundUser.save(); + return; + } +}; + +const addPost = async (user, content, imageUrl, imageId) => { + if (!content && !imageUrl) { + throw new Error("Please upload an image or write a post"); + } + const newPost = new Post({ + content: content, + creator: user._id, + imagePath: imageUrl, + imageId: imageId, + }); + newPost.save((err, post) => { + if (err) { + throw new Error("something went wrong during saving"); + } else { + try { + userAPI.findUser(user).then((user) => { + user.posts.push(post._id); + user.points += 3; + user.save((err, user) => { + if (err) { + throw new Error("something went wrong during saving"); + } else { + return; + } + }); + }); + } catch (err) { + throw new Error(err); + } + } + }); +}; + +const sharePost = async (postId, currentShare, user) => { + const post = await Post.findById(postId); + if (user.posts.includes(postId)) { + return; + } + const postCreator = await userModel.findById(post.creator); + postCreator.points += 2; + user.posts.push(postId); + post.stats.shares = currentShare + 1; + await Promise.all([postCreator.save(), post.save(), user.save()]); +}; + +module.exports = { fetchPosts, findPosts, deletePost, addPost, sharePost }; diff --git a/backend/api/profileAPI.js b/backend/api/profileAPI.js new file mode 100644 index 0000000..cf228da --- /dev/null +++ b/backend/api/profileAPI.js @@ -0,0 +1,76 @@ +const User = require("../models/Users"); + +const searchProfile = async (searchTerm) => { + const users = await User.find( + { + $or: [ + { $text: { $search: searchTerm } }, + { firstname: { $regex: searchTerm, $options: "i" } }, + { lastname: { $regex: searchTerm, $options: "i" } }, + { username: { $regex: searchTerm, $options: "i" } }, + ], + }, + { + score: { $meta: "textScore" }, + firstname: 1, + lastname: 1, + username: 1, + profilePic_url: 1, + _id: 1, + } + ).sort({ score: { $meta: "textScore" } }); + return users; + }; + + + const followAndUnfollow = async (followingId, followerId) =>{ + const followerPromise = User.findById(followerId) + const followingPromise = User.findById(followingId) + const [follower, following] = await Promise.all([followerPromise, followingPromise]) + + if(following.followers.includes(followerId)){ + following.points -= 10; + following.followers.pull(followerId) + } else{ + following.points += 10; + following.followers.push(followerId) + } + + follower.following.includes(followingId) ? follower.following.pull(followingId) : follower.following.push(followingId) + await Promise.all([following.save(), follower.save()]) +} + + +const getActivities = async function (user) { + try { + const userFound = await User.findById(user._id).populate("polls").populate("challenges").populate({ + path: "participatedChallenges", + populate: { + path: "creatorId", + model: "User" + } + }).exec(); + if (userFound) { + const combined = [...userFound.polls, ...userFound.challenges, ...userFound.participatedChallenges]; + const sorted = combined.sort((a, b) => b.timestamp - a.timestamp); + let uniqueArray = sorted + .filter((value, index, array) => array.findIndex(obj => String(obj._id) === String(value._id)) === index); + const totalVotes = uniqueArray.map(poll => { + if (!poll.options) { + return null; + } + return poll.options.reduce((acc, option) => { + return acc + option.voted_by.length; + }, 0); + }); + return [uniqueArray, totalVotes]; + } else { + throw new Error("User not found"); + } + } catch (error) { + throw new Error(error); + } + +} + +module.exports = {searchProfile, followAndUnfollow, getActivities}; \ No newline at end of file diff --git a/backend/api/userAPI.js b/backend/api/userAPI.js new file mode 100644 index 0000000..6b61291 --- /dev/null +++ b/backend/api/userAPI.js @@ -0,0 +1,41 @@ +const userModel = require("../models/Users"); +const postModel = require("../models/Posts"); +const passport = require("passport"); + +const findUser = async function (user) { + const userFound = await userModel.findById(user._id).populate("posts").exec(); + if (userFound) { + return userFound; + } else { + console.log("user not found"); + } + } + + const updateUser = async (id, postedData) => { + try { + const result = await userModel.findOneAndUpdate({ _id: id }, postedData); + return result; + } catch (err) { + if (err.name === "MongoServerError" && err.code === 11000) { + const field = Object.keys(err.keyPattern)[0]; // Get the field that caused the error + const value = err.keyValue[field]; // Get the value that caused the error + const message = `The ${field} "${value}" is already taken. Please choose a different ${field}.`; + throw new Error(message); + } else{ + throw new Error("something went wrong please try again later"); + } + } +} + + + +module.exports = {findUser, updateUser}; + + + + + + + + + diff --git a/backend/config/dbConnection.js b/backend/config/dbConnection.js new file mode 100644 index 0000000..312e908 --- /dev/null +++ b/backend/config/dbConnection.js @@ -0,0 +1,17 @@ +require("dotenv").config(); +const mongoose = require('mongoose'); + +const connectDB = async (DATABASE_URI) => { + mongoose.set("strictQuery", false); + mongoose.connect( + DATABASE_URI, {useNewUrlParser: true} + ); + const db = mongoose.connection; + db.on("error", console.error.bind(console, "connection error:")); + db.once("open", function () { + console.log("DB connection established"); + }); + +} + +module.exports = connectDB; \ No newline at end of file diff --git a/backend/config/loginConfig.js b/backend/config/loginConfig.js new file mode 100644 index 0000000..8093495 --- /dev/null +++ b/backend/config/loginConfig.js @@ -0,0 +1,31 @@ +const passportSetup = require("../config/passportConfig"); +const passport = require("passport"); +const User = require("../models/Users"); + +const initializeLogin = async (userData, req, res) => { +try{ + const user = new User({ + username: userData.username, + password: userData.password, + }); + + req.login(user, (err) => { + if (err) { + req.flash("error", err.message); + res.redirect("/login"); + } else { + passport.authenticate("local",{ + successRedirect: "/", + successFlash: "Welcome to Knot!", + failureRedirect: "/login", + failureFlash: "Invalid username or password", + })(req, res); + } + }); +} catch(err){ + console.log(err); + req.flash("error", err.message); +}; +}; + +module.exports = initializeLogin; diff --git a/backend/config/logoutConfig.js b/backend/config/logoutConfig.js new file mode 100644 index 0000000..ce220ee --- /dev/null +++ b/backend/config/logoutConfig.js @@ -0,0 +1,15 @@ +const passportSetup = require("./passportConfig"); +const passport = require("passport"); + +const initializeLogout = async (req, res) => { + req.logout((err) => { + if (err) { + console.log(err); + res.redirect("/unknownerror"); + } else { + res.redirect("/login"); + } + }); +}; + +module.exports = initializeLogout; diff --git a/backend/config/passportConfig.js b/backend/config/passportConfig.js new file mode 100644 index 0000000..29a41cc --- /dev/null +++ b/backend/config/passportConfig.js @@ -0,0 +1,62 @@ +const passport = require("passport"); +const GoogleStrategy = require("passport-google-oauth20"); +const User = require("../models/Users"); + +passport.use(User.createStrategy()); + +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser((id, done) => { + User.findById(id).then((user) => { + done(null, user); + }); +}); + +const generateRandomUsername = async (user) => { + const uniqueUserName = await User.findOne({ username: user }) + if (!uniqueUserName) { + return user + } + const prefix = user + const suffix = Math.floor(Math.random() * 10000); + const username = `${prefix}${suffix}`; + const foundUser = await User.findOne({ username }); + if (foundUser) { + return generateRandomUsername(); + } + return username; +} + +passport.use( + new GoogleStrategy( + { + clientID: process.env.CLIENT_ID, + clientSecret: process.env.CLIENT_SECRET, + callbackURL: process.env.CALLBACK_URL, + }, + async (accessToken, refreshToken, profile, done) => { + const randomProfileTheme = ["lorelei", "personas", "fun-emoji", "avataaars", "adventurer", "big-ears"] + const userData = { + googleId: profile.id, + username: await generateRandomUsername(profile._json.email.split("@")[0]), + firstname: profile.name.givenName, + lastname: profile.name.familyName, + email: profile._json.email, + bio: "", + profilePicId: "", + profilePic_url: `https://api.dicebear.com/5.x/${randomProfileTheme[Math.floor(Math.random()*randomProfileTheme.length)]}/svg?seed=${profile.id}&backgroundColor=ffffff,b6e3f4&backgroundType=gradientLinear`, + }; + User.findOne({ googleId: profile.id }, function (err, user) { + if (user) { + return done(err, user); + } else { + User.create(userData, function (err, user) { + return done(err, user); + }); + } + }); + } + ) +); diff --git a/backend/config/passportLocalConfig.js b/backend/config/passportLocalConfig.js new file mode 100644 index 0000000..e954ffc --- /dev/null +++ b/backend/config/passportLocalConfig.js @@ -0,0 +1,26 @@ +const passportSetup = require("../config/passportConfig"); +const passport = require("passport"); +const User = require("../models/Users"); + +const initializeSignup = async (userData, password, req, res) => { + try{ + User.register(userData, password, (err, user) => { + if (err) { + req.flash("error", err.message); + res.redirect("/signup"); + } else { + passport.authenticate("local", { + successRedirect: "/", + successFlash: "Welcome to Knot!", + failureRedirect: "/signup", + failureFlash: true, + })(req, res) + } + }); + } catch(err){ + req.flash("error", err.message); + console.log(err); + } +}; + +module.exports = initializeSignup; \ No newline at end of file diff --git a/backend/controllers/challengesController.js b/backend/controllers/challengesController.js new file mode 100644 index 0000000..18ee3db --- /dev/null +++ b/backend/controllers/challengesController.js @@ -0,0 +1,130 @@ + +const challengesAPI = require("../api/ChallengesAPI"); + +const challengesRender = async (req, res) => { + const pageInfo = { + title: "Knot - Challenges", + pagename: "challenges", + profilePic: req.user.profilePic_url, + userId: (String(req.user._id)), + }; + try { + const challenges = await challengesAPI.fetchChallenges(); + res.render('challenges', { challenges, pageInfo: pageInfo, }); + } catch (err) { + console.log(err); + res.send([]); + } +}; + +const createChallengeRender = async (req, res) => { + const pageInfo = { + user: req.user, + title: "Knot - New Challenge", + pagename: "add challenge", + profilePic: req.user.profilePic_url, + }; + res.render("addChallenge", { pageInfo: pageInfo }); +}; + +const createChallengeController = async (req, res) => { + const challengeData ={ + creatorId: req.user._id, + participators: [req.user._id] + } + if (req.body.content !== "") { + challengeData.content = req.body.content; + }else{ + req.flash("error", "chalu banta hai shale... seriously ?"); + res.redirect("/challenges/add"); + return + } + ; + if (req.body.desc !== "") { + challengeData.description = req.body.desc; + }else{ + req.flash("error", "chalu banta hai shale... seriously ?"); + res.redirect("/challenges/add"); + return + }; + if (req.body.duration !== "") { + challengeData.duration = req.body.duration; + }else{ + req.flash("error", "chalu banta hai shale... seriously ?"); + res.redirect("/challenges/add"); + return + }; + + try { + await challengesAPI.createChallenges(challengeData, req.user._id); + res.redirect("/challenges"); + } catch (err) { + console.log(err); + } +}; + +const viewOneChallengeRender = async(req, res)=>{ + const pageInfo = { + user: req.user, + title: "Knot - Challenge", + pagename: "challenge", + profilePic: req.user.profilePic_url, + userId: (String(req.user._id)) + }; + const challengeId = req.params.id; + + + try{ + const challenge = await challengesAPI.fetchOneChallenge(challengeId); + const challengeParticipators = challenge.participators.map((participator)=>{ + return String(participator._id); + }) + let challengeEnded = null; + const remainingTime = new Date(challenge.duration) - new Date(); + + if (!(remainingTime > 0)) { + challengeEnded = true; + } else { + challengeEnded = false; + } + res.render("oneChallenge", {pageInfo: pageInfo, challenge: challenge, challengeEnded: challengeEnded, challengeParticipators: challengeParticipators, messages: req.flash()}); + }catch(err){ + res.redirect("/challenges") + } +} + +const participateInChallengeRender = async(req, res)=>{ + const challengeId = req.params.id; + const userId = req.user._id; + try{ + await challengesAPI.participateInChallenge(challengeId, userId); + req.flash('success', 'You have successfully participated in this challenge'); + res.redirect(`/challenges/${challengeId}`); + }catch(err){ + req.flash('error', err.message); + res.redirect(`/challenges/${challengeId}`); + } +}; + +const deleteChallengeController = async(req, res)=>{ + const challengeId = req.params.challengeId; + const user = req.user; + try { + await challengesAPI.deleteChallenge(challengeId, user); + res.redirect("/profile/activity"); + } catch (err) { + req.flash("error", "failed to delete challenge"); + res.redirect("/profile"); + } +}; + + + +module.exports = { + challengesRender, + createChallengeRender, + createChallengeController, + viewOneChallengeRender, + participateInChallengeRender, + deleteChallengeController +}; diff --git a/backend/controllers/commentController.js b/backend/controllers/commentController.js new file mode 100644 index 0000000..0b266d1 --- /dev/null +++ b/backend/controllers/commentController.js @@ -0,0 +1,42 @@ +const commentAPI = require("../api/commentAPI") + + +const addCommentController= async (req,res) =>{ + const commentDetails = { + content: req.body.comment, + commentor: req.user._id + } + const postId = req.params.postId + + if(commentDetails.content.trim() === ""){ + req.flash("error","cannot add empty comment") + res.redirect(`/post/${postId}`) + return + } + + try{ + await commentAPI.addComment(commentDetails,postId) + res.redirect(`/post/${postId}`) + } catch(err){ + req.flash("error","cannot add comment, something went wrong") + res.redirect(`/post/${postId}`) + } +} + +const deleteCommentController = async (req,res) =>{ + const commentId = req.body.commentId + const postId = req.params.postId + try{ + await commentAPI.deleteComment(commentId,postId) + res.redirect(`/post/${postId}`) + } catch(err){ + req.flash("error","cannot delete comment, something went wrong") + res.redirect(`/post/${postId}`) + } +} + +module.exports = { + addCommentController, + deleteCommentController +} + diff --git a/backend/controllers/followersController.js b/backend/controllers/followersController.js new file mode 100644 index 0000000..bfe498b --- /dev/null +++ b/backend/controllers/followersController.js @@ -0,0 +1,80 @@ +const Users = require("../models/Users"); + +const followersRender = async (req, res) => { + const user = await Users.findById(req.user._id) + .populate("followers") + .populate("following"); + res.render("followers", { + pageTitle: "Knot - Followers", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "followers", + user: user, + isFollowers: true, + isFollowing: false, + isSearching: false, + followers: user.followers, + currentUser: req.user, + messages: req.flash(), + }); +}; + +const followingRender = async (req, res) => { + const user = await Users.findById(req.user._id) + .populate("followers") + .populate("following"); + res.render("followers", { + pageTitle: "Knot - Followers", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "followers", + user: user, + isFollowing: true, + isFollowers: false, + isSearching: false, + following: user.following, + currentUser: req.user, + messages: req.flash(), + }); +}; + +const viewProfileFollowersRender = async (req, res) => { + const user = await Users.findById(req.params.profileId) + .populate("followers") + .populate("following"); + res.render("followers", { + pageTitle: "Knot - Followers", + profilePicLoggedIn: user.profilePic_url, + pageName: "viewfollowers", + user: user, + isFollowers: true, + isFollowing: false, + followers: user.followers, + currentUser: req.user, + messages: req.flash(), + isSearching: false, + }); +}; + +const viewProfileFollowingRender = async (req, res) => { + const user = await Users.findById(req.params.profileId) + .populate("followers") + .populate("following"); + res.render("followers", { + pageTitle: "Knot - Followers", + profilePicLoggedIn: user.profilePic_url, + pageName: "viewfollowers", + user: user, + isFollowing: true, + isFollowers: false, + following: user.following, + currentUser: req.user, + messages: req.flash(), + isSearching: false, + }); +}; + +module.exports = { + followersRender, + followingRender, + viewProfileFollowersRender, + viewProfileFollowingRender, +}; diff --git a/backend/controllers/homeController.js b/backend/controllers/homeController.js new file mode 100644 index 0000000..2f44b37 --- /dev/null +++ b/backend/controllers/homeController.js @@ -0,0 +1,22 @@ +const postAPI = require('../api/postAPI'); + +const feedController = async (req, res) => { + const pageInfo = { + title: "Knot - Home", + pagename: "home", + profilePic: req.user.profilePic_url, + userId: req.user._id, + user: req.user, + }; + + try { + const posts = await postAPI.fetchPosts(); + res.render("home", { posts, pageInfo: pageInfo, messages: req.flash() }); + } catch (err) { + res.render("home", { posts: [], pageInfo: pageInfo, messages: req.flash() }); + } +}; + +module.exports = { + feedController, +}; diff --git a/backend/controllers/pointsController.js b/backend/controllers/pointsController.js new file mode 100644 index 0000000..5a5edd5 --- /dev/null +++ b/backend/controllers/pointsController.js @@ -0,0 +1,12 @@ +const pointsController = (req, res) => { + const pageInfo = { + pageTitle: "Knot - Points", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "points", + user: req.user, + currentUser: req.user, + }; + res.render("points", { pageInfo }); +}; + +module.exports = { pointsController }; diff --git a/backend/controllers/pollController.js b/backend/controllers/pollController.js new file mode 100644 index 0000000..6d121b3 --- /dev/null +++ b/backend/controllers/pollController.js @@ -0,0 +1,136 @@ +const userAPI = require("../api/userAPI"); +const pollsModel = require("../models/Polls"); +const pollsAPI = require("../api/pollsAPI"); + +const pollsRender = async (req, res) => { + const pageInfo = { + title: 'Knot - Polls', + pagename: 'polls', + profilePic: req.user.profilePic_url, + userId: req.user._id, + } + + try { + const [polls, totalVotes] = await pollsAPI.fetchPolls(); + res.render('polls', { polls, pageInfo: pageInfo, totalVotes }); + } catch (err) { + console.log(err); + res.render('polls', { polls: [] }); + } +} + + +const createPollsController = async (req, res) => { + const id = req.user._id; + const title = req.body.title; + const [option1, option2, option3, option4] = req.body.option; + if(title.trim() === "" || option1.trim() === "" || option2.trim() === "" || option3.trim() === "" || option4.trim() === ""){ + req.flash("error", "can't create poll with empty fields"); + res.redirect("/"); + return; + } + const newPoll = new pollsModel({ + creatorId: id, + content: title, + options: [ + { + + optionsName: option1, + voted_by: [] + + }, + { + + optionsName: option2, + voted_by: [] + }, + { + + optionsName: option3, + voted_by: [] + }, + { + + optionsName: option4, + voted_by: [] + } + + ] + + }); + newPoll.save((err, poll) => { + if (err) { + req.flash("error", "can't create poll, try again later"); + res.redirect("/"); + } else { + try { + userAPI.findUser(req.user).then((user) => { + user.polls.push(poll._id); + user.points += 5; + user.save((err, user) => { + if (err) { + req.flash("error", "can't create poll, try again later"); + res.redirect("/profile"); + } else { + res.redirect("/profile/activity"); + } + }); + }); + } catch (err) { + res.send(err.message); + } + } + }); + +} + + +const viewPollsRender = async (req, res) => { + const postData = await pollsAPI.getPolls(req.user); + res.render("viewPolls", { polls: postData[0] }); +}; + +const createPollsRender = async (req, res) => { + const pageInfo = { + user: req.user, + title: "Knot - New Polls", + pagename: "add polls", + profilePic: req.user.profilePic_url, + }; + const user = req.user; + res.render("addPolls", { user: user, pageInfo: pageInfo }); +} + +const voteController = async (req, res) => { + const clickedOptionId = req.body.clickedOptionId; + const pollId = req.body.pollId; + const userId = req.user._id; + try { + await pollsAPI.voteCount(pollId, clickedOptionId, userId); + res.redirect("/polls"); + } catch (err) { + console.log(err); + } + +} + +const deletePollController = async (req, res) => { + const pollId = req.params.pollId; + const user = req.user; + try { + await pollsAPI.deletePoll(pollId, user); + res.redirect("/profile/activity"); + } catch (err) { + req.flash("error", "can't delete poll, try again later"); + res.redirect("/profile"); + } +}; + +module.exports = { + pollsRender, + createPollsController, + viewPollsRender, + createPollsRender, + voteController, + deletePollController +}; \ No newline at end of file diff --git a/backend/controllers/postController.js b/backend/controllers/postController.js new file mode 100644 index 0000000..3e7857c --- /dev/null +++ b/backend/controllers/postController.js @@ -0,0 +1,118 @@ + +const devAPI = require("../api/devAPI"); +const postAPI = require("../api/postAPI"); +const Posts = require("../models/Posts"); + +const createPostRender = async (req, res) => { + const pageInfo = { + user: req.user, + title: "Knot - New Post", + pagename: "", + profilePic: req.user.profilePic_url, + }; + res.render("addpost", { pageInfo: pageInfo, messages: req.flash() }); +}; + +const createPostController = async (req, res) => { + const user = req.user; + const content = req.body.content; + const imageUrl = req.body.imgUrl; + const imageId = req.body.uploadedImgId; + const pageInfo = { + user: req.user, + title: "Knot - New Post", + pagename: "", + profilePic: req.user.profilePic_url, + }; + try { + await postAPI.addPost(user, content, imageUrl, imageId); + res.json({ success: "post added successfully" }); + } catch (err) { + req.flash("error", "please upload an image or write a post (supported formats: jpg, jpeg, png)"); + res.json({ error: err.message }); + } +}; + +const likeCountController = async (req, res) => { + if (!req.body.upvotes) return res.status(401).send("Unauthorized"); + try { + const postId = req.body.postId; + const currentLike = parseInt(req.body.upvotes); + const currentDisLike = parseInt(req.body.downvotes); + const userId = req.user._id; + await devAPI.increaseLike(postId, currentLike, currentDisLike, userId); + } catch (err) { + console.log(err); + } +}; +const dislikeCountController = async (req, res) => { + if (!req.body.downvotes) return res.status(401).send("Unauthorized"); + try { + const postId = req.body.postId; + const currentLike = parseInt(req.body.upvotes); + const currentDisLike = parseInt(req.body.downvotes); + const userId = req.user._id; + await devAPI.decreaseLike(postId, currentLike, currentDisLike, userId); + } catch (err) { + console.log(err); + } +}; + +const sharePostController = async (req, res) => { + if (!req.body.shares) return; + try { + const postId = req.body.postId; + const currentShare = parseInt(req.body.shares); + const user = req.user; + await postAPI.sharePost(postId, currentShare, user); + res.redirect("/profile"); + } catch (err) { + req.flash("error", "cannot share, something went wrong"); + res.redirect("/profile"); + } +}; + +const deletePostController = async (req, res) => { + const postId = req.params.postId; + const user = req.user; + try { + await postAPI.deletePost(postId, user); + res.redirect("/profile"); + } catch (err) { + req.flash("error", "cannot delete, something went wrong"); + res.redirect("/profile"); + } +}; + +// Single Post and Comments Controller + +const getSinglePostRender = async (req, res) => { + try { + const postId = req.params.postId; + const post = await Posts.findById(postId).populate("creator").exec(); + const stats = await post.stats.populate("comments"); + const comments = []; + for (let comment of stats.comments) { + comments.push(await comment.populate("commentor")); + } + const pageInfo = { + userId: req.user._id, + title: "Knot - Post", + pagename: "comment", + profilePic: req.user.profilePic_url, + }; + res.render("post", { pageInfo: pageInfo, post: post, comments: comments, messages: req.flash() }); + } catch (err) { + res.redirect("/"); + } +}; + +module.exports = { + createPostRender, + createPostController, + likeCountController, + dislikeCountController, + sharePostController, + deletePostController, + getSinglePostRender, +}; diff --git a/backend/controllers/profileController.js b/backend/controllers/profileController.js new file mode 100644 index 0000000..b80ffa3 --- /dev/null +++ b/backend/controllers/profileController.js @@ -0,0 +1,343 @@ +const userAPI = require("../api/userAPI"); +const postAPI = require("../api/postAPI"); +const profileAPI = require("../api/profileAPI"); +const deleteImage = require("../api/deleteImage"); + +const viewProfileRender = async (req, res) => { + const userData = await userAPI.findUser(req.user); + let profilePic = req.user.profilePic_url; + let posts = await postAPI.findPosts(req.user); + for (let post of posts) { + await post.populate("creator"); + } + + const creatorDetails = { + creator: req.user.firstname + " " + req.user.lastname, + profilePic_url: req.user.profilePic_url, + }; + + res.render("profile", { + activeUser: userData, + user: userData, + profilePic: profilePic, + profilePicLoggedIn: profilePic, + pageTitle: "Knot - Profile", + posts: posts, + creatorDetails: creatorDetails, + pageName: "profile", + messages: req.flash(), + }); +}; + +const viewActivityRender = async (req, res) => { + try { + const userData = await userAPI.findUser(req.user); + let profilePic = req.user.profilePic_url; + const [activities, totalVotes] = await profileAPI.getActivities(req.user); + const creatorDetails = { + creator: req.user.firstname + " " + req.user.lastname, + profilePic_url: req.user.profilePic_url, + }; + res.render("profileActivity", { + activeUser: userData, + user: userData, + profilePic: profilePic, + profilePicLoggedIn: profilePic, + pageTitle: "Knot - Profile", + creatorDetails: creatorDetails, + pageName: "profile-activities", + activities: activities, + totalVotes: totalVotes, + }); + } catch (err) { + req.flash("error", err.message); + res.redirect("/profile"); + } +}; + +const editProfileRender = async (req, res) => { + const userData = await userAPI.findUser(req.user); + let profilePic = req.user.profilePic_url; + res.render("editProfile", { + activeUser: userData, + user: userData, + profilePic: profilePic, + profilePicLoggedIn: profilePic, + pageTitle: "Knot - Profile", + pageName: "profile-edit", + existingdata: userData, + messages: req.flash(), + }); +}; +const editProfileController = async (req, res) => { + const id = req.user._id; + try { + if (req.body.profilePicId && req.user.profilePicId !== "" && !req.user.profilePicId) { + await deleteImage(req.user.profilePicId); + } + } catch (err) { + req.flash("error", err.message); + res.json({ error: err.message }); + } + if(req.body.profilePic === null){ + req.flash("error", "Please upload a valid profile picture"); + res.json({ error: "Please upload a valid profile picture" }); + return; + } + + const postedData = { + points: req.user.points, + }; + if (req.body.dob !== "") { + postedData.date_of_birth = req.body.dob; + } + if (req.body.firstname !== "") { + postedData.firstname = req.body.firstname.replace(/\s/g, ""); + } + if (req.body.lastname !== "") { + postedData.lastname = req.body.lastname.replace(/\s/g, ""); + } + if (req.body.bio !== "") { + postedData.bio = req.body.bio; + } + if (req.body.username !== "") { + postedData.username = req.body.username.replace(/\s/g, ""); + } + if (req.body.profilePic !== "") { + postedData.profilePic_url = req.body.profilePic; + postedData.profilePicId = req.body.profilePicId; + } + if ( + !(req.user.username == postedData.username) && + postedData.username != undefined + ) { + if (postedData.points < 20) { + req.flash( + "error", + "You don't have enough points to change your username" + ); + res.json({ + error: "You don't have enough points to change your username", + }); + return; + } else { + postedData.points -= 20; + } + } + if (postedData.profilePic_url) { + if (postedData.points < 50) { + try { + if (req.body.profilePicId) { + await deleteImage(req.body.profilePicId); + req.flash( + "error", + "You don't have enough points to change your profile picture" + ); + res.json({ + error: + "You don't have enough points to change your profile picture", + }); + } + } catch (err) { + req.flash("error", err.message); + res.json({ error: err.message }); + } + return; + } else { + postedData.points -= 50; + } + } + try { + await userAPI.updateUser(id, postedData); + req.flash("success", "Profile updated successfully"); + res.json({ success: "Profile updated successfully" }); + } catch (err) { + req.flash("error", err.message); + res.json({ error: err.message }); + } +}; +const singleProfileRender = async (req, res) => { + let user = { + _id: req.params.profileId, + }; + if (user._id === String(req.user._id)) { + res.redirect("/profile"); + return; + } + try { + const userData = await userAPI.findUser(user); + const activeUser = req.user; + let profilePic = userData.profilePic_url; + let posts = await postAPI.findPosts(user); + for (let post of posts) { + await post.populate("creator"); + } + const creatorDetails = { + creator: userData.firstname + " " + userData.lastname, + profilePic_url: userData.profilePic_url, + }; + + res.render("profile", { + activeUser, + user: userData, + profilePic: profilePic, + profilePicLoggedIn: req.user.profilePic_url, + pageTitle: "Knot - Profile", + posts: posts, + creatorDetails: creatorDetails, + pageName: "viewProfile", + messages: req.flash(), + }); + } catch (err) { + res.redirect("/"); + } +}; + +const singleProfileActivityRender = async (req, res) => { + let user = { + _id: req.params.profileId, + }; + if (user._id === String(req.user._id)) { + res.redirect("/profile/activity"); + return; + } + try { + const userDataPromise = userAPI.findUser(user); + const activitiesPromise = profileAPI.getActivities(user); + const [userData, [activities, totalVotes]] = await Promise.all([ + userDataPromise, + activitiesPromise, + ]); + const activeUser = req.user; + let profilePic = userData.profilePic_url; + const creatorDetails = { + creator: userData.firstname + " " + userData.lastname, + profilePic_url: userData.profilePic_url, + }; + res.render("profileActivity", { + activeUser, + user: userData, + profilePic: profilePic, + profilePicLoggedIn: req.user.profilePic_url, + pageTitle: "Knot - Profile", + creatorDetails: creatorDetails, + pageName: "viewProfile-activities", + activities: activities, + totalVotes: totalVotes, + }); + } catch (err) { + req.flash("error", err.message); + res.redirect("/profile"); + } +}; + +const followController = async (req, res) => { + const followerId = req.user._id; + const followingId = req.params.followingId; + try { + await profileAPI.followAndUnfollow(followingId, followerId); + res.redirect("back"); + } catch (err) { + req.flash("error", err.message); + res.redirect("back"); + } +}; + +const searchProfileRender = async (req, res) => { + res.render("followers", { + pageTitle: "Knot - Search Profile", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "searching-profile", + user: req.user, + isFollowing: false, + isFollowers: false, + isSearching: true, + currentUser: req.user, + foundUser: [], + searchValue: "", + messages: req.flash(), + }); +}; + +const searchProfileController = async (req, res) => { + if (!req.query.user || req.query.user === "") { + req.flash("error", "Please enter something to search"); + res.render("followers", { + pageTitle: "Knot - Search Profile", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "searching-profile", + user: req.user, + isFollowers: false, + isFollowing: false, + isSearching: true, + currentUser: req.user, + foundUser: [], + searchValue: '', + messages: req.flash(), + }); + return; + } + try { + const foundUser = await profileAPI.searchProfile(req.query.user); + if (foundUser.length > 0) { + res.render("followers", { + pageTitle: "Knot - Search Profile", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "searching-profile", + user: req.user, + isFollowers: false, + isFollowing: false, + isSearching: true, + currentUser: req.user, + foundUser: foundUser, + searchValue: req.query.user, + messages: req.flash(), + }); + return; + } else { + req.flash("error", `No user found that matches '${req.query.user}'`); + res.render("followers", { + pageTitle: "Knot - Search Profile", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "searching-profile", + user: req.user, + isFollowers: false, + isFollowing: false, + isSearching: true, + currentUser: req.user, + foundUser: [], + searchValue: '', + messages: req.flash(), + }); + return; + } + } catch (error) { + req.flash("somethingWrong", "something went wrong, please try again later"); + res.render("followers", { + pageTitle: "Knot - Search Profile", + profilePicLoggedIn: req.user.profilePic_url, + pageName: "searching-profile", + user: req.user, + isFollowers: false, + isFollowing: false, + isSearching: true, + currentUser: req.user, + foundUser: [], + searchValue: '', + messages: req.flash(), + }); + return; + } +}; + +module.exports = { + viewProfileRender, + viewActivityRender, + editProfileRender, + editProfileController, + singleProfileRender, + singleProfileActivityRender, + followController, + searchProfileController, + searchProfileRender, +}; diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js new file mode 100644 index 0000000..32f3267 --- /dev/null +++ b/backend/controllers/userController.js @@ -0,0 +1,67 @@ +const express = require("express"); +const User = require("../models/Users"); +const passportSetup = require("../config/passportConfig"); +const initializeSignup = require("../config/passportLocalConfig"); +const initializeLogin = require("../config/loginConfig"); +const initializeLogout = require("../config/logoutConfig"); + + + +const signUpRender = (req, res) => { + res.render("signUp", {pageTitle: 'Knot - Sign Up', messages: req.flash()}); +}; + +const signUpController = async (req, res) => { + const randomProfileTheme = ["lorelei", "personas", "fun-emoji", "avataaars", "adventurer", "big-ears"] + const userData = { + firstname: req.body.firstname.replace(/\s/g, ""), + lastname: req.body.lastname.replace(/\s/g, ""), + username: req.body.username.replace(/\s/g, ""), + email: req.body.email, + bio: "", + profilePicId: "", + profilePic_url: `https://api.dicebear.com/5.x/${randomProfileTheme[Math.floor(Math.random()*randomProfileTheme.length)]}/svg?seed=${req.body.username}&backgroundColor=ffffff,b6e3f4&backgroundType=gradientLinear`, + }; + try{ + + await initializeSignup(userData, req.body.password, req, res); + } catch(err){ + req.flash("error", err.message); + console.log(err); + }; +}; + + +const loginRender = (req, res) => { + res.render("login",{pageTitle: 'Knot - Login', messages: req.flash()}); +}; + +const loginController = async (req, res) => { + const userData = { + username: req.body.username, + password: req.body.password + } + try{ + + await initializeLogin(userData, req, res); + } catch(err){ + req.flash("error", err.message); + console.log(err); + }; +}; + +const logoutController = (req, res) => { + initializeLogout(req, res); +}; +const forgotPasswordController = (req, res) => { + req.flash("warning", "We are working on this feature."); + res.redirect("/login"); +}; +module.exports = { + signUpController, + signUpRender, + loginController, + loginRender, + logoutController, + forgotPasswordController +}; diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 0000000..d61cf11 --- /dev/null +++ b/backend/index.js @@ -0,0 +1,78 @@ +require("dotenv").config(); +const express = require("express"); +const path = require("path"); +const flash = require("connect-flash"); +const app = express(); +const session = require("express-session"); +const connectDB = require("./config/dbConnection"); +const homeRouter = require("./routes/home"); +const signUpRouter = require("./routes/signup"); +const authRouter = require("./routes/auth"); +const profileRouter = require("./routes/profile"); +const loginRouter = require("./routes/login"); +const logoutRouter = require("./routes/logout"); +const forgotPasswordRouter = require("./routes/forgotpassword"); +const postRouter = require("./routes/post"); +const followersRouter = require("./routes/followers"); +const pollsRouter = require("./routes/polls"); +const challengesRouter = require("./routes/challenges"); +const devApiRouter = require("./routes/devApi"); +const pointsRouter = require("./routes/points"); +const checkAuthorized = require("./middlewares/checkAuth"); +const passportSetup = require("./config/passportConfig"); +const passport = require("passport"); +app.use(express.static(path.join(__dirname, "..", "frontend", "public"))); +app.use(express.static("public")); +app.use(express.json()); +app.set("view engine", "ejs"); +app.set("views", path.join(__dirname, "views")); + +app.use( + express.urlencoded({ + extended: false, + }) +); +app.use(flash()); +app.use( + session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: true, + cookie: { + maxAge: 1000 * 60 * 60 * 24 * 7, + }, + }) +); + +app.use(passport.initialize()); +app.use(passport.session()); + +app.use("/signup", signUpRouter); +app.use("/auth", authRouter); +app.use("/login", loginRouter); +app.use("/forgotpassword", forgotPasswordRouter); +app.use(checkAuthorized); + +app.use("/", homeRouter) +app.use("/", followersRouter); +app.use("/profile", profileRouter); +app.use("/logout", logoutRouter); +app.use("/post", postRouter); +app.use("/polls", pollsRouter); +app.use("/challenges", challengesRouter) +app.use("/api/v1", devApiRouter); +app.use("/points", pointsRouter); + +//use wildcard to catch all routes +app.get("*", (req, res) => { + res.render("404"); +}); + + +(async () => { + await connectDB(process.env.DB_URI); + const PORT = process.env.PORT || 80; +app.listen(PORT, () => { + console.log("listening on port 80"); +}); +})(); diff --git a/backend/middlewares/checkAuth.js b/backend/middlewares/checkAuth.js new file mode 100644 index 0000000..eefc408 --- /dev/null +++ b/backend/middlewares/checkAuth.js @@ -0,0 +1,10 @@ +const checkAuth = (req, res, next) => { + if(req.isAuthenticated()){ + next(); + }else{ + res.redirect('/login'); + } +} + + +module.exports = checkAuth; \ No newline at end of file diff --git a/backend/middlewares/notAuth.js b/backend/middlewares/notAuth.js new file mode 100644 index 0000000..bb55340 --- /dev/null +++ b/backend/middlewares/notAuth.js @@ -0,0 +1,9 @@ +const checkNotAuth = (req, res, next) => { + if(req.isAuthenticated()){ + return res.redirect('/profile'); + } + next(); +} + + +module.exports = checkNotAuth; \ No newline at end of file diff --git a/backend/models/Challenges.js b/backend/models/Challenges.js new file mode 100644 index 0000000..3c8ef28 --- /dev/null +++ b/backend/models/Challenges.js @@ -0,0 +1,12 @@ +const mongoose = require("mongoose") + +const challengesSchema = mongoose.Schema({ + creatorId: { type: mongoose.Schema.Types.ObjectId,ref: "User", required: true }, + content: { type: String, required: true }, + description: { type: String, required: true }, + timestamp: { type: Date, default: Date.now }, + participators:[{type:mongoose.Schema.Types.ObjectId,ref: "User"}], + duration:{type: Date} +}); + +module.exports = mongoose.model("Challenge", challengesSchema); \ No newline at end of file diff --git a/backend/models/Comments.js b/backend/models/Comments.js new file mode 100644 index 0000000..dfc077f --- /dev/null +++ b/backend/models/Comments.js @@ -0,0 +1,22 @@ +const mongoose = require("mongoose"); +const schema = mongoose.Schema; + +const commentSchema = mongoose.Schema({ + content: { type: String, required: true }, + commentor: { type: schema.Types.ObjectId, ref: "User", required: true }, + timestamp: { type: Date, default: Date.now }, + stats: { + upvotes: { type: Number, default: 0 }, + downvotes: { type: Number, default: 0 }, + }, + replies: [ + { + content: { type: String, required: true }, + author: { type: schema.Types.ObjectId, ref: "User", required: true }, + } + ] + + +}); + +module.exports = mongoose.model("Comment", commentSchema); \ No newline at end of file diff --git a/backend/models/Polls.js b/backend/models/Polls.js new file mode 100644 index 0000000..3454f1c --- /dev/null +++ b/backend/models/Polls.js @@ -0,0 +1,15 @@ +const mongoose = require("mongoose") + +const pollsSchema = mongoose.Schema({ + creatorId: { type: mongoose.Schema.Types.ObjectId,ref: "User", required: true }, + content: { type: String, required: true }, + timestamp: { type: Date, default: Date.now } , + options: [ + { + optionsName: String, + voted_by: [{type:mongoose.Schema.Types.ObjectId,ref: "User" }] + } + ] +}); + +module.exports = mongoose.model("Poll", pollsSchema); \ No newline at end of file diff --git a/backend/models/Posts.js b/backend/models/Posts.js new file mode 100644 index 0000000..b8c0659 --- /dev/null +++ b/backend/models/Posts.js @@ -0,0 +1,26 @@ +const mongoose = require("mongoose"); +const schema = mongoose.Schema; + +const postSchema = mongoose.Schema( + { + content: { type: String }, + imagePath: { type: String }, + imageId: { type: String }, + creator: { + type: schema.Types.ObjectId, + ref: "User", + required: true, + unique: false, + }, + timestamp: { type: Date, default: Date.now }, + stats: { + upvoted_by: { type: Array, default: [] }, + downvoted_by: { type: Array, default: [] }, + shares: { type: Number, default: 0 }, + comments: [{ type: schema.Types.ObjectId, ref: "Comment" }], + }, + }, + { timestamps: true } +); + +module.exports = mongoose.model("Post", postSchema); diff --git a/backend/models/Users.js b/backend/models/Users.js new file mode 100644 index 0000000..e039f23 --- /dev/null +++ b/backend/models/Users.js @@ -0,0 +1,64 @@ +const mongoose = require("mongoose"); +const passportLocalMongoose = require("passport-local-mongoose"); +const findOrCreate = require("mongoose-findorcreate"); + +let UserSchema = new mongoose.Schema( + { + firstname: { + type: String, + required: true, + match: [/^[a-zA-Z0-9]+$/, "is invalid"], + index: {type: "text"}, + }, + lastname: { + type: String, + required: true, + match: [/^[a-zA-Z0-9]+$/, "is invalid"], + index: {type: "text"}, + }, + username: { + type: String, + lowercase: true, + required: true, + unique: true, + match: [/^[a-zA-Z0-9]+$/, "is invalid"], + index: {type: "text"}, + }, + googleId: { + type: String, + }, + password: { + type: String, + }, + bio: { + type: String, + }, + date_of_birth: { type: Date }, + email: { + type: String, + required: true, + index: true, + }, + profilePic_url: { + type: String, + }, + profilePicId:{type:String}, + points: { + type: Number, + default: 0, + }, + sharedPosts: [{ type: mongoose.Schema.Types.ObjectId, ref: "Post" }], + participatedChallenges: [{ type: mongoose.Schema.Types.ObjectId, ref: "Challenge" }], + posts: [{ type: mongoose.Schema.Types.ObjectId, ref: "Post" }], + challenges: [{ type: mongoose.Schema.Types.ObjectId, ref: "Challenge" }], + polls: [{ type: mongoose.Schema.Types.ObjectId, ref: "Poll" }], + followers: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], + following: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], + }, + { + timestamps: true, + } +); +UserSchema.plugin(passportLocalMongoose); +UserSchema.plugin(findOrCreate); +module.exports = mongoose.model("User", UserSchema); diff --git a/backend/routes/auth.js b/backend/routes/auth.js new file mode 100644 index 0000000..b503471 --- /dev/null +++ b/backend/routes/auth.js @@ -0,0 +1,18 @@ +const router = require("express").Router(); +const passport = require("passport"); +const checkNotAuthorized = require("../middlewares/notAuth.js"); + +router.get( + "/google", + passport.authenticate("google", { scope: ["profile", "email"] }) +); + +router.get( + "/google/login", + passport.authenticate("google", { failureRedirect: "/login" }), + (req, res) => { + res.redirect("/"); + } +); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/challenges.js b/backend/routes/challenges.js new file mode 100644 index 0000000..3a96fe1 --- /dev/null +++ b/backend/routes/challenges.js @@ -0,0 +1,11 @@ +const router = require('express').Router(); +const challengesController = require('../controllers/challengesController'); +const checkAuthorized = require("../middlewares/checkAuth.js"); + +router.route('/').get(checkAuthorized, challengesController.challengesRender); +router.route('/add').get(checkAuthorized, challengesController.createChallengeRender).post(checkAuthorized, challengesController.createChallengeController); +router.get('/:id', checkAuthorized, challengesController.viewOneChallengeRender); +router.get('/participate/:id', checkAuthorized, challengesController.participateInChallengeRender); +router.route('/delete/:challengeId').get(challengesController.deleteChallengeController); + +module.exports = router; diff --git a/backend/routes/devApi.js b/backend/routes/devApi.js new file mode 100644 index 0000000..61beba6 --- /dev/null +++ b/backend/routes/devApi.js @@ -0,0 +1,10 @@ +const router = require("express").Router(); +const controller = require("../controllers/postController"); +const getSignature = require("../api/getSignature"); + +router.route("/post/upvote").post(controller.likeCountController); +router.route("/post/downvote").post(controller.dislikeCountController); +router.route("/post/share").post(controller.sharePostController); +router.route("/get-signature").post(getSignature); + +module.exports = router; diff --git a/backend/routes/followers.js b/backend/routes/followers.js new file mode 100644 index 0000000..fd7ba78 --- /dev/null +++ b/backend/routes/followers.js @@ -0,0 +1,9 @@ +const router = require('express').Router(); +const controller = require('../controllers/followersController'); + +router.route('/followers').get(controller.followersRender); +router.route('/following').get(controller.followingRender); +router.route('/followers/:profileId').get(controller.viewProfileFollowersRender); +router.route('/following/:profileId').get(controller.viewProfileFollowingRender); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/forgotpassword.js b/backend/routes/forgotpassword.js new file mode 100644 index 0000000..9fa8fdc --- /dev/null +++ b/backend/routes/forgotpassword.js @@ -0,0 +1,10 @@ +const router = require('express').Router(); +const controller = require('../controllers/userController'); +const checkNotAuthorized = require("../middlewares/notAuth.js"); + + +router.route('/').get(checkNotAuthorized, controller.forgotPasswordController); + +module.exports = router; + + diff --git a/backend/routes/home.js b/backend/routes/home.js new file mode 100644 index 0000000..5a06d3c --- /dev/null +++ b/backend/routes/home.js @@ -0,0 +1,6 @@ +const router = require('express').Router(); +const home = require('../controllers/homeController'); + +router.get('/', home.feedController); + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/login.js b/backend/routes/login.js new file mode 100644 index 0000000..d7691ef --- /dev/null +++ b/backend/routes/login.js @@ -0,0 +1,10 @@ +const router = require('express').Router(); +const controller = require('../controllers/userController'); +const checkNotAuthorized = require("../middlewares/notAuth.js"); + + +router.route('/').get(checkNotAuthorized, controller.loginRender).post(controller.loginController); + +module.exports = router; + + diff --git a/backend/routes/logout.js b/backend/routes/logout.js new file mode 100644 index 0000000..32722a2 --- /dev/null +++ b/backend/routes/logout.js @@ -0,0 +1,8 @@ +const router = require('express').Router(); +const controller = require('../controllers/userController'); +const checkAuthorized = require("../middlewares/checkAuth.js"); + + +router.route('/').post(checkAuthorized, controller.logoutController); + +module.exports = router; diff --git a/backend/routes/points.js b/backend/routes/points.js new file mode 100644 index 0000000..79a8d74 --- /dev/null +++ b/backend/routes/points.js @@ -0,0 +1,7 @@ +const router = require('express').Router(); +const points = require('../controllers/pointsController'); + +router.get('/', points.pointsController); + + +module.exports = router; \ No newline at end of file diff --git a/backend/routes/polls.js b/backend/routes/polls.js new file mode 100644 index 0000000..d804989 --- /dev/null +++ b/backend/routes/polls.js @@ -0,0 +1,12 @@ +const router = require('express').Router(); +const pollsController = require('../controllers/pollController'); +const checkAuthorized = require("../middlewares/checkAuth.js"); + +router.route('/').get(pollsController.pollsRender) +router.post('/vote',pollsController.voteController); +router.route('/add').get(pollsController.createPollsRender).post(checkAuthorized, pollsController.createPollsController); +router.route('/delete/:pollId').get(pollsController.deletePollController); + + + +module.exports = router; diff --git a/backend/routes/post.js b/backend/routes/post.js new file mode 100644 index 0000000..611d7fc --- /dev/null +++ b/backend/routes/post.js @@ -0,0 +1,12 @@ +const router = require('express').Router(); +const controller = require('../controllers/postController'); +const {addCommentController,deleteCommentController} = require('../controllers/commentController') +const checkAuthorized = require("../middlewares/checkAuth.js"); + + +router.route('/add').get(checkAuthorized, controller.createPostRender).post(checkAuthorized, controller.createPostController); +router.route('/delete/:postId').get(checkAuthorized, controller.deletePostController); +router.route('/:postId').get(checkAuthorized, controller.getSinglePostRender) +router.route("/:postId/comment").post(addCommentController).delete(deleteCommentController) + +module.exports = router; diff --git a/backend/routes/profile.js b/backend/routes/profile.js new file mode 100644 index 0000000..10db487 --- /dev/null +++ b/backend/routes/profile.js @@ -0,0 +1,16 @@ + +const router = require('express').Router(); +const controller = require('../controllers/profileController'); +const checkAuthorized = require("../middlewares/checkAuth.js"); + + +router.route('/').get(checkAuthorized, controller.viewProfileRender); +router.get('/searchuser', controller.searchProfileRender); +router.get('/search', controller.searchProfileController); +router.route('/activity').get(checkAuthorized, controller.viewActivityRender); +router.route("/update").get(controller.editProfileRender).post(controller.editProfileController); +router.get('/follow/:followingId',checkAuthorized, controller.followController) +router.route('/:profileId').get(checkAuthorized, controller.singleProfileRender); +router.route('/activity/:profileId').get(checkAuthorized, controller.singleProfileActivityRender); + +module.exports = router; diff --git a/backend/routes/signup.js b/backend/routes/signup.js new file mode 100644 index 0000000..5d683e9 --- /dev/null +++ b/backend/routes/signup.js @@ -0,0 +1,13 @@ +const router = require("express").Router(); +const passport = require("passport"); +const checkNotAuthorized = require("../middlewares/notAuth.js"); + +const signUp = require("../controllers/userController"); +router + .route("/") + .get(checkNotAuthorized, signUp.signUpRender) + .post(signUp.signUpController); + + +module.exports = router; + diff --git a/backend/views/404.ejs b/backend/views/404.ejs new file mode 100644 index 0000000..4be560f --- /dev/null +++ b/backend/views/404.ejs @@ -0,0 +1,25 @@ + + +
+ + + + + +The page you are looking for, we did Knot design that yet!
+ Go to Home +