Skip to content

Commit 1e2b0e1

Browse files
Functional Project Page (#1124)
* edit project blog script * create notification schema * create like & like-status routes * feat: functional like button with ui * setup comments section & comments wrapper * setup comment schema * created /comment route to add new comments in DB * create comment in DB * create /get-comments route * func: fetch comments from DB * render comments on frontend * update: comments pagination * feat: reply to comments & sub-comments * create /get-replies route * feat: show & hide replies * create /delete-comment route * feat: delete comments & replies * resolve-bug: pagination/load all comments * fixes: comments nested structure * fixes: immediate action of reply/comments on frontend * update delete comment func * update styling & formatting
1 parent 3f89145 commit 1e2b0e1

17 files changed

+1048
-48
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import Project from "../Models/project.model.js";
2+
import Notification from "../Models/notification.model.js";
3+
import Comment from "../Models/comment.model.js";
4+
5+
export const likeProject = async (req, res) => {
6+
let user_id = req.user;
7+
8+
let { _id, islikedByUser } = req.body;
9+
10+
let incrementVal = !islikedByUser ? 1 : -1;
11+
12+
Project.findOneAndUpdate({ _id }, { $inc: { "activity.total_likes": incrementVal } })
13+
.then(project => {
14+
if (!islikedByUser) {
15+
let like = new Notification({
16+
type: "like",
17+
project: _id,
18+
notification_for: project.author,
19+
user: user_id
20+
});
21+
22+
like.save().then(notification => {
23+
return res.status(200).json({ liked_by_user: true });
24+
})
25+
} else {
26+
Notification.findOneAndDelete({ type: "like", project: _id, user: user_id })
27+
.then(() => {
28+
return res.status(200).json({ liked_by_user: false });
29+
})
30+
.catch(err => {
31+
return res.status(500).json({ error: err.message });
32+
})
33+
}
34+
})
35+
}
36+
37+
export const likeStatus = async (req, res) => {
38+
let user_id = req.user;
39+
40+
let { _id } = req.body;
41+
42+
Notification.exists({ type: "like", project: _id, user: user_id })
43+
.then(isLiked => {
44+
return res.status(200).json({ isLiked });
45+
})
46+
.catch(err => {
47+
return res.status(500).json({ error: err.message });
48+
})
49+
}
50+
51+
export const addComment = async (req, res) => {
52+
let user_id = req.user;
53+
54+
let { _id, comment, project_author, replying_to } = req.body;
55+
56+
if (!comment.length) {
57+
return res.status(403).json({ error: "Write something to leave a comment" });
58+
}
59+
60+
let commentObj = {
61+
project_id: _id,
62+
project_author,
63+
comment,
64+
commented_by: user_id,
65+
}
66+
67+
if (replying_to) {
68+
commentObj.parent = replying_to;
69+
commentObj.isReply = true;
70+
}
71+
72+
new Comment(commentObj).save().then(async commentFile => {
73+
let { comment, commentedAt, children } = commentFile;
74+
75+
Project.findOneAndUpdate({ _id }, { $push: { "comments": commentFile._id }, $inc: { "activity.total_comments": 1, "activity.total_parent_comments": replying_to ? 0 : 1 } })
76+
.then(project => {
77+
console.log('New comment created')
78+
});
79+
80+
let notificationObj = new Notification({
81+
type: replying_to ? "reply" : "comment",
82+
project: _id,
83+
notification_for: project_author,
84+
user: user_id,
85+
comment: commentFile._id,
86+
})
87+
88+
if (replying_to) {
89+
notificationObj.replied_on_comment = replying_to;
90+
await Comment.findOneAndUpdate({ _id: replying_to }, { $push: { children: commentFile._id } })
91+
.then(replyingToCommentDoc => {
92+
notificationObj.notification_for = replyingToCommentDoc.commented_by;
93+
});
94+
}
95+
96+
notificationObj.save().then(notification => {
97+
console.log('New notification created')
98+
});
99+
100+
return res.status(200).json({ comment, commentedAt, _id: commentFile._id, user_id, children });
101+
})
102+
}
103+
104+
export const getComments = async (req, res) => {
105+
let { project_id, skip } = req.body;
106+
107+
let maxLimit = 5;
108+
109+
Comment.find({ project_id, isReply: false })
110+
.populate("commented_by", "personal_info.username personal_info.fullname personal_info.profile_img")
111+
.skip(skip)
112+
.limit(maxLimit)
113+
.sort({ "commentedAt": -1 })
114+
.then(comment => {
115+
return res.status(200).json(comment);
116+
})
117+
.catch(err => {
118+
return res.status(500).json({ error: err.message });
119+
})
120+
}
121+
122+
export const getReplies = async (req, res) => {
123+
let { _id, skip } = req.body;
124+
125+
let maxLimit = 5;
126+
127+
Comment.findOne({ _id })
128+
.populate({
129+
path: "children",
130+
option: {
131+
limit: maxLimit,
132+
skip: skip,
133+
sort: { "commentedAt": -1 }
134+
},
135+
populate: {
136+
path: 'commented_by',
137+
select: 'personal_info.username personal_info.fullname personal_info.profile_img'
138+
},
139+
select: "-project_id -updatedAt"
140+
})
141+
.select("children")
142+
.then(doc => {
143+
return res.status(200).json({ replies: doc.children });
144+
})
145+
.catch(err => {
146+
return res.status(500).json({ error: err.message });
147+
})
148+
}
149+
150+
const deleteComments = (_id) => {
151+
Comment.findOneAndDelete({ _id })
152+
.then(comment => {
153+
if (comment.parent) {
154+
Comment.findOneAndUpdate({ _id: comment.parent }, { $pull: { children: _id } })
155+
.then(data => {
156+
console.log('Comment deleted successfully')
157+
})
158+
.catch(err => {
159+
console.log(err);
160+
})
161+
}
162+
163+
Notification.findOneAndDelete({ comment: _id })
164+
.then(notification => console.log('Notification deleted successfully'))
165+
.catch(err => console.log(err));
166+
167+
Notification.findOneAndDelete({ reply: _id })
168+
.then(notification => console.log('Notification deleted successfully'))
169+
.catch(err => console.log(err));
170+
171+
Project.findOneAndUpdate({ _id: comment.project_id }, { $pull: { comments: _id }, $inc: { "activity.total_comments": -1 }, "activity.total_parent_comments": comment.parent ? 0 : -1 })
172+
.then(project => {
173+
if (comment.children.length) {
174+
comment.children.map(replies => {
175+
deleteComments(replies);
176+
})
177+
}
178+
})
179+
})
180+
.catch(err => {
181+
console.log(err.message);
182+
})
183+
}
184+
185+
export const deleteComment = async (req, res) => {
186+
let user_id = req.user;
187+
188+
let { _id } = req.body;
189+
190+
Comment.findOne({ _id })
191+
.then(comment => {
192+
if (user_id == comment.commented_by || user_id == comment.project_author) {
193+
deleteComments(_id);
194+
return res.status(200).json({ message: "Comment deleted successfully" });
195+
} else {
196+
return res.status(403).json({ error: "You are not authorized to delete this comment" });
197+
}
198+
})
199+
}

backend/Controllers/project.controller.js

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import User from "../Models/user.model.js";
66
export const createProject = async (req, res) => {
77
let authorId = req.user;
88

9-
let { title, des, banner, projectUrl, repository, tags, content, draft } = req.body;
9+
let { title, des, banner, projectUrl, repository, tags, content, draft, id } = req.body;
1010

1111
if (!title.length) {
1212
return res.status(403).json({ error: "You must provide a title" });
@@ -36,36 +36,47 @@ export const createProject = async (req, res) => {
3636

3737
tags = tags.map(tag => tag.toLowerCase());
3838

39-
let project_id = title.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, '-').trim() + nanoid();
40-
41-
let project = new Project({
42-
title,
43-
des,
44-
banner,
45-
projectUrl,
46-
repository,
47-
tags,
48-
content,
49-
author: authorId,
50-
project_id,
51-
draft: Boolean(draft)
52-
})
53-
54-
project.save()
55-
.then(project => {
56-
let incrementVal = draft ? 0 : 1;
39+
let project_id = id || title.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, '-').trim() + nanoid();
5740

58-
User.findOneAndUpdate({ _id: authorId }, { $inc: { "account_info.total_posts": incrementVal }, $push: { "projects": project._id } })
59-
.then(user => {
60-
return res.status(200).json({ id: project.project_id });
61-
})
62-
.catch(err => {
63-
return res.status(500).json({ error: "Failed to update total posts number" });
64-
})
65-
})
66-
.catch(err => {
67-
return res.status(500).json({ error: err.message });
41+
if (id) {
42+
Project.findOneAndUpdate({ project_id }, { title, des, banner, content, tags, draft: draft ? draft : false })
43+
.then(project => {
44+
return res.status(200).json({ id: project_id });
45+
})
46+
.catch(err => {
47+
return res.status(500).json({ error: err.message });
48+
})
49+
} else {
50+
let project = new Project({
51+
title,
52+
des,
53+
banner,
54+
projectUrl,
55+
repository,
56+
tags,
57+
content,
58+
author: authorId,
59+
project_id,
60+
draft: Boolean(draft)
6861
})
62+
63+
project.save()
64+
.then(project => {
65+
let incrementVal = draft ? 0 : 1;
66+
67+
User.findOneAndUpdate({ _id: authorId }, { $inc: { "account_info.total_posts": incrementVal }, $push: { "projects": project._id } })
68+
.then(user => {
69+
return res.status(200).json({ id: project.project_id });
70+
})
71+
.catch(err => {
72+
return res.status(500).json({ error: "Failed to update total posts number" });
73+
})
74+
})
75+
.catch(err => {
76+
return res.status(500).json({ error: err.message });
77+
})
78+
}
79+
6980
}
7081

7182
export const getAllProjects = async (req, res) => {
@@ -167,9 +178,9 @@ export const searchProjectsCount = async (req, res) => {
167178
}
168179

169180
export const getProject = async (req, res) => {
170-
let { project_id } = req.body;
181+
let { project_id, draft, mode } = req.body;
171182

172-
let incrementVal = 1;
183+
let incrementVal = mode !== 'edit' ? 1 : 0;
173184

174185
Project.findOneAndUpdate({ project_id }, { $inc: { "activity.total_reads": incrementVal } })
175186
.populate("author", "personal_info.fullname personal_info.username personal_info.profile_img")
@@ -183,6 +194,10 @@ export const getProject = async (req, res) => {
183194
return res.status(500).json({ error: err.message });
184195
})
185196

197+
if (project.draft && !draft) {
198+
return res.status(500).json({ error: "You can't access draft project" });
199+
}
200+
186201
return res.status(200).json({ project });
187202
})
188203
.catch(err => {

backend/Models/comment.model.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import mongoose from "mongoose";
2+
import commentSchema from "../Schemas/comment.schema.js";
3+
4+
const Comment = mongoose.model("comments", commentSchema);
5+
6+
export default Comment;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import mongoose from "mongoose";
2+
import notificationSchema from "../Schemas/notification.schema.js";
3+
4+
const Notification = mongoose.model("notifications", notificationSchema);
5+
6+
export default Notification;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import express from "express";
2+
import { addComment, deleteComment, getComments, getReplies, likeProject, likeStatus } from "../../Controllers/notification.controller.js";
3+
import { authenticateUser } from "../../Middlewares/auth.middleware.js";
4+
5+
const notificationRoutes = express.Router();
6+
7+
notificationRoutes.post("/like", authenticateUser, likeProject);
8+
notificationRoutes.post("/like-status", authenticateUser, likeStatus);
9+
notificationRoutes.post("/comment", authenticateUser, addComment);
10+
notificationRoutes.post("/get-comments", getComments);
11+
notificationRoutes.post("/get-replies", getReplies);
12+
notificationRoutes.post("/delete-comment", authenticateUser, deleteComment);
13+
14+
export default notificationRoutes;

backend/Routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import authRoutes from './api/auth.routes.js';
33
import mediaRoutes from './api/media.routes.js';
44
import projectRoutes from './api/project.routes.js';
55
import userRoutes from './api/user.routes.js';
6+
import notificationRoutes from './api/notification.routes.js';
67

78
const router = express.Router();
89

910
router.use('/auth', authRoutes);
1011
router.use('/user', userRoutes);
1112
router.use('/media', mediaRoutes);
1213
router.use('/project', projectRoutes);
14+
router.use('/notification', notificationRoutes);
1315

1416
export default router;

backend/Schemas/comment.schema.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Schema } from "mongoose";
2+
3+
const commentSchema = Schema(
4+
{
5+
project_id: {
6+
type: Schema.Types.ObjectId,
7+
required: true,
8+
ref: 'projects'
9+
},
10+
project_author: {
11+
type: Schema.Types.ObjectId,
12+
required: true,
13+
ref: 'projects',
14+
},
15+
comment: {
16+
type: String,
17+
required: true
18+
},
19+
children: {
20+
type: [Schema.Types.ObjectId],
21+
ref: 'comments'
22+
},
23+
commented_by: {
24+
type: Schema.Types.ObjectId,
25+
require: true,
26+
ref: 'users'
27+
},
28+
isReply: {
29+
type: Boolean,
30+
default: false
31+
},
32+
parent: {
33+
type: Schema.Types.ObjectId,
34+
ref: 'comments'
35+
}
36+
37+
},
38+
{
39+
timestamps: {
40+
createdAt: 'commentedAt'
41+
}
42+
}
43+
)
44+
45+
export default commentSchema;

0 commit comments

Comments
 (0)