├── 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 |
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 |
10 | Sing In
11 |
12 |
18 |
19 | <% if (error.generic) { %>
20 |
21 | <%= error.generic %>
22 |
23 | <% } %>
24 |
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 |
27 |
28 |
29 |
30 |
31 |
<%= post.author.name %>
34 |
<%= post.content %>
35 |
36 |
37 |
38 | <%= post.likes.length%>
40 |
43 |
44 |
45 | <% if (user.likedPosts.includes(post._id)) { %>
46 |
47 |
48 | Liked
49 |
50 | <% }else {%>
51 |
52 | Like
53 |
54 | <%}%>
55 |
58 |
59 | Share
60 |
61 |
62 |
63 | <%= post.createdAt.toDateString() %>
64 |
65 |
66 |
67 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/views/register.ejs:
--------------------------------------------------------------------------------
1 | <%- include('partials/head', {user: ""}); %>
2 |
3 |
9 | Sing Up
10 |
11 |
17 |
18 | <% if (error.generic) { %>
19 |
20 | <%= error.generic %>
21 |
22 | <% } %>
23 |
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 |
12 |
13 |
14 |
18 | <%= profileUser.name %>
19 |
20 |
<%= profileUser.description%>
21 | <% if (user._id.toString() === profileUser._id.toString()) { %>
22 |
27 | Edit profile
28 |
29 | <% } else if(requestFriendship?.status === "pending"){ %>
30 |
36 | Pending
37 |
38 | <% } else if (requestedFriendship?.status === "pending") { %>
39 |
44 | Accept
45 |
46 | <% } else if (!requestedFriendship && !requestFriendship) {%>
47 |
52 | Add friend
53 |
54 | <% } else if (user.friends.includes(profileUser._id)) {%>
55 |
61 | friends
62 |
63 | <% } %>
64 |
65 |
66 |
67 |
3 |
4 |
12 |
13 |
14 | Get connected with us on social networks:
15 |
16 |
17 |
18 |
19 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Company name
53 |
54 |
55 | Here you can use rows and columns to organize your footer
56 | content. Lorem ipsum dolor sit amet, consectetur adipisicing
57 | elit.
58 |
59 |
60 |
61 |
62 |
63 |
79 |
80 |
81 |
82 |
98 |
99 |
100 |
101 |
102 |
103 |
Contact
104 |
New York, NY 10012, US
105 |
106 |
107 | info@example.com
108 |
109 |
+ 01 234 567 88
110 |
+ 01 234 567 89
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
124 | © 2021 Copyright:
125 |
MDBootstrap.com
128 |
129 |
130 |
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 |
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 |