├── CONTRIBUTING.md ├── Procfile ├── .gitignore ├── public ├── logo.png ├── partials-css │ └── nav.css ├── default-profile.jpg ├── profile-background.jpg ├── sound.js ├── sounds │ └── notification-sound.mp3 ├── profile.css ├── login-register.css ├── shared.css ├── friendShared.js ├── search.js ├── sharedUI.js └── shared.js ├── .vscode └── settings.json ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── models ├── like.js ├── friendship.js ├── comment.js ├── post.js ├── notification.js └── user.js ├── index.js ├── db ├── search.js ├── like.js ├── friendship.js ├── comment.js ├── post.js ├── notification.js └── user.js ├── utils └── validation.js ├── db.config.js ├── views ├── components │ ├── optionsDropDown.ejs │ ├── deletePostModal.ejs │ ├── editPostModal.ejs │ ├── createPost.ejs │ ├── editProfileModal.ejs │ ├── post.ejs │ └── profileCard.ejs ├── all.ejs ├── partials │ ├── head.ejs │ ├── footer.ejs │ └── nav.ejs ├── login.ejs ├── register.ejs ├── post-display.ejs ├── profile.ejs └── home.ejs ├── middleware └── ensureAuth.js ├── package.json ├── controlers ├── user.js ├── friendship.js ├── like.js ├── post.js ├── comment.js ├── auth.js ├── page.js └── notification.js ├── LICENSE ├── server.config.js ├── passport.config.js ├── README.md ├── webSocketHandler.js ├── router.js └── CODE_OF_CONDUCT.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ! 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonardpepa/Pepaverse/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/partials-css/nav.css: -------------------------------------------------------------------------------- 1 | .logo{ 2 | width: 100%; 3 | max-width: 85px; 4 | height: auto; 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecl.launchConfiguration": "not found", 3 | "ecl.targetCluster": {} 4 | } 5 | -------------------------------------------------------------------------------- /public/default-profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonardpepa/Pepaverse/HEAD/public/default-profile.jpg -------------------------------------------------------------------------------- /public/profile-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonardpepa/Pepaverse/HEAD/public/profile-background.jpg -------------------------------------------------------------------------------- /public/sound.js: -------------------------------------------------------------------------------- 1 | const playNotification = () => { 2 | new Audio('./sounds/notification-sound.mp3').play(); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /public/sounds/notification-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leonardpepa/Pepaverse/HEAD/public/sounds/notification-sound.mp3 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/profile.css: -------------------------------------------------------------------------------- 1 | .profile-card{ 2 | background-image: url("/profile-background.jpg") ; 3 | background-repeat: no-repeat; 4 | background-position: center; 5 | background-size: cover; 6 | } 7 | -------------------------------------------------------------------------------- /public/login-register.css: -------------------------------------------------------------------------------- 1 | .submit-btn-container { 2 | margin: 0.5rem 0; 3 | } 4 | .submit-btn-container *{ 5 | margin: 0.5rem 0; 6 | } 7 | #main{ 8 | max-width: 45%; 9 | margin: 2rem auto 0 auto; 10 | } 11 | 12 | @media (max-width: 600px) { 13 | #main{ 14 | max-width: 100%; 15 | margin: 2rem auto 0 auto; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /models/like.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | 3 | const likeSchema = new Mongoose.Schema({ 4 | postId: { 5 | type: Mongoose.SchemaTypes.ObjectId, 6 | ref: "Post", 7 | }, 8 | userId: { 9 | type: Mongoose.SchemaTypes.ObjectId, 10 | ref: "User", 11 | }, 12 | }); 13 | 14 | const Like = Mongoose.model("Like", likeSchema); 15 | 16 | module.exports = Like; 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | const { server, app } = require("./server.config"); 4 | const router = require("./router.js"); 5 | const { initdb } = require("./db.config"); 6 | 7 | initdb(); 8 | require("./webSocketHandler")(); 9 | app.use("/", router); 10 | 11 | const PORT = process.env.PORT || 3000; 12 | 13 | server.listen(PORT, (req, res) => { 14 | console.log(`App is listening on port ${PORT}`); 15 | }); 16 | -------------------------------------------------------------------------------- /db/search.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user"); 2 | 3 | const searchUsers = async (searchQuery) => { 4 | try { 5 | if (!searchQuery) { 6 | return []; 7 | } 8 | const users = await User.find({ 9 | searchName: { $regex: ".*" + searchQuery.toLowerCase() + ".*" }, 10 | }).limit(5); 11 | return await users; 12 | } catch (error) { 13 | console.log(error); 14 | } 15 | }; 16 | 17 | module.exports = { searchUsers }; 18 | -------------------------------------------------------------------------------- /utils/validation.js: -------------------------------------------------------------------------------- 1 | function validateEmail(email) { 2 | if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) { 3 | return true; 4 | } 5 | return false; 6 | } 7 | 8 | function validatePassword(password, confirm) { 9 | if (!/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,}$/.test(password)) { 10 | return false; 11 | } 12 | if (password === confirm) { 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | module.exports = { validateEmail, validatePassword }; 19 | -------------------------------------------------------------------------------- /db.config.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | Mongoose.set('strictQuery', false); 3 | const initdb = async () => { 4 | try { 5 | return await Mongoose.connect(process.env.MONGO_DB_URL, { 6 | // useCreateIndex: true, 7 | useNewUrlParser: true, 8 | // useFindAndModify: false, 9 | useUnifiedTopology: true, 10 | }); 11 | } catch (error) { 12 | console.log(error); 13 | } 14 | }; 15 | 16 | 17 | module.exports = { initdb }; 18 | -------------------------------------------------------------------------------- /models/friendship.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | 3 | const friendshipSchema = new Mongoose.Schema({ 4 | author: { 5 | type: Mongoose.Schema.Types.ObjectId, 6 | ref: "User", 7 | }, 8 | receiver: { 9 | type: Mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | status: { 13 | type: String, 14 | }, 15 | notification: { 16 | type: Mongoose.Schema.Types.ObjectId, 17 | ref: "Notification", 18 | }, 19 | }); 20 | 21 | const Friendship = Mongoose.model("Friendship", friendshipSchema); 22 | 23 | module.exports = Friendship; 24 | -------------------------------------------------------------------------------- /views/components/optionsDropDown.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | 3 | const commentSchema = new Mongoose.Schema({ 4 | author: { 5 | type: Mongoose.Schema.Types.ObjectId, 6 | ref: "User", 7 | }, 8 | post: { 9 | type: Mongoose.Schema.Types.ObjectId, 10 | ref: "Post", 11 | }, 12 | createdAt: { 13 | type: Date, 14 | default: Date.now, 15 | }, 16 | updatedAt: { 17 | type: Date, 18 | default: Date.now, 19 | }, 20 | content: { 21 | type: String, 22 | }, 23 | }); 24 | 25 | const Comment = Mongoose.model("Comment", commentSchema); 26 | 27 | module.exports = Comment; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /middleware/ensureAuth.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const jwt = require('jsonwebtoken'); 3 | const User = require("../models/user"); 4 | 5 | const ensureAuth = (req, res, next) => { 6 | if (req.isAuthenticated()) { 7 | next(); 8 | } else { 9 | res.redirect("/"); 10 | } 11 | }; 12 | 13 | const passportAuthenticateLocal = (req, res) => { 14 | passport.authenticate("local", { 15 | failureRedirect: "/login?e=Email or Password are incorrect", 16 | })(req, res, async () => { 17 | const token = jwt.sign({ user: {username: req.user.username, _id: req.user._id } }, process.env.SECRET); 18 | res.cookie('token', token); 19 | res.redirect("/"); 20 | }); 21 | }; 22 | 23 | module.exports = { ensureAuth, passportAuthenticateLocal }; 24 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | 3 | const postSchema = new Mongoose.Schema({ 4 | content: { 5 | type: String, 6 | required: [true, "Post cannot be empty"], 7 | }, 8 | createdAt: { 9 | type: Date, 10 | default: Date.now, 11 | }, 12 | updatedAt: { 13 | type: Date, 14 | default: Date.now, 15 | }, 16 | author: { 17 | type: Mongoose.SchemaTypes.ObjectId, 18 | ref: "User", 19 | }, 20 | likes: [ 21 | { 22 | type: Mongoose.Schema.Types.ObjectId, 23 | ref: "Like", 24 | }, 25 | ], 26 | comments: [ 27 | { 28 | type: Mongoose.Schema.Types.ObjectId, 29 | ref: "Comment", 30 | }, 31 | ], 32 | }); 33 | 34 | const Post = Mongoose.model("Post", postSchema); 35 | 36 | module.exports = Post; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pepaverse", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "connect-mongo": "^4.6.0", 15 | "cookie-parser": "^1.4.6", 16 | "dotenv": "^10.0.0", 17 | "ejs": "^3.1.6", 18 | "express": "^4.17.1", 19 | "express-session": "^1.17.2", 20 | "jsonwebtoken": "^9.0.0", 21 | "mongoose": "^6.0.9", 22 | "mongoose-findorcreate": "^3.0.0", 23 | "morgan": "^1.10.0", 24 | "passport": "^0.6.0", 25 | "passport-google-oauth20": "^2.0.0", 26 | "passport-local": "^1.0.0", 27 | "passport-local-mongoose": "^6.1.0", 28 | "ws": "^8.11.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /views/components/deletePostModal.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /controlers/user.js: -------------------------------------------------------------------------------- 1 | const { updateUser } = require("../db/user"); 2 | const { searchUsers } = require("../db/search"); 3 | 4 | const userController = { 5 | update: async (req, res, next) => { 6 | let { profileUrl, description } = req.body; 7 | if (description === "") { 8 | description = "No description is provided"; 9 | } 10 | 11 | if (profileUrl === "") { 12 | profileUrl = "/default-profile.jpg"; 13 | } 14 | 15 | const user = await updateUser(req.user._id, { description, profileUrl }); 16 | if (await user) { 17 | return res.json({ 18 | ok: true, 19 | }); 20 | } else { 21 | return res.json({ 22 | ok: false, 23 | }); 24 | } 25 | }, 26 | search: async (req, res, next) => { 27 | try { 28 | const result = await searchUsers(req.body.search); 29 | return await res.json(result); 30 | } catch (error) { 31 | console.log(error); 32 | } 33 | }, 34 | }; 35 | 36 | module.exports = userController; 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /views/all.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/head", {user: user}) %> 2 | 3 | 4 | 5 | <%- include('partials/nav', {user: user}); %> 6 |
7 |
8 | <% allUsers.forEach(user => { %> 9 | 15 | <% }) %> 16 |
17 |
18 | 19 | <%- include("partials/footer") %> -------------------------------------------------------------------------------- /models/notification.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | 3 | const notificationSchema = new Mongoose.Schema( 4 | { 5 | author: { 6 | type: Mongoose.Schema.Types.ObjectId, 7 | ref: "User", 8 | }, 9 | receiver: { 10 | type: Mongoose.Schema.Types.ObjectId, 11 | ref: "User", 12 | }, 13 | seen: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | post: { 18 | type: Mongoose.Schema.Types.ObjectId, 19 | ref: "Post", 20 | }, 21 | type: { 22 | type: String, 23 | }, 24 | status: { 25 | type: String, 26 | default: "pending" 27 | }, 28 | like: { 29 | type: Mongoose.Schema.Types.ObjectId, 30 | ref: "Like", 31 | }, 32 | comment: { 33 | type: Mongoose.Schema.Types.ObjectId, 34 | ref: "Comment", 35 | }, 36 | friendship: { 37 | type: Mongoose.Schema.Types.ObjectId, 38 | ref: "Friendship", 39 | } 40 | }, 41 | { timestamps: true } 42 | ); 43 | 44 | const Notification = Mongoose.model("Notification", notificationSchema); 45 | 46 | module.exports = Notification; 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Leonard Pepa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /views/components/editPostModal.ejs: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /server.config.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | 4 | const ejs = require("ejs"); 5 | const session = require("express-session"); 6 | const cookieParser = require('cookie-parser'); 7 | const morgan = require("morgan"); 8 | const http = require("http"); 9 | const MongoStore = require('connect-mongo'); 10 | 11 | 12 | app.set("view engine", "ejs"); 13 | app.use(express.json()); 14 | app.use(cookieParser()); 15 | app.use(express.urlencoded({ extended: true })); 16 | app.use(morgan("dev")); 17 | app.use(express.static(__dirname + "/public")); 18 | app.use( 19 | session({ 20 | secret: process.env.SECRET, 21 | resave: false, 22 | saveUninitialized: false, 23 | cookie: { 24 | httpOnly: true, 25 | }, 26 | store: MongoStore.create({ 27 | mongoUrl: process.env.MONGO_DB_URL, 28 | crypto: { 29 | secret: process.env.SECRET 30 | } 31 | }) 32 | }) 33 | ); 34 | 35 | const { passport } = require("./passport.config"); 36 | 37 | 38 | app.use(passport.initialize()); 39 | 40 | app.use(passport.session()); 41 | 42 | const server = http.createServer(app); 43 | 44 | module.exports = { server, app, express }; 45 | -------------------------------------------------------------------------------- /public/shared.css: -------------------------------------------------------------------------------- 1 | #main { 2 | margin: 1rem auto; 3 | } 4 | 5 | .create-post-img{ 6 | border-radius: 50%; 7 | max-width: 60px; 8 | margin-left: 18%; 9 | } 10 | 11 | .post-img{ 12 | border-radius: 50%; 13 | max-width: 87px; 14 | } 15 | 16 | .create-post-name{ 17 | width: 100%; 18 | min-width: max-content; 19 | margin: 0 1.2rem; 20 | text-decoration: none; 21 | } 22 | 23 | h5:hover{ 24 | color: rgb(73, 73, 199); 25 | } 26 | 27 | 28 | 29 | .post-name{ 30 | text-decoration: none; 31 | } 32 | 33 | .create-post-name:visited, .post-name{ 34 | color: black; 35 | } 36 | 37 | .like:hover, .comment-btn:hover, .share:hover{ 38 | transform: scale(1.1); 39 | cursor: pointer; 40 | box-shadow: 0 1rem 3rem rgba(0.1, .175);; 41 | transition: all 0.3s; 42 | } 43 | 44 | .hidden{ 45 | visibility: hidden; 46 | display: none; 47 | width: 0; 48 | height: 0; 49 | } 50 | 51 | .option{ 52 | width: max-content; 53 | padding: 0.5rem; 54 | } 55 | 56 | .option:hover{ 57 | background-color: rgb(240, 240, 240); 58 | cursor: pointer; 59 | transition: all 0.3s linear; 60 | } 61 | 62 | .pointer:hover{ 63 | cursor: pointer; 64 | } -------------------------------------------------------------------------------- /controlers/friendship.js: -------------------------------------------------------------------------------- 1 | const { acceptFriendship, deleteFriendship, undoFriendship } = require("../db/friendship"); 2 | 3 | const friendshipController = { 4 | accept: async (req, res) => { 5 | const friendshipId = req.body.friendshipId; 6 | 7 | const friendship = await acceptFriendship(friendshipId); 8 | 9 | if (!friendship) { 10 | return res.json({ 11 | ok: false, 12 | }); 13 | } 14 | return res.json({ 15 | ok: true, 16 | }); 17 | }, 18 | delete: async (req, res) => { 19 | const friendshipId = req.body.friendshipId; 20 | 21 | const friendship = await deleteFriendship(friendshipId); 22 | if (!friendship) { 23 | return res.json({ 24 | ok: false, 25 | }); 26 | } 27 | return res.json({ 28 | ok: true, 29 | }); 30 | }, 31 | undo: async (req, res) => { 32 | const friendshipId = req.body.friendshipId; 33 | 34 | const friendship = await undoFriendship(friendshipId); 35 | if (!friendship) { 36 | return res.json({ 37 | ok: false, 38 | }); 39 | } 40 | return res.json({ 41 | ok: true, 42 | }); 43 | } 44 | }; 45 | 46 | module.exports = friendshipController; 47 | -------------------------------------------------------------------------------- /views/components/createPost.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | Users profile 8 | 9 |
<%= user.name %>
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /controlers/like.js: -------------------------------------------------------------------------------- 1 | const { 2 | createLike, 3 | deleteLike, 4 | findLikeByUserIdandPostId, 5 | } = require("../db/like"); 6 | const { getPostById } = require("../db/post"); 7 | 8 | const likeController = { 9 | create: async (req, res, next) => { 10 | const user = req.user; 11 | const postId = req.body.postId; 12 | 13 | const likeExists = await findLikeByUserIdandPostId(user._id, postId); 14 | 15 | if (likeExists) { 16 | return await res.json({ 17 | n: -1, 18 | message: "error", 19 | ok: false, 20 | liked: false, 21 | }); 22 | } 23 | 24 | const like = await createLike(user._id, postId); 25 | if (like) { 26 | return await res.json({ 27 | n: like.postId.likes.length, 28 | message: "Like Added", 29 | ok: true, 30 | liked: true, 31 | like: like._id, 32 | }); 33 | } 34 | }, 35 | delete: async (req, res, next) => { 36 | const postId = req.body.postId; 37 | const userId = req.user._id; 38 | 39 | const like = await findLikeByUserIdandPostId(userId, postId); 40 | if (like) { 41 | const deletedLike = await deleteLike(like._id); 42 | const post = await getPostById(postId); 43 | return await res.json({ 44 | n: post.likes.length, 45 | ok: true, 46 | liked: false, 47 | like: like._id, 48 | }); 49 | } else { 50 | return await res.json({ 51 | n: -1, 52 | ok: false, 53 | liked: false, 54 | }); 55 | } 56 | }, 57 | }; 58 | 59 | module.exports = likeController; 60 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require("mongoose"); 2 | const passportLocalMongoose = require("passport-local-mongoose"); 3 | const findOrCreate = require("mongoose-findorcreate"); 4 | 5 | const userSchema = new Mongoose.Schema({ 6 | //email 7 | username: { 8 | type: String, 9 | required: true, 10 | unique: true, 11 | }, 12 | password: { 13 | type: String, 14 | }, 15 | name: { 16 | type: String, 17 | unique: true, 18 | }, 19 | googleId: { 20 | type: String, 21 | }, 22 | profileUrl: { 23 | type: String, 24 | }, 25 | friends: [ 26 | { 27 | type: Mongoose.Schema.Types.ObjectId, 28 | ref: "User", 29 | }, 30 | ], 31 | description: { 32 | type: String, 33 | }, 34 | likedPosts: [ 35 | { 36 | type: Mongoose.Schema.Types.ObjectId, 37 | ref: "Post", 38 | }, 39 | ], 40 | likes: [ 41 | { 42 | type: Mongoose.Schema.Types.ObjectId, 43 | ref: "Like", 44 | }, 45 | ], 46 | comments: [ 47 | { 48 | type: Mongoose.Schema.Types.ObjectId, 49 | ref: "Comment", 50 | }, 51 | ], 52 | posts: [ 53 | { 54 | type: Mongoose.Schema.Types.ObjectId, 55 | ref: "Post", 56 | }, 57 | ], 58 | notifications: [ 59 | { 60 | type: Mongoose.Schema.Types.ObjectId, 61 | ref: "Notification", 62 | }, 63 | ], 64 | searchName: { 65 | type: String, 66 | }, 67 | }); 68 | 69 | userSchema.plugin(passportLocalMongoose); 70 | userSchema.plugin(findOrCreate); 71 | const User = new Mongoose.model("User", userSchema); 72 | 73 | module.exports = User; 74 | -------------------------------------------------------------------------------- /passport.config.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const User = require("./models/user"); 3 | const GoogleStrategy = require("passport-google-oauth20").Strategy; 4 | require("dotenv").config(); 5 | 6 | passport.use(User.createStrategy()); 7 | 8 | passport.serializeUser(function (user, done) { 9 | done(null, user.id); 10 | }); 11 | 12 | passport.deserializeUser(function (id, done) { 13 | User.findById(id, function (err, user) { 14 | done(err, user); 15 | }); 16 | }); 17 | 18 | passport.use( 19 | new GoogleStrategy( 20 | { 21 | clientID: process.env.GOOGLE_CLIENT_ID, 22 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 23 | callbackURL: `${process.env.FRONTEND_URL}/auth/google/home`, 24 | userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo", 25 | }, 26 | function (accessToken, refreshToken, profile, cb) { 27 | User.findOne({ username: profile.emails[0].value }, (err, found) => { 28 | if (err) { 29 | console.log(err); 30 | } 31 | 32 | if (found) { 33 | return cb(err, found); 34 | } 35 | User.findOrCreate( 36 | { 37 | googleId: profile.id, 38 | username: profile.emails[0].value, 39 | name: profile.displayName, 40 | profileUrl: profile.photos[0].value, 41 | searchName: profile.displayName.toLowerCase(), 42 | }, 43 | function (err, user) { 44 | return cb(err, user); 45 | } 46 | ); 47 | }); 48 | } 49 | ) 50 | ); 51 | 52 | module.exports = { passport }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pepaverse 2 | 3 | # Pepaverse is a social network build with nodejs, mongoDB, passportjs and web sockets 4 | 5 | # it started as my first attempt to create a chat application but now has been evolved to my fist attempt creating my own social network to share with my friends 6 | 7 | # Use Cases ( that works for now ) 8 | 9 | * Login-Register ( via email-password or google-auth20 ) 10 | * Edit Profile ( profile picture url and description ) 11 | * Send Friend Request 12 | * Accept Friend Request 13 | * Delete Friend Request 14 | * Create Post ( only text for now ) 15 | * Delete Post 16 | * Delete Comment 17 | * Update Post 18 | * Update Comment 19 | * Like Post ( any users post ) 20 | * Notifications (like, comment, friend request) in real time using web sockets 21 | * Search Users ( via name ) 22 | * Logout 23 | 24 | # Soon to be implemented 25 | * Live Notifications (DONE!) 26 | * Private Chat 27 | 28 | # Local Installation 29 | 30 | you need to add and configure an .env file as follows 31 | * PORT=< port number > 32 | * MONGO_DB_URL=< any mongodb url you prefer> 33 | * GOOGLE_CLIENT_ID=< your google id privided by google for the application > 34 | * GOOGLE_CLIENT_SECRET=< your google secret privided by google for the application > 35 | * SECRET=< secret string for the cookie and jwt > 36 | * FRONTEND_URL=< http://localhost:< port > for local or a hosted url with your fronted> 37 | 38 | ```terminal 39 | git clone https://github.com/Leonardpepa/Pepaverse.git 40 | cd Pepaverse/ 41 | npm install 42 | node index.js 43 | ``` 44 | * source for ui components: [Bootsrap](https://getbootstrap.com/) 45 | * source for icons: [Font Awesome](https://fontawesome.com/) 46 | -------------------------------------------------------------------------------- /controlers/post.js: -------------------------------------------------------------------------------- 1 | const { 2 | getPostById, 3 | createPost, 4 | deletePost, 5 | updatePost, 6 | } = require("../db/post"); 7 | 8 | const postcontroller = { 9 | create: async (req, res) => { 10 | try { 11 | const content = req.body.postTextContent; 12 | const author = req.user._id; 13 | 14 | const post = await createPost(content, author); 15 | 16 | return await res.redirect("/"); 17 | } catch (error) { 18 | console.log(error); 19 | } 20 | }, 21 | delete: async (req, res) => { 22 | console.log("yes"); 23 | try { 24 | const postId = req.body.postId; 25 | 26 | const author = (await getPostById(postId)).author._id; 27 | 28 | if (author.toString() !== req.user._id.toString()) { 29 | return res.json({ ok: false }); 30 | } 31 | 32 | const deletedPost = await deletePost(postId); 33 | 34 | if (deletePost) { 35 | return res.json({ ok: true }); 36 | } 37 | 38 | return res.json({ ok: false }); 39 | } catch (error) { 40 | console.log(error); 41 | } 42 | }, 43 | 44 | update: async (req, res) => { 45 | const content = req.body.content; 46 | const postId = req.body.postId; 47 | 48 | const author = (await getPostById(postId)).author._id; 49 | 50 | if (author.toString() !== req.user._id.toString()) { 51 | return res.json({ ok: false }); 52 | } 53 | 54 | const post = await updatePost(postId, { 55 | content: content, 56 | updatedAt: Date.now(), 57 | }); 58 | 59 | 60 | if (!post) { 61 | return res.json({ ok: false, post }); 62 | } 63 | return res.json({ ok: true, post }); 64 | }, 65 | }; 66 | 67 | module.exports = postcontroller; 68 | -------------------------------------------------------------------------------- /controlers/comment.js: -------------------------------------------------------------------------------- 1 | const { 2 | getCommentById, 3 | createComment, 4 | deleteComment, 5 | getCommentsByPostId, 6 | updateComment, 7 | } = require("../db/comment"); 8 | 9 | const commentController = { 10 | create: async (req, res, next) => { 11 | const comment = await createComment( 12 | req.body.postId, 13 | req.user._id, 14 | req.body.content 15 | ); 16 | if (comment) { 17 | return res.json({ 18 | ok: true, 19 | comment, 20 | }); 21 | } 22 | return res.json({ 23 | ok: false, 24 | }); 25 | }, 26 | delete: async (req, res, next) => { 27 | const author = (await getCommentById(req.body.commentId)).author._id; 28 | 29 | if (author.toString() !== req.user._id.toString()) { 30 | return res.json({ 31 | ok: false, 32 | 33 | }); 34 | } 35 | const comment = await deleteComment(req.body.commentId); 36 | if (comment) { 37 | return res.json({ 38 | ok: true, 39 | comment: comment, 40 | }); 41 | } 42 | }, 43 | update: async (req, res) => { 44 | const content = req.body.content; 45 | const commentId = req.body.commentId; 46 | 47 | const author = (await getCommentById(req.body.commentId)).author._id; 48 | 49 | if (author.toString() !== req.user._id.toString()) { 50 | return res.json({ ok: false, comment }); 51 | } 52 | 53 | const comment = await updateComment(commentId, { 54 | content: content, 55 | updatedAt: Date.now(), 56 | }); 57 | 58 | if (!comment) { 59 | return res.json({ ok: false, comment }); 60 | } 61 | return res.json({ ok: true, comment }); 62 | }, 63 | getCommentsByPostId: async (req, res, next) => { 64 | const comments = await getCommentsByPostId(req.params.postId); 65 | return res.json({ 66 | results: comments, 67 | }); 68 | }, 69 | }; 70 | 71 | module.exports = commentController; 72 | -------------------------------------------------------------------------------- /db/like.js: -------------------------------------------------------------------------------- 1 | const Like = require("../models/like"); 2 | const Post = require("../models/post"); 3 | const User = require("../models/user"); 4 | 5 | const createLike = async (userId, postId) => { 6 | try { 7 | const like = await new Like({ 8 | userId, 9 | postId, 10 | }).save(); 11 | 12 | //update ikes in the post model 13 | await Post.findOneAndUpdate( 14 | { _id: postId }, 15 | { $push: { likes: like._id } } 16 | ); 17 | 18 | //update likes and liked posts in user model 19 | await User.findOneAndUpdate( 20 | { _id: userId }, 21 | { $push: { likes: like._id, likedPosts: postId } } 22 | ); 23 | 24 | const l = await Like.findById(like._id) 25 | .populate("userId") 26 | .populate("postId") 27 | .exec(); 28 | 29 | return await l; 30 | } catch (error) { 31 | console.log(error); 32 | } 33 | }; 34 | 35 | const deleteLike = async (likeId) => { 36 | try { 37 | const like = await Like.findByIdAndRemove(likeId); 38 | 39 | //delete like from post 40 | await Post.findOneAndUpdate( 41 | { _id: like.postId }, 42 | { $pull: { likes: like._id } } 43 | ); 44 | 45 | //delete likes and likedPost from user 46 | await User.findOneAndUpdate( 47 | { _id: like.userId }, 48 | { $pull: { likes: like._id, likedPosts: like.postId } } 49 | ); 50 | 51 | return await like; 52 | } catch (error) { 53 | console.log(error); 54 | } 55 | }; 56 | 57 | const findLikeById = async (id) => { 58 | try { 59 | const like = await Like.findById(id); 60 | return await like; 61 | } catch (error) { 62 | console.log(error); 63 | } 64 | }; 65 | 66 | const findLikeByUserIdandPostId = async (userId, postId) => { 67 | try { 68 | const like = await Like.findOne({ 69 | $and: [{ userId: userId }, { postId: postId }], 70 | }); 71 | return await like; 72 | } catch (error) { 73 | console.log(error); 74 | } 75 | }; 76 | 77 | module.exports = { 78 | createLike, 79 | deleteLike, 80 | findLikeById, 81 | findLikeByUserIdandPostId, 82 | }; 83 | -------------------------------------------------------------------------------- /views/components/editProfileModal.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/friendShared.js: -------------------------------------------------------------------------------- 1 | const sendFriendRequest = async (id) => { 2 | const receiver = id 3 | 4 | const res = await fetch("/notification/friend-request/create", { 5 | method: "post", 6 | credentials: "include", 7 | headers: { 8 | "Content-Type": "application/json", 9 | }, 10 | body: JSON.stringify({ 11 | receiver, 12 | type: "friend-request", 13 | postId: null, 14 | }), 15 | }); 16 | const data = await res.json(); 17 | if(data.ok){ 18 | if (socket.OPEN){ 19 | const notif = JSON.stringify({"type": "CREATE_FRIEND_REQUEST", data}); 20 | await socket.send(notif) 21 | } 22 | window.location.reload(); 23 | } 24 | return data; 25 | } 26 | 27 | const acceptFriendRequest = async (id) => { 28 | const res = await fetch("/friendship/accept", { 29 | method: "post", 30 | credentials: "include", 31 | headers: { 32 | "Content-Type": "application/json", 33 | }, 34 | body: JSON.stringify({ 35 | friendshipId: id, 36 | }), 37 | }); 38 | const data = await res.json(); 39 | if(data.ok){ 40 | window.location.reload(); 41 | } 42 | return data; 43 | } 44 | 45 | const deleteFriend = async (id) => { 46 | const res = await fetch("/friendship", { 47 | method: "delete", 48 | credentials: "include", 49 | headers: { 50 | "Content-Type": "application/json", 51 | }, 52 | body: JSON.stringify({ 53 | friendshipId: id, 54 | }), 55 | }); 56 | const data = await res.json(); 57 | if(data.ok){ 58 | window.location.reload(); 59 | } 60 | return data; 61 | } 62 | 63 | const undoFriendRequest = async (id) => { 64 | const res = await fetch("/friendship/undo", { 65 | method: "delete", 66 | credentials: "include", 67 | headers: { 68 | "Content-Type": "application/json", 69 | }, 70 | body: JSON.stringify({ 71 | friendshipId: id, 72 | }), 73 | }); 74 | const data = await res.json(); 75 | if(data.ok){ 76 | window.location.reload(); 77 | } 78 | return data; 79 | } -------------------------------------------------------------------------------- /controlers/auth.js: -------------------------------------------------------------------------------- 1 | const { createUser, loginUser } = require("../db/user"); 2 | const passport = require("passport"); 3 | const { validateEmail, validatePassword } = require("../utils/validation"); 4 | const User = require("../models/user"); 5 | const jwt = require('jsonwebtoken'); 6 | 7 | const authController = { 8 | google: (req, res) => { 9 | return passport.authenticate("google", { scope: ["profile", "email"] })(req, res); 10 | }, 11 | googleCallback: (req, res) => { 12 | passport.authenticate( 13 | "google", 14 | { failureRedirect: "/login?e=Unauthorized" }, 15 | (err, user) => { 16 | req.login(user, async (err) => { 17 | if (!err) { 18 | const token = jwt.sign({ user: {username: user.username, _id: user._id } }, process.env.SECRET); 19 | res.cookie('token', token); 20 | return res.redirect("/"); 21 | } 22 | }); 23 | } 24 | )(req, res); 25 | }, 26 | login: async (req, res, next) => { 27 | const { username, password } = req.body; 28 | return await loginUser(req, res, username, password); 29 | }, 30 | register: async (req, res, next) => { 31 | const { username, password, confirm } = req.body; 32 | const name = username.split("@")[0]; 33 | const profileUrl = ""; 34 | const searchName = name.toLowerCase(); 35 | 36 | if (!validateEmail(username)) { 37 | return res.render("register", { error: { emailError: "Invalid email format" } }); 38 | } 39 | if (!validatePassword(password, confirm)) { 40 | return res.render("register", { 41 | error: { 42 | passwordError: 43 | "Password should contain atleast one digit, one lower and upper case letter and the length should be atleast 8 characters", 44 | }, 45 | }); 46 | } 47 | return await createUser( 48 | req, 49 | res, 50 | username, 51 | password, 52 | name, 53 | profileUrl, 54 | searchName 55 | ); 56 | }, 57 | logout: async (req, res, next) => { 58 | req.logout((err) => { 59 | req.session.destroy(function (err) { 60 | res.clearCookie('token'); 61 | return res.redirect('/'); 62 | }); 63 | }); 64 | }, 65 | }; 66 | 67 | module.exports = authController; 68 | -------------------------------------------------------------------------------- /public/search.js: -------------------------------------------------------------------------------- 1 | const searchForm = document.querySelector(".search-form"); 2 | const searchInput = document.getElementById("search"); 3 | const searchresults = document.querySelector(".search-results"); 4 | 5 | //https://stackoverflow.com/questions/4220126/run-javascript-function-when-user-finishes-typing-instead-of-on-key-up 6 | let typingTimer; //timer identifier 7 | let doneTypingInterval = 150; 8 | 9 | searchForm.addEventListener("keyup", (e) => { 10 | clearTimeout(typingTimer); 11 | typingTimer = setTimeout(displayUsers, doneTypingInterval); 12 | }); 13 | 14 | searchForm.addEventListener("submit", async (e) => { 15 | e.preventDefault(); 16 | }); 17 | 18 | const displayUsers = async () => { 19 | 20 | if (!searchInput.value) { 21 | return; 22 | } 23 | 24 | const users = await fetchUser(searchInput.value); 25 | updateList(await users); 26 | }; 27 | 28 | searchInput.addEventListener("input", async (e) => { 29 | clearTimeout(typingTimer); 30 | }); 31 | 32 | searchInput.addEventListener("focusout", (e) => { 33 | setTimeout(() => { 34 | updateList([]); 35 | }, 800); 36 | }); 37 | 38 | const fetchUser = async (name) => { 39 | const res = await fetch("/users/search", { 40 | credentials: "include", 41 | method: "post", 42 | body: JSON.stringify({ search: name }), 43 | headers: { 44 | "Content-Type": "application/json", 45 | }, 46 | }); 47 | const data = await res.json(); 48 | return data; 49 | }; 50 | 51 | const updateList = (list) => { 52 | searchresults.innerHTML = ""; 53 | 54 | list.forEach((user) => { 55 | const div = document.createElement("div"); 56 | div.classList.add("list-group-item"); 57 | div.classList.add("d-flex"); 58 | div.classList.add("justify-content-start"); 59 | div.classList.add("align-items-center"); 60 | 61 | const aimg = document.createElement("a"); 62 | aimg.innerHTML = `Users profile`; 63 | aimg.href = "/profile/" + user._id; 64 | 65 | const aname = document.createElement("a"); 66 | aname.classList.add("m-1"); 67 | aname.textContent = user.name; 68 | aname.href = "/profile/" + user._id; 69 | 70 | div.appendChild(aimg); 71 | div.appendChild(aname); 72 | 73 | searchresults.appendChild(div); 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /views/partials/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | pepaverse 11 | 12 | 18 | 24 | 25 | <% if (user) { %> 26 | 27 | 28 | 59 | <% } %> -------------------------------------------------------------------------------- /controlers/page.js: -------------------------------------------------------------------------------- 1 | const { findUserById, getAllUsers } = require("../db/user"); 2 | const { getFriendshipByUsersId } = require("../db/friendship"); 3 | const { getPostByUserId, getPostById } = require("../db/post"); 4 | const { getUnSeenNotificationbyUserId } = require("../db/notification"); 5 | const pageController = { 6 | home: async (req, res) => { 7 | if (req.isAuthenticated()) { 8 | const user = await findUserById(req.user._id); 9 | 10 | let posts = []; 11 | 12 | const notifications = await getUnSeenNotificationbyUserId(user._id); 13 | 14 | posts = [...user.posts]; 15 | 16 | for (let i = 0; i < user.friends.length; i++) { 17 | posts = [...posts, ...(await getPostByUserId(user.friends[i]))]; 18 | } 19 | 20 | posts.sort(function (a, b) { 21 | // Turn your strings into dates, and then subtract them 22 | // to get a value that is either negative, positive, or zero. 23 | return new Date(b.createdAt) - new Date(a.createdAt); 24 | }); 25 | 26 | return res.render("home", { user: user, posts, notifications }); 27 | } else { 28 | return res.redirect("/login"); 29 | } 30 | }, 31 | login: (req, res) => { 32 | return res.render("login", { error: { generic: req.query.e } }); 33 | }, 34 | register: (req, res) => { 35 | return res.render("register", { error: { generic: req.query.e } }); 36 | }, 37 | profile: async (req, res) => { 38 | const id = req.params.userid; 39 | const user = await findUserById(req.user._id); 40 | const profileUser = await findUserById(id); 41 | 42 | const notifications = await getUnSeenNotificationbyUserId(user._id); 43 | 44 | let requestFriendship = await getFriendshipByUsersId(user._id, id); 45 | let requestedFriendship = await getFriendshipByUsersId(id, user._id); 46 | 47 | return await res.render("profile", { 48 | profileUser, 49 | user, 50 | requestFriendship, 51 | requestedFriendship, 52 | notifications, 53 | }); 54 | }, 55 | all: async (req, res) => { 56 | const users = await getAllUsers(req.user._id); 57 | const notifications = await getUnSeenNotificationbyUserId(req.user._id); 58 | res.render("all", { user: req.user, allUsers: users, notifications }); 59 | }, 60 | post: async (req, res) => { 61 | const post = await getPostById(req.params.id); 62 | 63 | const notifications = await getUnSeenNotificationbyUserId(req.user._id); 64 | 65 | res.render("post-display", { user: req.user, notifications, post }); 66 | }, 67 | }; 68 | 69 | module.exports = pageController; 70 | -------------------------------------------------------------------------------- /db/friendship.js: -------------------------------------------------------------------------------- 1 | const Friendship = require("../models/friendship"); 2 | const Notification = require("../models/notification"); 3 | const User = require("../models/user"); 4 | const { deleteNotification } = require("./notification"); 5 | 6 | const createFriendship = async (author, receiver, status, notification) => { 7 | try { 8 | const friendship = await new Friendship({ 9 | author, 10 | receiver, 11 | status, 12 | notification, 13 | }).save(); 14 | 15 | if (!friendship) { 16 | return null; 17 | } 18 | 19 | return friendship; 20 | } catch (error) { 21 | console.log(error); 22 | } 23 | }; 24 | 25 | const deleteFriendship = async (id) => { 26 | const friendship = await Friendship.findByIdAndRemove(id); 27 | 28 | if (!friendship) { 29 | return null; 30 | } 31 | 32 | await User.findByIdAndUpdate(friendship.author, { 33 | $pull: { friends: friendship.receiver }, 34 | }); 35 | await User.findByIdAndUpdate(friendship.receiver, { 36 | $pull: { friends: friendship.author }, 37 | }); 38 | 39 | await deleteNotification(friendship.notification); 40 | 41 | return friendship; 42 | }; 43 | 44 | const acceptFriendship = async (id) => { 45 | const friendship = await Friendship.findByIdAndUpdate(id, { 46 | status: "friends", 47 | }); 48 | 49 | if (!friendship) { 50 | return null; 51 | } 52 | 53 | await User.findByIdAndUpdate(friendship.author, { 54 | $addToSet: { friends: friendship.receiver }, 55 | }); 56 | await User.findByIdAndUpdate(friendship.receiver, { 57 | $addToSet: { friends: friendship.author }, 58 | }); 59 | await Notification.findByIdAndUpdate(friendship.notification, { 60 | status: "friends", 61 | seen: true, 62 | }); 63 | 64 | return friendship; 65 | }; 66 | 67 | const undoFriendship = async (id) => { 68 | const friendship = await Friendship.findByIdAndRemove(id); 69 | 70 | if (!friendship) { 71 | return null; 72 | } 73 | 74 | await User.findByIdAndUpdate(friendship.author, { 75 | $pull: { friends: friendship.receiver }, 76 | }); 77 | await User.findByIdAndUpdate(friendship.receiver, { 78 | $pull: { friends: friendship.author }, 79 | }); 80 | 81 | 82 | await deleteNotification(friendship.notification); 83 | 84 | return friendship; 85 | }; 86 | 87 | const getFriendshipByUsersId = async (author, receiver) => { 88 | const friendship = await Friendship.findOne({ author, receiver }); 89 | if (!friendship) { 90 | return null; 91 | } 92 | return friendship; 93 | }; 94 | 95 | module.exports = { 96 | createFriendship, 97 | acceptFriendship, 98 | getFriendshipByUsersId, 99 | deleteFriendship, 100 | undoFriendship, 101 | }; 102 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | <%- include('partials/head', {user: ""}); %> 2 | 3 | 4 | 5 | 6 | 7 | <%- include('partials/nav', {user: ""}); %> 8 | 9 |
10 |
Sing In
11 |
12 | 18 |
19 | <% if (error.generic) { %> 20 | 23 | <% } %> 24 |
25 |
26 | 34 | 35 |
36 |
37 | 45 | 46 |
47 |
48 | 49 | OR 55 | 63 | 66 | 67 | Login in with Google 68 | 69 |

Dont have an account? Sing Up

70 |
71 |
72 |
73 | <%- include('partials/footer'); %> 74 | -------------------------------------------------------------------------------- /db/comment.js: -------------------------------------------------------------------------------- 1 | const Like = require("../models/like"); 2 | const Post = require("../models/post"); 3 | const User = require("../models/user"); 4 | const Comment = require("../models/comment"); 5 | 6 | const createComment = async (postId, userId, content) => { 7 | try { 8 | const comment = await new Comment({ 9 | author: userId, 10 | post: postId, 11 | content, 12 | }).save(); 13 | 14 | await User.findOneAndUpdate( 15 | { _id: userId }, 16 | { $push: { comments: comment._id } } 17 | ); 18 | 19 | await Post.findOneAndUpdate( 20 | { _id: postId }, 21 | { $push: { comments: comment._id } } 22 | ); 23 | 24 | if (comment) { 25 | return await Comment.findById(comment._id).populate({ 26 | path: "author", 27 | select: ["name", "profileUrl"], 28 | }); 29 | } 30 | return null; 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | }; 35 | 36 | const deleteComment = async (commentId) => { 37 | try { 38 | const comment = await Comment.findByIdAndRemove(commentId); 39 | if (!comment) { 40 | return null; 41 | } 42 | 43 | await User.findOneAndUpdate( 44 | { _id: comment.author }, 45 | { $pull: { comments: commentId } } 46 | ); 47 | 48 | await Post.findOneAndUpdate( 49 | { _id: comment.post }, 50 | { $pull: { comments: commentId } } 51 | ); 52 | 53 | return comment; 54 | } catch (error) { 55 | console.log(error); 56 | } 57 | }; 58 | 59 | const updateComment = async (id, fieldsToUpdate) => { 60 | try { 61 | const comment = await Comment.findOneAndUpdate( 62 | { _id: id }, 63 | { ...fieldsToUpdate } 64 | ); 65 | if (!comment) { 66 | return null; 67 | } 68 | 69 | return await comment; 70 | } catch (error) { 71 | console.log(error); 72 | } 73 | }; 74 | 75 | const getCommentsByPostId = async (postId) => { 76 | try { 77 | const comments = await Comment.find({ post: postId }).populate({ 78 | path: "author", 79 | select: ["name", "profileUrl"], 80 | }); 81 | if (comments.length === 0) { 82 | return []; 83 | } 84 | return comments; 85 | } catch (error) { 86 | console.log(error); 87 | } 88 | }; 89 | 90 | const getCommentById = async (commentId) => { 91 | try { 92 | const comment = await Comment.findById(commentId); 93 | if (!comment) { 94 | return null; 95 | } 96 | 97 | return await Comment.findById(comment._id).populate({ 98 | path: "author", 99 | select: ["name", "profileUrl"], 100 | }); 101 | } catch (error) { 102 | console.log(error); 103 | } 104 | }; 105 | 106 | module.exports = { 107 | getCommentById, 108 | createComment, 109 | deleteComment, 110 | getCommentsByPostId, 111 | updateComment, 112 | }; 113 | -------------------------------------------------------------------------------- /db/post.js: -------------------------------------------------------------------------------- 1 | const Like = require("../models/like"); 2 | const Post = require("../models/post"); 3 | const User = require("../models/user"); 4 | const { deleteComment } = require("./comment"); 5 | const { deleteLike } = require("./like"); 6 | 7 | const createPost = async (content, userId) => { 8 | try { 9 | const post = await new Post({ 10 | content, 11 | author: userId, 12 | }).save(); 13 | 14 | await User.findOneAndUpdate( 15 | { _id: post.author }, 16 | { $push: { posts: post._id } } 17 | ); 18 | 19 | const p = await Post.findById(post._id) 20 | .populate("author") 21 | .populate("likes") 22 | .exec(); 23 | 24 | return await p; 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | }; 29 | 30 | const deletePost = async (postId) => { 31 | try { 32 | const deletedPost = await Post.findByIdAndRemove(postId); 33 | 34 | await User.findByIdAndUpdate(deletedPost.author, { 35 | $pull: { posts: postId }, 36 | }); 37 | 38 | const comments = deletedPost.comments; 39 | 40 | comments.forEach(async (comment) => { 41 | await deleteComment(comment); 42 | }); 43 | 44 | const likes = deletedPost.likes; 45 | 46 | likes.forEach(async (like) => { 47 | await deleteLike(like); 48 | }); 49 | } catch (error) { 50 | console.log(error); 51 | } 52 | }; 53 | 54 | const updatePost = async (id, fieldsToUpdate) => { 55 | try { 56 | const post = await Post.findOneAndUpdate( 57 | { _id: id }, 58 | { ...fieldsToUpdate } 59 | ); 60 | if (!post) { 61 | return null; 62 | } 63 | 64 | return await post; 65 | } catch (error) { 66 | console.log(error); 67 | } 68 | }; 69 | 70 | const getPostById = async (postId) => { 71 | try { 72 | const post = await Post.findById(postId).populate({ 73 | path: "author", 74 | select: ["name", "profileUrl"], 75 | }); 76 | 77 | if (!post) { 78 | return null; 79 | } 80 | 81 | return post; 82 | } catch (error) { 83 | console.log(error); 84 | } 85 | }; 86 | 87 | const getPostByUserId = async (userId) => { 88 | const posts = await Post.find({ author: userId }).populate([ 89 | { 90 | path: "author", 91 | select: ["name", "profileUrl"], 92 | }, 93 | { 94 | path: "comments", 95 | options: { 96 | sort: { 97 | createdAt: "desc", 98 | }, 99 | }, 100 | populate: [ 101 | { 102 | path: "author", 103 | select: ["name", "profileUrl"], 104 | }, 105 | ], 106 | }, 107 | ]); 108 | if (!posts) { 109 | return []; 110 | } 111 | return posts; 112 | }; 113 | 114 | module.exports = { 115 | getPostByUserId, 116 | getPostById, 117 | createPost, 118 | updatePost, 119 | deletePost, 120 | }; 121 | -------------------------------------------------------------------------------- /webSocketHandler.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | const jwt = require("jsonwebtoken"); 3 | const { server } = require("./server.config"); 4 | const wss = new WebSocket.Server({server: server}); 5 | const clients = new Map(); 6 | 7 | const closureFunc = () => { 8 | wss.on('connection', (ws, req) => { 9 | const cookie = req.headers.cookie; 10 | 11 | if (cookie) { 12 | const token = cookie.split("token=")[1].split(";")[0]; 13 | 14 | jwt.verify(token, process.env.SECRET, function (err, decoded) { 15 | 16 | if(err){ 17 | console.log(err); 18 | }else{ 19 | ws.authUser = decoded.user; 20 | 21 | clients.set(String(ws.authUser._id), ws); 22 | } 23 | 24 | }); 25 | 26 | } 27 | 28 | ws.on("message", (message) => { 29 | let jsonMessage = {"type": ""} 30 | 31 | try { 32 | jsonMessage = JSON.parse(message.toString()); 33 | } catch (error) { 34 | console.log(error); 35 | } 36 | 37 | const type = jsonMessage.type; 38 | 39 | if (type == "CREATE_FRIEND_REQUEST"){ 40 | console.log(jsonMessage); 41 | 42 | const notification = { 43 | type: "FRIEND_REQUEST_NOTIFICATION", 44 | data: jsonMessage.data 45 | } 46 | 47 | cl = clients.get(String(jsonMessage.data.notification.receiver)); 48 | 49 | if(cl){ 50 | cl.send(JSON.stringify(notification)) 51 | } 52 | 53 | } 54 | 55 | 56 | if (type == "CREATE_LIKE_NOTIFICATION"){ 57 | console.log(jsonMessage); 58 | 59 | const notification = { 60 | type: "LIKE_NOTIFICATION", 61 | data: jsonMessage.data 62 | } 63 | 64 | cl = clients.get(String(jsonMessage.data.notification.receiver)); 65 | 66 | if(cl){ 67 | cl.send(JSON.stringify(notification)) 68 | } 69 | 70 | } 71 | 72 | 73 | if (type == "CREATE_COMMENT_NOTIFICATION"){ 74 | console.log(jsonMessage); 75 | 76 | const notification = { 77 | type: "COMMENT_NOTIFICATION", 78 | data: jsonMessage.data 79 | } 80 | 81 | cl = clients.get(String(jsonMessage.data.notification.receiver)); 82 | 83 | if(cl){ 84 | cl.send(JSON.stringify(notification)) 85 | } 86 | 87 | } 88 | 89 | }) 90 | 91 | // When the client closes the connection, remove them from the list of connected clients 92 | ws.on('close', (ws) => { 93 | clients.delete(String(ws?.authUser?._id)); 94 | }); 95 | }); 96 | 97 | } 98 | 99 | 100 | module.exports = closureFunc; 101 | 102 | 103 | -------------------------------------------------------------------------------- /router.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const passport = require("passport"); 3 | const { ensureAuth } = require("./middleware/ensureAuth"); 4 | const likeController = require("./controlers/like"); 5 | const postController = require("./controlers/post"); 6 | const pageController = require("./controlers/page"); 7 | const authController = require("./controlers/auth"); 8 | const userController = require("./controlers/user"); 9 | const commentController = require("./controlers/comment"); 10 | const notificationController = require("./controlers/notification"); 11 | const friendshipController = require("./controlers/friendship"); 12 | 13 | router.get("/", pageController.home); 14 | router.get("/login", pageController.login); 15 | router.get("/register", pageController.register); 16 | router.get("/profile/:userid", ensureAuth, pageController.profile); 17 | router.get("/all", ensureAuth, pageController.all); 18 | router.get("/post/:id", ensureAuth, pageController.post); 19 | 20 | router.post("/users/update/:userid", ensureAuth, userController.update); 21 | 22 | router.get("/auth/google", authController.google); 23 | 24 | router.get("/auth/google/home", authController.googleCallback); 25 | 26 | router.get("/auth/logout", authController.logout); 27 | 28 | router.post("/auth/register", authController.register); 29 | 30 | router.post("/auth/login", authController.login); 31 | 32 | //like post router 33 | router.post("/users/like", ensureAuth, likeController.create); 34 | router.delete("/users/like", ensureAuth, likeController.delete); 35 | 36 | //comment post 37 | router.post("/users/comment", ensureAuth, commentController.create); 38 | router.delete("/users/comment", ensureAuth, commentController.delete); 39 | 40 | //update comment 41 | router.post("/users/comment/update", ensureAuth, commentController.update); 42 | 43 | //get comments from post 44 | router.get( 45 | "/post/:postId/comment", 46 | ensureAuth, 47 | commentController.getCommentsByPostId 48 | ); 49 | 50 | //upload a post 51 | router.post("/users/post", ensureAuth, postController.create); 52 | router.delete("/users/post", ensureAuth, postController.delete); 53 | 54 | //update post 55 | router.post("/users/post/update", ensureAuth, postController.update); 56 | 57 | //search 58 | router.post("/users/search", ensureAuth, userController.search); 59 | 60 | //notifications 61 | router.post("/notification/friend-request/create", ensureAuth, notificationController.createFriendRequest); 62 | 63 | router.post("/notification/like/create", ensureAuth, notificationController.createLikeNotification); 64 | 65 | router.delete("/notification/like/delete", ensureAuth, notificationController.deleteByLike); 66 | 67 | router.post("/notification/comment/create", ensureAuth, notificationController.createCommentNotification); 68 | 69 | router.delete("/notification/comment/delete", ensureAuth, notificationController.deleteByComment); 70 | 71 | router.post("/notification/:id/seen", ensureAuth, notificationController.updateSeen); 72 | 73 | //friend request 74 | router.post("/friendship/accept", ensureAuth, friendshipController.accept); 75 | 76 | router.delete("/friendship", ensureAuth, friendshipController.delete); 77 | 78 | router.delete("/friendship/undo", ensureAuth, friendshipController.undo); 79 | 80 | module.exports = router; 81 | -------------------------------------------------------------------------------- /views/components/post.ejs: -------------------------------------------------------------------------------- 1 |
6 | <% if (user._id.toString() === post.author._id.toString()) {%> 7 | 8 | <%- include("deletePostModal", {post: post}) %> 9 | 10 | 11 | 12 | <%- include("editPostModal", {post: post}) %> 13 | 14 | 15 | 16 | <%- include("optionsDropDown", {post: post}) %> 17 | 18 | <% } %> 19 |
20 |
21 | 22 | Users profile 27 | 28 |
29 |
30 |
31 |
<%= post.author.name %>
34 |

<%= post.content %>

35 |
36 |
37 | 38 | <%= post.likes.length%> 40 | 41 | <%= post.comments.length %> 43 |
44 |
45 | <% if (user.likedPosts.includes(post._id)) { %> 46 | 47 | 50 | <% }else {%> 51 | 54 | <%}%> 55 |

56 | Comment 57 |

58 | 61 |
62 |

63 | <%= post.createdAt.toDateString() %> 64 |

65 | 66 | 67 | 84 |
85 |
86 |
87 | -------------------------------------------------------------------------------- /views/register.ejs: -------------------------------------------------------------------------------- 1 | <%- include('partials/head', {user: ""}); %> 2 | 3 | 4 | 5 | 6 | 7 | <%- include('partials/nav', {user: ""}) %> 8 |
9 |
Sing Up
10 |
11 | 17 |
18 | <% if (error.generic) { %> 19 | 22 | <% } %> 23 |
24 | <% if (error.emailError) { %> 25 | 28 | <% } %> 29 |
30 | 38 | 39 |
40 |
41 | 49 | 50 |
51 |
52 | 60 | 61 |
62 | <% if (error.passwordError) { %> 63 | 66 | <% } %> 67 |
68 | 69 | OR 75 | 83 | 86 | 87 | Register in with Google 88 | 89 |

Already have an account? Sing In

90 |
91 |
92 |
93 | <%- include('partials/footer'); %> 94 | -------------------------------------------------------------------------------- /controlers/notification.js: -------------------------------------------------------------------------------- 1 | const { 2 | createFriendRequestNotifification, 3 | createLikeNotification, 4 | deleteNotification, 5 | deleteNotificationByLikeId, 6 | createCommentNotification, 7 | deleteNotificationByCommentId, 8 | updateSeen, 9 | } = require("../db/notification"); 10 | 11 | const notificationController = { 12 | createFriendRequest: async (req, res) => { 13 | const author = req.user._id; 14 | const receiver = req.body.receiver; 15 | const type = req.body.type; 16 | const postId = req.body.postId; 17 | 18 | const notification = await createFriendRequestNotifification( 19 | author, 20 | receiver, 21 | postId, 22 | type 23 | ); 24 | 25 | if (!notification) { 26 | return res.json({ 27 | ok: false, 28 | notification: null, 29 | }); 30 | } 31 | return res.json({ 32 | ok: true, 33 | notification, 34 | }); 35 | }, 36 | createLikeNotification: async (req, res) => { 37 | const author = req.user._id; 38 | const receiver = req.body.receiver; 39 | const type = req.body.type; 40 | const post = req.body.post; 41 | const like = req.body.like; 42 | 43 | if(receiver.toString() === req.user._id.toString()){ 44 | return res.json({ 45 | ok: false, 46 | notification: null, 47 | }); 48 | } 49 | 50 | const notification = await createLikeNotification( 51 | author, 52 | receiver, 53 | post, 54 | type, 55 | like, 56 | ); 57 | 58 | if (!notification) { 59 | return res.json({ 60 | ok: false, 61 | notification: null, 62 | }); 63 | } 64 | return res.json({ 65 | ok: true, 66 | notification, 67 | }); 68 | }, 69 | createCommentNotification: async (req, res) => { 70 | const author = req.user._id; 71 | const receiver = req.body.receiver; 72 | const type = req.body.type; 73 | const post = req.body.post; 74 | const like = req.body.like; 75 | const comment = req.body.comment; 76 | 77 | if(receiver.toString() === req.user._id.toString()){ 78 | return res.json({ 79 | ok: false, 80 | notification: null, 81 | }); 82 | } 83 | 84 | const notification = await createCommentNotification( 85 | author, 86 | receiver, 87 | post, 88 | type, 89 | like, 90 | comment, 91 | ); 92 | 93 | if (!notification) { 94 | return res.json({ 95 | ok: false, 96 | notification: null, 97 | }); 98 | } 99 | return res.json({ 100 | ok: true, 101 | notification, 102 | }); 103 | }, 104 | deleteByLike: async (req, res) => { 105 | const notification = await deleteNotificationByLikeId(req.body.like); 106 | if(!notification){ 107 | return res.json({ 108 | ok: false 109 | }); 110 | } 111 | return res.json({ 112 | ok: true, 113 | notification 114 | }); 115 | }, 116 | deleteByComment: async (req, res) => { 117 | const notification = await deleteNotificationByCommentId(req.body.comment); 118 | if(!notification){ 119 | return res.json({ 120 | ok: false 121 | }); 122 | } 123 | return res.json({ 124 | ok: true, 125 | notification 126 | }); 127 | }, 128 | updateSeen: async (req, res) => { 129 | const notification = await updateSeen(req.params.id); 130 | 131 | if(!notification){ 132 | return res.json({ok: false}); 133 | } 134 | 135 | return res.json({ok: true}); 136 | }, 137 | }; 138 | 139 | module.exports = notificationController; 140 | -------------------------------------------------------------------------------- /views/components/profileCard.ejs: -------------------------------------------------------------------------------- 1 |
5 | Users Profile picture 12 | 13 |
14 |
18 | <%= profileUser.name %> 19 |
20 |

<%= profileUser.description%>

21 | <% if (user._id.toString() === profileUser._id.toString()) { %> 22 | 29 | <% } else if(requestFriendship?.status === "pending"){ %> 30 | 38 | <% } else if (requestedFriendship?.status === "pending") { %> 39 | 46 | <% } else if (!requestedFriendship && !requestFriendship) {%> 47 | 54 | <% } else if (user.friends.includes(profileUser._id)) {%> 55 | 63 | <% } %> 64 |
65 |
66 | 67 | 84 | 85 | 102 | -------------------------------------------------------------------------------- /public/sharedUI.js: -------------------------------------------------------------------------------- 1 | const createFriendRequestNotificationUI = (data) => { 2 | const notificationsContainer = document.getElementById("notifications"); 3 | 4 | let notificationBadge = document.getElementById("notification-badge"); 5 | 6 | const zeroNotifications = document.getElementById("zero-notifications"); 7 | 8 | const div = document.createElement("div"); 9 | 10 | div.innerHTML = ` 11 |
12 | 13 | user profile 14 | 15 |
  • ${data.notification.author.name} wants to be your friend
  • 16 | 17 |
    18 | `; 19 | 20 | if (zeroNotifications) { 21 | notificationsContainer.removeChild(zeroNotifications); 22 | } 23 | 24 | if (notificationBadge) { 25 | notificationBadge.textContent = Number(notificationBadge.textContent) + 1; 26 | } else { 27 | notificationBadge = document.createElement("span"); 28 | notificationBadge.innerHTML = ` 1`; 29 | document.getElementById("navbarDropdown").appendChild(notificationBadge); 30 | } 31 | 32 | notificationsContainer.appendChild(div); 33 | }; 34 | 35 | createLikeNotificationUi = (data) => { 36 | const notificationsContainer = document.getElementById("notifications"); 37 | 38 | let notificationBadge = document.getElementById("notification-badge"); 39 | 40 | const zeroNotifications = document.getElementById("zero-notifications"); 41 | 42 | const div = document.createElement("div"); 43 | 44 | div.innerHTML = ` 45 |
    46 | 47 | user profile 48 | 49 |
  • ${data.notification.author.name} liked your post
  • 50 |
    51 | `; 52 | 53 | if (zeroNotifications) { 54 | notificationsContainer.removeChild(zeroNotifications); 55 | } 56 | 57 | if (notificationBadge) { 58 | notificationBadge.textContent = Number(notificationBadge.textContent) + 1; 59 | } else { 60 | notificationBadge = document.createElement("span"); 61 | notificationBadge.innerHTML = ` 1`; 62 | document.getElementById("navbarDropdown").appendChild(notificationBadge); 63 | } 64 | 65 | notificationsContainer.appendChild(div); 66 | }; 67 | 68 | const createCommentNotificationUI = (data) => { 69 | const notificationsContainer = document.getElementById("notifications"); 70 | 71 | let notificationBadge = document.getElementById("notification-badge"); 72 | 73 | const zeroNotifications = document.getElementById("zero-notifications"); 74 | 75 | const div = document.createElement("div"); 76 | 77 | div.innerHTML = ` 78 |
    79 | 80 | user profile 81 | 82 |
  • ${data.notification.author.name} commented on your post
  • 83 |
    84 | `; 85 | 86 | if (zeroNotifications) { 87 | notificationsContainer.removeChild(zeroNotifications); 88 | } 89 | 90 | if (notificationBadge) { 91 | notificationBadge.textContent = Number(notificationBadge.textContent) + 1; 92 | } else { 93 | notificationBadge = document.createElement("span"); 94 | notificationBadge.innerHTML = ` 1`; 95 | document.getElementById("navbarDropdown").appendChild(notificationBadge); 96 | } 97 | 98 | notificationsContainer.appendChild(div); 99 | }; 100 | -------------------------------------------------------------------------------- /views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 131 | 132 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /db/notification.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user"); 2 | const Notification = require("../models/notification"); 3 | 4 | const createFriendRequestNotifification = async ( 5 | author, 6 | receiver, 7 | post, 8 | type 9 | ) => { 10 | let exists = 11 | (await Notification.findOne({ 12 | author, 13 | receiver, 14 | type: "friend-request", 15 | })) || 16 | (await Notification.findOne({ 17 | author: receiver, 18 | receiver: author, 19 | type: "friend-request", 20 | })); 21 | 22 | if (exists) { 23 | return null; 24 | } 25 | 26 | let notification = await new Notification({ 27 | author, 28 | receiver, 29 | type, 30 | post: null, 31 | like: null, 32 | }).save(); 33 | 34 | if (!notification) { 35 | return null; 36 | } 37 | 38 | const { createFriendship } = require("./friendship"); 39 | const friendship = await createFriendship( 40 | author, 41 | receiver, 42 | "pending", 43 | notification._id 44 | ); 45 | 46 | if (!friendship) { 47 | return null; 48 | } 49 | 50 | notification = await Notification.findByIdAndUpdate(friendship.notification, { 51 | $set: { friendship: friendship._id }, 52 | }); 53 | 54 | await User.findOneAndUpdate( 55 | { _id: receiver }, 56 | { $push: { notifications: notification } } 57 | ); 58 | 59 | return await Notification.findById(notification._id).populate({ 60 | path: "author", 61 | select: ["name", "profileUrl"], 62 | }); 63 | }; 64 | 65 | const createLikeNotification = async (author, receiver, post, type, like) => { 66 | let exists = await Notification.findOne({ 67 | author, 68 | receiver: receiver, 69 | type: "like", 70 | post: post, 71 | }); 72 | 73 | if (exists) { 74 | return null; 75 | } 76 | 77 | let notification = await new Notification({ 78 | author, 79 | receiver, 80 | type, 81 | post, 82 | like, 83 | }).save(); 84 | 85 | if (!notification) { 86 | return null; 87 | } 88 | 89 | await User.findOneAndUpdate( 90 | { _id: receiver }, 91 | { $push: { notifications: notification } } 92 | ); 93 | 94 | return await Notification.findById(notification._id).populate({ 95 | path: "author", 96 | select: ["name", "profileUrl"], 97 | }); 98 | }; 99 | 100 | const createCommentNotification = async ( 101 | author, 102 | receiver, 103 | post, 104 | type, 105 | like, 106 | comment 107 | ) => { 108 | let notification = await new Notification({ 109 | author, 110 | receiver, 111 | type, 112 | post, 113 | like, 114 | comment, 115 | }).save(); 116 | 117 | if (!notification) { 118 | return null; 119 | } 120 | 121 | await User.findOneAndUpdate( 122 | { _id: receiver }, 123 | { $push: { notifications: notification } } 124 | ); 125 | 126 | return await Notification.findById(notification._id).populate({ 127 | path: "author", 128 | select: ["name", "profileUrl"], 129 | }); 130 | }; 131 | 132 | const deleteNotification = async (id) => { 133 | const notification = await Notification.findByIdAndRemove(id); 134 | 135 | if (!notification) { 136 | return null; 137 | } 138 | 139 | await User.findOneAndUpdate( 140 | { _id: notification.receiver }, 141 | { $pull: { notifications: notification._id } } 142 | ); 143 | }; 144 | 145 | const deleteNotificationByCommentId = async (id) => { 146 | const notification = await Notification.findOneAndRemove({ comment: id }); 147 | if (!notification) { 148 | return null; 149 | } 150 | await User.findOneAndUpdate( 151 | { _id: notification.receiver }, 152 | { $pull: { notifications: notification._id } } 153 | ); 154 | return notification; 155 | }; 156 | 157 | const deleteNotificationByLikeId = async (id) => { 158 | const notification = await Notification.findOneAndRemove({ like: id }); 159 | if (!notification) { 160 | return null; 161 | } 162 | await User.findOneAndUpdate( 163 | { _id: notification.receiver }, 164 | { $pull: { notifications: notification._id } } 165 | ); 166 | return notification; 167 | }; 168 | 169 | const getUnSeenNotificationbyUserId = async (id) => { 170 | const notifications = await Notification.find({ 171 | receiver: id, 172 | seen: false, 173 | }).populate({ path: "author", select: ["name", "profileUrl"] }); 174 | if (!notifications) { 175 | return []; 176 | } 177 | return notifications; 178 | }; 179 | 180 | const updateSeen = async (id) => { 181 | const notification = await Notification.findByIdAndUpdate(id, { seen: true }); 182 | 183 | if (!notification) { 184 | return null; 185 | } 186 | 187 | return notification; 188 | }; 189 | 190 | module.exports = { 191 | createLikeNotification, 192 | deleteNotification, 193 | getUnSeenNotificationbyUserId, 194 | createFriendRequestNotifification, 195 | deleteNotificationByLikeId, 196 | createCommentNotification, 197 | deleteNotificationByCommentId, 198 | updateSeen, 199 | }; 200 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /db/user.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user"); 2 | const { passportAuthenticateLocal } = require("../middleware/ensureAuth"); 3 | 4 | const findUserById = async (id) => { 5 | try { 6 | const user = await User.findById(id, { hash: 0, salt: 0 }) 7 | .select("-googleId") 8 | .select("-hash") 9 | .select("-salt") 10 | .populate("likes") 11 | .populate({ 12 | path: "notifications", 13 | populate: [{ path: "author", select: ["name"] }], 14 | }) 15 | .populate({ 16 | path: "posts", 17 | options: { 18 | sort: { 19 | createdAt: "desc", 20 | }, 21 | }, 22 | populate: [ 23 | { 24 | path: "author", 25 | select: ["name", "profileUrl"], 26 | }, 27 | { 28 | path: "comments", 29 | options: { 30 | sort: { 31 | createdAt: "desc", 32 | }, 33 | }, 34 | populate: [ 35 | { 36 | path: "author", 37 | select: ["name", "profileUrl"], 38 | }, 39 | ], 40 | }, 41 | ], 42 | }); 43 | return await user; 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | }; 48 | const getUserByEmail = async (username) => { 49 | try { 50 | const user = await User.findOne({ username }) 51 | .select("-googleId") 52 | .select("-hash") 53 | .select("-salt") 54 | .populate("likes") 55 | .populate({ 56 | path: "notifications", 57 | populate: [{ path: "author", select: ["name"] }], 58 | }) 59 | .populate({ 60 | path: "posts", 61 | options: { 62 | sort: { 63 | createdAt: "desc", 64 | }, 65 | }, 66 | populate: [ 67 | { 68 | path: "author", 69 | select: ["name", "profileUrl"], 70 | }, 71 | { 72 | path: "comments", 73 | populate: [ 74 | { 75 | path: "author", 76 | select: ["name", "profileUrl"], 77 | }, 78 | ], 79 | }, 80 | ], 81 | }); 82 | 83 | return await user; 84 | } catch (error) { 85 | console.log(error); 86 | } 87 | }; 88 | 89 | const getUserByUsername = async (name) => { 90 | try { 91 | const user = await User.findOne({ name }) 92 | .select("-googleId") 93 | .select("-hash") 94 | .select("-salt") 95 | .populate("likes") 96 | .populate({ 97 | path: "notifications", 98 | populate: [{ path: "author", select: ["name"] }], 99 | }) 100 | .populate({ 101 | path: "posts", 102 | options: { 103 | sort: { 104 | createdAt: "desc", 105 | }, 106 | }, 107 | populate: [ 108 | { 109 | path: "author", 110 | select: ["name", "profileUrl"], 111 | }, 112 | { 113 | path: "comments", 114 | populate: [ 115 | { 116 | path: "author", 117 | select: ["name", "profileUrl"], 118 | }, 119 | ], 120 | }, 121 | ], 122 | }); 123 | return await user; 124 | } catch (error) { 125 | console.log(error); 126 | } 127 | }; 128 | 129 | const createUser = async ( 130 | req, 131 | res, 132 | username, 133 | password, 134 | name, 135 | profileUrl, 136 | searchName 137 | ) => { 138 | try { 139 | await User.register( 140 | { username, name, profileUrl, searchName }, 141 | password, 142 | (err, user) => { 143 | if (err) { 144 | res.redirect("/register?e=" + err.message); 145 | return; 146 | } 147 | passportAuthenticateLocal(req, res); 148 | } 149 | ); 150 | } catch (error) { 151 | res.redirect("/register?e=An unexpected error ocured please try again"); 152 | } 153 | }; 154 | 155 | const loginUser = async (req, res, username, password) => { 156 | try { 157 | const user = new User({ 158 | username, 159 | password, 160 | }); 161 | 162 | await req.login(user, (err) => { 163 | if (err) { 164 | res.redirect("/login?e=An unexpected error ocured please try again"); 165 | return; 166 | } 167 | passportAuthenticateLocal(req, res); 168 | }); 169 | } catch (error) { 170 | res.redirect("/login?e=An unexpected error ocured please try again"); 171 | } 172 | }; 173 | 174 | const deleteUser = async (id) => { 175 | try { 176 | const user = await User.findByIdAndRemove(id); 177 | return await user; 178 | } catch (error) { 179 | console.log(error); 180 | } 181 | }; 182 | 183 | const updateUser = async (id, fieldsToUpdate) => { 184 | try { 185 | const user = await User.findOneAndUpdate({ _id: id }, { ...fieldsToUpdate }) 186 | .select("-googleId") 187 | .select("-hash") 188 | .select("-salt") 189 | .populate("likes") 190 | .populate({ 191 | path: "notifications", 192 | populate: [{ path: "author", select: ["name"] }], 193 | }) 194 | .populate({ 195 | path: "posts", 196 | options: { 197 | sort: { 198 | createdAt: "desc", 199 | }, 200 | }, 201 | populate: [ 202 | { 203 | path: "author", 204 | select: ["name", "profileUrl"], 205 | }, 206 | { 207 | path: "comments", 208 | populate: [ 209 | { 210 | path: "author", 211 | select: ["name", "profileUrl"], 212 | }, 213 | ], 214 | }, 215 | ], 216 | }); 217 | return user; 218 | } catch (error) { 219 | console.log(error); 220 | } 221 | }; 222 | 223 | const getAllUsers = async (userId) => { 224 | try { 225 | const users = await User.find({ 226 | _id: { $nin: [userId] }, 227 | }); 228 | 229 | if (!users.length) { 230 | return []; 231 | } 232 | return users; 233 | } catch (error) { 234 | console.log(error); 235 | } 236 | }; 237 | 238 | module.exports = { 239 | findUserById, 240 | getUserByEmail, 241 | getUserByUsername, 242 | createUser, 243 | deleteUser, 244 | updateUser, 245 | loginUser, 246 | getAllUsers, 247 | }; 248 | -------------------------------------------------------------------------------- /views/partials/nav.ejs: -------------------------------------------------------------------------------- 1 | 2 | 112 | <% if (user) { %> 113 | 114 | 115 | 133 | <% } %> -------------------------------------------------------------------------------- /views/post-display.ejs: -------------------------------------------------------------------------------- 1 | <%- include('partials/head', {user: user}); %> 2 | 3 | 4 | 5 | 6 | 7 | <%- include("partials/nav", {user: user}) %> 8 | 9 |
    10 | <%- include("components/post", {post: post, user: user}) %> 11 |
    12 | 13 | 14 | 127 | <%- include('partials/footer'); %> 128 | -------------------------------------------------------------------------------- /views/profile.ejs: -------------------------------------------------------------------------------- 1 | <%- include('partials/head', {user: user}); %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%- include("partials/nav", {user: user}) %> 9 | 10 |
    11 |
    12 | <%- include("components/profileCard", {profileUser: profileUser, user: user}) %> 13 |
    14 | 15 |
    16 | <% if (user._id.toString() === profileUser._id.toString()) { %> 17 | <%- include("components/createPost", {user: user}) %> 18 | <% } %> 19 |

    <%=profileUser.posts.length === 0? "User hasn't upload any post" : profileUser.name + " Posts" %>

    20 | 21 |
    22 |
    23 | <% profileUser.posts.forEach((post) => { %> 24 | <%- include("components/post", {post: post, user: user}) %> 25 | <% }) %> 26 |
    27 |
    28 | 29 |
    30 | 31 | 32 | <%- include("components/editProfileModal", { user: user }) %> 33 | 34 |
    35 | 36 | 67 | 220 | <%- include('partials/footer'); %> 221 | -------------------------------------------------------------------------------- /public/shared.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const deletePost = async (postId) => { 4 | const res = await fetch("/users/post", { 5 | method: "delete", 6 | credentials: "include", 7 | headers: { 8 | "Content-Type": "application/json", 9 | }, 10 | body: JSON.stringify({ postId: postId }), 11 | }); 12 | const data = await res.json(); 13 | if (data.ok) { 14 | const post = document.getElementById(`post${postId}`); 15 | post.parentElement.removeChild(post); 16 | } 17 | return await data; 18 | }; 19 | 20 | const deleteComment = async (commentId, postId) => { 21 | const res = await fetch("/users/comment", { 22 | method: "delete", 23 | credentials: "include", 24 | headers: { 25 | "Content-Type": "application/json", 26 | }, 27 | body: JSON.stringify({ commentId: commentId }), 28 | }); 29 | const data = await res.json(); 30 | if (data.ok) { 31 | const comment = document.getElementById(`comment${commentId}`); 32 | comment.parentElement.removeChild(comment); 33 | const commentDisplay = document.getElementById(`comment-display${postId}`); 34 | commentDisplay.textContent = ` ${Number(commentDisplay.textContent) - 1}`; 35 | await deleteCommentNotification(data.comment._id); 36 | } 37 | return await data; 38 | }; 39 | 40 | const updatePost = async (postId, content) => { 41 | try { 42 | const response = await fetch("/users/post/update", { 43 | method: "post", 44 | credentials: "include", 45 | headers: { 46 | "Content-Type": "application/json", 47 | }, 48 | body: JSON.stringify({ postId: postId, content: content }), 49 | }); 50 | const data = await response.json(); 51 | 52 | return await data; 53 | } catch (error) { 54 | alert("An Error Occured Please try again"); 55 | } 56 | }; 57 | 58 | const updateComment = async (commentId, content) => { 59 | try { 60 | const response = await fetch("/users/comment/update", { 61 | method: "post", 62 | credentials: "include", 63 | headers: { 64 | "Content-Type": "application/json", 65 | }, 66 | body: JSON.stringify({ commentId: commentId, content: content }), 67 | }); 68 | const data = await response.json(); 69 | 70 | return await data; 71 | } catch (error) { 72 | alert("An Error Occured Please try again"); 73 | } 74 | }; 75 | 76 | const sendLikeRequest = async (postId, liked ) => { 77 | const res = await fetch("/users/like", { 78 | method: liked ? "delete" : "post", 79 | credentials: "include", 80 | headers: { 81 | "Content-Type": "application/json", 82 | }, 83 | body: JSON.stringify({ postId: postId }), 84 | }); 85 | const data = await res.json(); 86 | return await data; 87 | }; 88 | 89 | const commentPost = async (postId, content) => { 90 | const res = await fetch("/users/comment", { 91 | method: "post", 92 | credentials: "include", 93 | headers: { 94 | "Content-Type": "application/json", 95 | }, 96 | body: JSON.stringify({ 97 | postId, 98 | content, 99 | }), 100 | }); 101 | const data = await res.json(); 102 | return data; 103 | }; 104 | 105 | const createLikeNotification = async (author, receiver, post, like) => { 106 | const res = await fetch("/notification/like/create", { 107 | method: "post", 108 | credentials: "include", 109 | headers: { 110 | "Content-Type": "application/json", 111 | }, 112 | body: JSON.stringify({ 113 | author: author, 114 | receiver: receiver, 115 | post: post, 116 | type: "like", 117 | like: like, 118 | }), 119 | }); 120 | const data = await res.json(); 121 | 122 | if(data.ok){ 123 | if (socket.OPEN){ 124 | const notif = JSON.stringify({"type": "CREATE_LIKE_NOTIFICATION", data}); 125 | await socket.send(notif) 126 | } 127 | } 128 | 129 | return data; 130 | }; 131 | 132 | const deleteLikeNotification = async (id) => { 133 | const res = await fetch("/notification/like/delete", { 134 | method: "delete", 135 | credentials: "include", 136 | headers: { 137 | "Content-Type": "application/json", 138 | }, 139 | body: JSON.stringify({ 140 | like: id, 141 | }), 142 | }); 143 | const data = await res.json(); 144 | return data; 145 | }; 146 | 147 | const createCommentNotification = async (author, receiver, post, comment) => { 148 | const res = await fetch("/notification/comment/create", { 149 | method: "post", 150 | credentials: "include", 151 | headers: { 152 | "Content-Type": "application/json", 153 | }, 154 | body: JSON.stringify({ 155 | author: author, 156 | receiver: receiver, 157 | post: post, 158 | type: "comment", 159 | like: null, 160 | comment: comment, 161 | }), 162 | }); 163 | const data = await res.json(); 164 | 165 | if(data.ok){ 166 | if (socket.OPEN){ 167 | const notif = JSON.stringify({"type": "CREATE_COMMENT_NOTIFICATION", data}); 168 | await socket.send(notif) 169 | } 170 | } 171 | 172 | 173 | return data; 174 | }; 175 | 176 | const deleteCommentNotification = async (comment) => { 177 | const res = await fetch("/notification/comment/delete", { 178 | method: "delete", 179 | credentials: "include", 180 | headers: { 181 | "Content-Type": "application/json", 182 | }, 183 | body: JSON.stringify({ 184 | comment: comment, 185 | }), 186 | }); 187 | const data = await res.json(); 188 | return data; 189 | }; 190 | 191 | const fetchComments = async (postId) => { 192 | const res = await fetch(`/post/${postId}/comment`, { 193 | method: "get", 194 | credentials: "include", 195 | headers: { 196 | "Content-Type": "application/json", 197 | }, 198 | }); 199 | const data = await res.json(); 200 | return data; 201 | }; 202 | 203 | const commentForms = document.querySelectorAll(".comment-form"); 204 | 205 | commentForms.forEach(async (form) => { 206 | form.addEventListener("submit", async (e) => { 207 | e.preventDefault(); 208 | 209 | const postInput = document.getElementById( 210 | `comment-input${form.classList[1]}` 211 | ); 212 | 213 | if (postInput.value) { 214 | const data = await commentPost(postInput.classList[1], postInput.value); 215 | if (data.ok) { 216 | const commentDisplay = document.getElementById( 217 | `comment-display${form.classList[1]}` 218 | ); 219 | 220 | commentDisplay.textContent = 221 | " " + (Number(commentDisplay.textContent) + 1); 222 | 223 | const commentDiv = document.getElementById( 224 | `comments-div${form.classList[1]}` 225 | ); 226 | 227 | const comment = await createUIComment(data.comment, commentDiv); 228 | 229 | postInput.value = ""; 230 | 231 | const resData = await createCommentNotification( 232 | data.comment.author._id, 233 | postInput.classList[3], 234 | data.comment.post, 235 | data.comment._id 236 | ); 237 | 238 | // if (resData.ok) { 239 | // socket.emit("CREATE_COMMENT_NOTIFICATION", resData); 240 | // } 241 | 242 | comment.scrollIntoView({ 243 | behavior: "smooth", 244 | block: "center", 245 | inline: "center", 246 | }); 247 | } 248 | } 249 | }); 250 | }); 251 | 252 | const showCommentBtns = document.querySelectorAll(".comment-btn"); 253 | 254 | const showComments = async (list) => { 255 | list.forEach((btn) => { 256 | btn.addEventListener("click", async (e) => { 257 | const commentsContainer = document.getElementById( 258 | `comments${btn.classList[0]}` 259 | ); 260 | commentsContainer.classList.toggle("hidden"); 261 | if (commentsContainer.children[1].children.length === 0) { 262 | const data = await fetchComments(btn.classList[0]); 263 | displayFetchedComments(data.results, commentsContainer.children[1]); 264 | } 265 | }); 266 | }); 267 | }; 268 | 269 | showComments(showCommentBtns); 270 | 271 | const displayFetchedComments = (list, div) => { 272 | list.forEach((comment) => { 273 | createUIComment(comment, div); 274 | }); 275 | const updateCommentForms = document.querySelectorAll(".update-comment"); 276 | 277 | updateCommentForms.forEach((form) => { 278 | form.addEventListener("submit", async (e) => { 279 | e.preventDefault(); 280 | 281 | const commentId = form.classList[1]; 282 | const content = e.target.content.value; 283 | 284 | const response = await updateComment(commentId, content); 285 | if (response.ok) { 286 | const commentText = document.getElementById(`comment-text${commentId}`); 287 | commentText.textContent = content; 288 | const updatedAt = document.getElementById(`updatedAt${commentId}`); 289 | updatedAt.innerHTML = 290 | 'Last updated 0 minutes ago'; 291 | } 292 | }); 293 | }); 294 | }; 295 | 296 | const updatePostForms = document.querySelectorAll(".update-post"); 297 | 298 | updatePostForms.forEach((form) => { 299 | form.addEventListener("submit", async (e) => { 300 | e.preventDefault(); 301 | const postId = form.classList[1]; 302 | const content = e.target.content.value; 303 | 304 | const response = await updatePost(postId, content); 305 | 306 | if (response.ok) { 307 | const postText = document.getElementById(`post-text${postId}`); 308 | postText.textContent = content; 309 | } 310 | }); 311 | }); 312 | 313 | const updateNotificationSeen = async (element) => { 314 | const res = await fetch(`/notification/${element.id}/seen`, { 315 | method: "post", 316 | credentials: "include", 317 | headers: { 318 | "Content-Type": "application/json", 319 | }, 320 | }); 321 | const data = await res.json(); 322 | if (data.ok) { 323 | window.location.href = element.getAttribute("data-url"); 324 | } 325 | }; 326 | 327 | const like = async (btn) => { 328 | const res = await sendLikeRequest(btn.classList[3], btn.getAttribute("liked")); 329 | const parent = btn.parentElement.parentElement; 330 | const displayLike = document.getElementById(`like-display${btn.classList[3]}`); 331 | if(res.ok){ 332 | if(res.liked){ 333 | btn.setAttribute("liked", "true"); 334 | btn.style.color = "red"; 335 | btn.children[0].classList.remove("far"); 336 | btn.children[0].classList.add("fas"); 337 | btn.children[1].textContent = "Liked" 338 | displayLike.textContent = " " + (Number(displayLike.textContent) + 1); 339 | if(btn.classList[4].toString() !== '<%= user._id.toString() %>'){ 340 | const data = await createLikeNotification('<%= user._id %>', btn.classList[4], btn.classList[3], res.like); 341 | // if(data.ok){ 342 | // socket.emit("CREATE_LIKE_NOTIFICATION", data); 343 | // } 344 | } 345 | }else{ 346 | btn.setAttribute("liked", ""); 347 | btn.style.color = "black"; 348 | btn.children[0].classList.remove("fas"); 349 | btn.children[0].classList.add("far"); 350 | btn.children[1].textContent = "Like" 351 | displayLike.textContent = " " + (Number(displayLike.textContent) - 1); 352 | await deleteLikeNotification(res.like); 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /views/home.ejs: -------------------------------------------------------------------------------- 1 | <%- include("partials/head", {user: user}) %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%- include('partials/nav', {user: user}); %> 9 |
    10 | 11 |

    Home Page

    12 | 13 | 14 | 15 | <%- include("components/createPost", {user: user}) %> 16 | 17 |

    Feed

    18 | 19 |
    20 |
    21 | <% posts.forEach((post) => { %> 22 | <%- include("components/post", {post: post, user: user}) %> 23 | <% }) %> 24 |
    25 |
    26 | 27 |
    28 | 29 | 31 | 32 | 185 | 271 | <%- include("partials/footer") %> --------------------------------------------------------------------------------