78 |
Search memories
79 |
81 |
89 |
90 |
91 |
92 |
100 | */}
101 |
102 |
103 | saveSearchPost("titles", chips)}
107 | value={searchPost.titles}
108 | onDelete={(value, index) => deleteChip("titles", index)}
109 | />
110 |
111 |
112 | saveSearchPost("tags", chips)}
116 | value={searchPost.tags}
117 | onDelete={(value, index) => deleteChip("tags", index)}
118 | />
119 |
120 |
121 | {error.value && error.type === "search" && (
122 |
123 | {error.message}
124 |
125 | )}
126 |
137 |
145 |
146 |
147 | >
148 | );
149 | }
150 |
151 | export default SearchPost;
152 |
--------------------------------------------------------------------------------
/client/src/Redux/Actions/actions.jsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from "react-router-dom";
2 |
3 | import {
4 | deletePostAPI,
5 | editPostAPI,
6 | getAllPostsAPI,
7 | likePostAPI,
8 | addPostAPI,
9 | signInAPI,
10 | signUpAPI,
11 | googleSigninAPI,
12 | searchPostAPI,
13 | getPostAPI,
14 | addCommentAPI,
15 | } from "../../APIs/APIs";
16 | import {
17 | ADD_POST,
18 | DECREASE_POST_COUNT,
19 | DELETE_POST,
20 | EDIT_POST,
21 | EDIT_POST_ID,
22 | ERROR_RESET,
23 | GET_POSTS,
24 | GOOGLE_AUTH,
25 | INCREASE_POST_COUNT,
26 | LIKE_POST,
27 | LOGOUT,
28 | RESET_ID,
29 | SEARCH_POST,
30 | SET_POST_COUNT,
31 | SET_POST_DETAILS,
32 | SIGNIN,
33 | SIGNUP,
34 | UPDATE_GOOGLE_AUTH,
35 | } from "./actionStrings";
36 |
37 | export const addPostAction = (post, googleAuth) => async (dispatch) => {
38 | const res = await addPostAPI(post, googleAuth);
39 | dispatch({
40 | type: ADD_POST,
41 | payload: res.data.post,
42 | });
43 | dispatch({
44 | type: INCREASE_POST_COUNT,
45 | });
46 | };
47 |
48 | export const getPostsAction =
49 | (page = 1) =>
50 | async (dispatch) => {
51 | const res = await getAllPostsAPI(page);
52 | dispatch({
53 | type: GET_POSTS,
54 | payload: res.data.posts,
55 | });
56 | dispatch({
57 | type: SET_POST_COUNT,
58 | payload: res.data.totalCount,
59 | });
60 | };
61 |
62 | export const getPostAction = (id, googleAuth) => async (dispatch) => {
63 | const res = await getPostAPI(id, googleAuth);
64 | dispatch({
65 | type: SET_POST_DETAILS,
66 | payload: res.data.post,
67 | });
68 | };
69 |
70 | export const addCommentAction = (comment, id, googleAuth) => async (dispatch) => {
71 | const res = await addCommentAPI(comment, id, googleAuth);
72 | dispatch({
73 | type: SET_POST_DETAILS,
74 | payload: res.data.post,
75 | });
76 | };
77 |
78 | export const editPostACtion = (post, googleAuth) => async (dispatch) => {
79 | const res = await editPostAPI(post, googleAuth);
80 | dispatch({
81 | type: EDIT_POST,
82 | payload: res.data.post,
83 | });
84 | };
85 |
86 | export const likePostAction = (post, googleAuth) => async (dispatch) => {
87 | const res = await likePostAPI(post, googleAuth);
88 | dispatch({
89 | type: LIKE_POST,
90 | payload: res.data.post,
91 | });
92 | };
93 |
94 | export const deletePostAction = (post, googleAuth) => async (dispatch) => {
95 | const res = await deletePostAPI(post, googleAuth);
96 | dispatch({
97 | type: DELETE_POST,
98 | payload: post._id,
99 | });
100 | dispatch({
101 | type: DECREASE_POST_COUNT,
102 | });
103 | };
104 |
105 | export const setEditPostIdAction = (id) => async (dispatch) => {
106 | dispatch({
107 | type: EDIT_POST_ID,
108 | payload: id,
109 | });
110 | };
111 |
112 | export const serachPostAction =
113 | (titles, tags, googleAuth, page = 1) =>
114 | async (dispatch) => {
115 | const res = await searchPostAPI(titles, tags, googleAuth, page);
116 | dispatch({
117 | type: SEARCH_POST,
118 | payload: res.data.posts, // Will be changed
119 | });
120 | dispatch({
121 | type: SET_POST_COUNT,
122 | payload: res.data.totalCount,
123 | });
124 | };
125 |
126 | export const resetIdACtion = () => async (dispatch) => {
127 | dispatch({
128 | type: RESET_ID,
129 | });
130 | };
131 |
132 | export const unexpectedErrorAction = (mes) => async (dispatch) => {
133 | dispatch({
134 | type: mes,
135 | payload: { value: true, message: mes },
136 | });
137 | };
138 |
139 | export const errorResetAction = () => async (dispatch) => {
140 | dispatch({
141 | type: ERROR_RESET,
142 | payload: { value: false, message: "All is good" },
143 | });
144 | };
145 |
146 | export const signUpAction = (user) => async (dispatch) => {
147 | const res = await signUpAPI(user);
148 | if (res?.data?.message !== "Sign up Successfully") return res;
149 | await dispatch({
150 | type: SIGNUP,
151 | payload: { user: res.data.data.user, token: res.data.data.token },
152 | });
153 | return res;
154 | };
155 |
156 | export const signInAction = (user) => async (dispatch) => {
157 | const res = await signInAPI(user);
158 | if (res?.data?.message !== "Sign in Successfully") return res;
159 | await dispatch({
160 | type: SIGNIN,
161 | payload: { user: res.data.data.user, token: res.data.data.token },
162 | });
163 | return res;
164 | };
165 |
166 | export const googleAuthAction = (profile, token) => async (dispatch) => {
167 | const res = await googleSigninAPI(token);
168 | if (
169 | res?.data?.message !== "Sign up Successfully with Google" &&
170 | res?.data?.message !== "Sign in Successfully with Google"
171 | )
172 | return res;
173 | await dispatch({
174 | type: GOOGLE_AUTH,
175 | payload: { profile, token, user: res.data.data.user },
176 | });
177 | };
178 |
179 | export const updateGoogleAuthAction = (googleAuth) => async (dispatch) => {
180 | await dispatch({
181 | type: UPDATE_GOOGLE_AUTH,
182 | payload: googleAuth,
183 | });
184 | };
185 |
186 | export const logOutAction = () => async (dispatch) => {
187 | await dispatch({
188 | type: LOGOUT,
189 | });
190 | };
191 |
--------------------------------------------------------------------------------
/client/src/Components/Posts/Post/Post.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable array-callback-return */
2 | import React, { useState } from "react";
3 | import Card from "react-bootstrap/Card";
4 | import ListGroup from "react-bootstrap/ListGroup";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import {
8 | faEllipsis,
9 | faThumbsUp as faThumbsUpSolid,
10 | faTrashAlt,
11 | } from "@fortawesome/free-solid-svg-icons";
12 | import { faThumbsUp } from "@fortawesome/free-regular-svg-icons";
13 | import Style from "./Post.module.scss";
14 | import moment from "moment";
15 | import {
16 | deletePostAction,
17 | likePostAction,
18 | setEditPostIdAction,
19 | } from "../../../Redux/Actions/actions";
20 | import { useNavigate } from "react-router-dom";
21 |
22 | function Post() {
23 | const posts = useSelector((state) => state.posts);
24 | const googleAuth = useSelector((state) => state.googleAuth);
25 | const dispatch = useDispatch();
26 | const [waitingLike, setWaitingLike] = useState(false);
27 | const [waitingDelete, setWaitingDelete] = useState(false);
28 | const [deletedPostId, setdDletedPostId] = useState("");
29 | const [editedPostId, setdEditedPostId] = useState("");
30 | let navigate = useNavigate();
31 | const postId = useSelector((state) => state.postId);
32 |
33 | return (
34 | <>
35 |
36 | {posts.map((post, index) => {
37 | return (
38 |
39 |
40 |
41 | {
46 | navigate(`/post/details/${post._id}`, { replace: true });
47 | window.scrollTo({
48 | top: 0,
49 | behavior: "smooth",
50 | });
51 | }}
52 | />
53 |
54 |
55 | {post.tags
56 | .map((value, index) => {
57 | return "#" + value;
58 | })
59 | .join(" ")}
60 |
61 | {post.title}
62 | {post.message}
63 |
64 |
65 |
66 |
67 |
68 | {(!waitingLike || editedPostId !== post._id) &&
69 | "Like "}
70 | {waitingLike &&
71 | editedPostId === post._id &&
72 | "Wait ... "}
73 |
74 | {
83 | setWaitingLike(true);
84 | setdEditedPostId(post._id);
85 | await dispatch(likePostAction(post, googleAuth));
86 | setdEditedPostId("");
87 | setWaitingLike(false);
88 | }}
89 | />
90 | {post.likeCount.length}
91 |
92 | {post.creator === googleAuth.user._id &&
93 | postId !== post._id && (
94 |
95 |
96 | {(!waitingDelete || deletedPostId !== post._id) &&
97 | "Delete "}
98 | {waitingDelete &&
99 | deletedPostId === post._id &&
100 | "Wait ... "}
101 |
102 | {
107 | setWaitingDelete(true);
108 | setdDletedPostId(post._id);
109 | await dispatch(
110 | deletePostAction(post, googleAuth)
111 | );
112 | setdDletedPostId("");
113 | setWaitingDelete(false);
114 | }}
115 | />
116 |
117 | )}
118 |
119 |
120 |
123 |
124 | {post.postName}
125 | {post.creator === googleAuth.user._id &&
126 | deletedPostId !== post._id && (
127 | {
132 | dispatch(setEditPostIdAction(post._id));
133 | }}
134 | />
135 | )}
136 |
137 | {moment(post.createdAt).fromNow()}
138 |
139 |
140 |
141 |
142 | );
143 | })}
144 |
145 | >
146 | );
147 | }
148 |
149 | export default Post;
150 |
--------------------------------------------------------------------------------
/server/modules/users/controller/user-control.js:
--------------------------------------------------------------------------------
1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== //
2 | const users = require("../model/user-model");
3 | const bcrypt = require("bcrypt");
4 | var jwt = require("jsonwebtoken");
5 | const { StatusCodes } = require("http-status-codes");
6 | const { generatePassword } = require("./services");
7 |
8 | // ====== --- ====== > User Methods < ====== --- ====== //
9 |
10 | /*
11 | //==// signUp: is the logic of '/signup' api that used to create new user with (name, email, password, age) fields.
12 | the response of this function in success (Sign up Successfully), in failure (show error message).
13 | */
14 | const signUp = async (req, res) => {
15 | try {
16 | let { name, email, password, confirmPassword } = req.body;
17 |
18 | const oldUser = await users.findOne({ email, isDeleted: false });
19 | if (!oldUser) {
20 | if (password === confirmPassword) {
21 | const newUser = new users({ name, email, password });
22 | const data = await newUser.save();
23 |
24 | var token = jwt.sign(
25 | {
26 | data: { name: data.name, email: data.email, role: data.role },
27 | exp: Math.floor(Date.now() / 1000) + 60 * 60,
28 | },
29 | process.env.ENCRYPT_KEY
30 | );
31 |
32 | res.status(StatusCodes.CREATED).json({
33 | message: "Sign up Successfully",
34 | data: { token, user: newUser },
35 | });
36 | } else {
37 | res
38 | .status(StatusCodes.BAD_REQUEST)
39 | .json({ message: "Password not matched confirm passwords" });
40 | }
41 | } else {
42 | res
43 | .status(StatusCodes.BAD_REQUEST)
44 | .json({ message: "Email is Already Found" });
45 | }
46 | } catch (error) {
47 | console.log(error);
48 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error);
49 | }
50 | };
51 |
52 | /*
53 | //==// signin: is the logic of '/signin' api that used to sign in to website.
54 | the response of this function in success (Sign in Successfully), in failure (show error message).
55 | */
56 | const signIn = async (req, res) => {
57 | try {
58 | let { email, password } = req.body;
59 | const oldUser = await users.findOne({ email, isDeleted: false });
60 | if (oldUser) {
61 | let cdate = Date.now();
62 | let match = bcrypt.compare(
63 | password,
64 | oldUser.password,
65 | function (err, result) {
66 | if (result) {
67 | var token = jwt.sign(
68 | {
69 | data: {
70 | name: oldUser.name,
71 | email: oldUser.email,
72 | role: oldUser.role,
73 | },
74 | exp: Math.floor(cdate / 1000) + 60 * 60,
75 | },
76 | process.env.ENCRYPT_KEY
77 | );
78 | res.status(StatusCodes.OK).json({
79 | message: "Sign in Successfully",
80 | data: { token, user: oldUser },
81 | });
82 | } else {
83 | res
84 | .status(StatusCodes.BAD_REQUEST)
85 | .json({ message: "Incorrect Password !" });
86 | }
87 | }
88 | );
89 | } else {
90 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User Not Found !" });
91 | }
92 | } catch (error) {
93 | console.log(error);
94 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error);
95 | }
96 | };
97 |
98 | /*
99 | //==// Google signin: is the logic of '/google' api that used to continue with google.
100 | the response of this function in success (User login with google success), in failure (show error message).
101 | */
102 | const googleSignIn = async (req, res) => {
103 | try {
104 | let { name, email } = req.body;
105 | const oldUser = await users.findOne({ email, isDeleted: false });
106 | if (oldUser) {
107 | var token = jwt.sign(
108 | {
109 | data: {
110 | name: oldUser.name,
111 | email: oldUser.email,
112 | role: oldUser.role,
113 | },
114 | exp: Math.floor(Date.now() / 1000) + 60 * 60,
115 | },
116 | process.env.ENCRYPT_KEY
117 | );
118 | res.status(StatusCodes.OK).json({
119 | message: "Sign in Successfully with Google",
120 | data: { token, user: oldUser },
121 | });
122 | } else {
123 | const randomPassword = generatePassword();
124 | const newUser = new users({ name, email, password: randomPassword });
125 | const data = await newUser.save();
126 |
127 | var token = jwt.sign(
128 | {
129 | data: { name: data.name, email: data.email, role: data.role },
130 | exp: Math.floor(Date.now() / 1000) + 60 * 60,
131 | },
132 | process.env.ENCRYPT_KEY
133 | );
134 |
135 | res.status(StatusCodes.CREATED).json({
136 | message: "Sign up Successfully with Google",
137 | data: { token, user: newUser },
138 | });
139 | }
140 | } catch (error) {
141 | console.log(error);
142 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error);
143 | }
144 | };
145 |
146 | /*
147 | //==// Update password: is the logic of '/user-update-password' api that used to update user password.
148 | the response of this function in success (User updated Successfully), in failure (show error message).
149 | */
150 | const updatePassword = async (req, res) => {
151 | try {
152 | let { oldPassword, newPassword } = req.body;
153 | let { email, role } = req.decoded;
154 | const oldUser = await users.findOne({ email, isDeleted: false });
155 | if (oldUser) {
156 | let match = bcrypt.compare(
157 | oldPassword,
158 | oldUser.password,
159 | async function (err, result) {
160 | if (result) {
161 | let password = await bcrypt.hash(newPassword, 7);
162 | const data = await users.updateOne(
163 | { email, isDeleted: false },
164 | { password }
165 | );
166 |
167 | res
168 | .status(StatusCodes.OK)
169 | .json({ Message: "Password updated Successfully" });
170 | } else {
171 | res
172 | .status(StatusCodes.BAD_REQUEST)
173 | .json({ Message: "Incorrect Password !" });
174 | }
175 | }
176 | );
177 | } else {
178 | res.status(StatusCodes.BAD_REQUEST).json({ Message: "User Not Found !" });
179 | }
180 | } catch (error) {
181 | console.log(error);
182 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error);
183 | }
184 | };
185 |
186 | // ====== --- ====== > Export Module < ====== --- ====== //
187 | module.exports = {
188 | signUp,
189 | signIn,
190 | googleSignIn,
191 | updatePassword,
192 | };
193 |
--------------------------------------------------------------------------------
/client/src/Components/AddPostForm/AddPostForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Button from "react-bootstrap/Button";
3 | import Form from "react-bootstrap/Form";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import {
6 | addPostAction,
7 | editPostACtion,
8 | errorResetAction,
9 | resetIdACtion,
10 | unexpectedErrorAction,
11 | } from "../../Redux/Actions/actions";
12 | import Alert from "react-bootstrap/Alert";
13 | import {
14 | ERROR_ADD_POST,
15 | ERROR_EDIT_POST,
16 | } from "../../Redux/Actions/actionStrings";
17 |
18 | function AddPostForm() {
19 | const dispatch = useDispatch();
20 | const googleAuth = useSelector((state) => state.googleAuth);
21 | const error = useSelector((state) => state.error);
22 | const postId = useSelector((state) => state.postId);
23 | const [post, setPost] = useState({
24 | postName: "",
25 | title: "",
26 | message: "",
27 | tags: [],
28 | file: "",
29 | });
30 | const posts = useSelector((state) => state.posts);
31 | const [waiting, setWaiting] = useState(false);
32 | const [editState, setEditState] = useState(false);
33 |
34 | if (postId && editState === false) {
35 | setPost(posts.find((item) => item._id === postId));
36 | setEditState(true);
37 | }
38 |
39 | const savePostData = ({ target }) => {
40 | if (target.name !== "tags")
41 | setPost({ ...post, [target.name]: target.value });
42 | else setPost({ ...post, [target.name]: target.value.split(" ") });
43 | };
44 |
45 | const clearForm = (e) => {
46 | e.preventDefault();
47 | setPost({
48 | postName: "",
49 | title: "",
50 | message: "",
51 | tags: [],
52 | file: "",
53 | });
54 |
55 | document.getElementById("postForm").reset();
56 | };
57 |
58 | const submitPost = async (e) => {
59 | e.preventDefault();
60 | setWaiting(true);
61 | if (!editState) {
62 | let result = Object.values(post).every((p) => {
63 | if (typeof p === "string") return p !== "" && !/^\s/.test(p);
64 | else return p[0] !== "";
65 | });
66 | if (result) {
67 | await dispatch(errorResetAction());
68 | await dispatch(addPostAction(post, googleAuth));
69 | clearForm(e);
70 | } else await dispatch(unexpectedErrorAction(ERROR_ADD_POST));
71 | } else {
72 | let result = Object.values(post).every((p) => {
73 | if (typeof p === "string") return p !== "" && !/^\s/.test(p);
74 | else return p[0] !== "";
75 | });
76 | if (result) {
77 | await dispatch(errorResetAction());
78 | await dispatch(editPostACtion(post, googleAuth));
79 | dispatch(resetIdACtion());
80 | setEditState(false);
81 | clearForm(e);
82 | } else await dispatch(unexpectedErrorAction(ERROR_EDIT_POST));
83 | }
84 | setWaiting(false);
85 | };
86 |
87 | const getBase64 = ({ target }, cb) => {
88 | let reader = new FileReader();
89 | if (target.files[0] && target.files[0].type.match("image.*"))
90 | reader.readAsDataURL(target.files[0]);
91 | else target.value = "";
92 | reader.onload = function () {
93 | cb(reader.result);
94 | };
95 | reader.onerror = function (error) {
96 | console.log("Error: ", error);
97 | };
98 | };
99 |
100 | return (
101 | <>
102 |
103 |
104 | {editState && "Editing a Memory"}
105 | {!editState && "Creating a Memory"}
106 |
107 |
109 |
117 |
118 |
119 |
120 |
128 |
129 |
130 |
131 |
139 |
140 |
141 |
142 |
150 |
151 |
152 |
153 | {!editState && (
154 | {
156 | getBase64(e, (result) => {
157 | setPost({
158 | ...post,
159 | [e.target.name]: result,
160 | filePath: e.target.value,
161 | });
162 | });
163 | }}
164 | type="file"
165 | name="file"
166 | required={true}
167 | />
168 | )}
169 |
170 |
171 | {error.value && error.type === "post" && (
172 |
173 | {error.message}
174 |
175 | )}
176 |
177 |
188 | {!editState && (
189 |
197 | )}
198 | {editState && (
199 |
207 | )}
208 |
209 |
210 | >
211 | );
212 | }
213 |
214 | export default AddPostForm;
215 |
--------------------------------------------------------------------------------
/client/src/Components/PostDetails/PostDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Card from "react-bootstrap/Card";
3 | import ListGroup from "react-bootstrap/ListGroup";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { useNavigate } from "react-router-dom";
6 | import {
7 | addCommentAction,
8 | errorResetAction,
9 | getPostAction,
10 | serachPostAction,
11 | unexpectedErrorAction,
12 | } from "../../Redux/Actions/actions";
13 | import { useParams } from "react-router-dom";
14 | import moment from "moment";
15 | import Button from "react-bootstrap/Button";
16 | import Form from "react-bootstrap/Form";
17 | import { ERROR_SEND_COMMENT } from "../../Redux/Actions/actionStrings";
18 | import Alert from "react-bootstrap/Alert";
19 | import { useRef } from "react";
20 |
21 | function PostDetails() {
22 | const dispatch = useDispatch();
23 | const error = useSelector((state) => state.error);
24 | const postDetails = useSelector((state) => state.postDetails);
25 | const memoryProfile = JSON.parse(localStorage.getItem("memoryProfile"));
26 | const [waiting, setWaiting] = useState(true);
27 | const [waitingBtn, setWaitingBtn] = useState(false);
28 | const [comment, setComment] = useState("");
29 | const [comments, setComments] = useState(postDetails.comments);
30 | let navigate = useNavigate();
31 | const commentRef = useRef();
32 | const { id } = useParams();
33 | const posts = useSelector((state) => state.posts).filter((value) => {
34 | return value._id !== postDetails._id;
35 | });
36 |
37 | const fetchMyAPI = async () => {
38 | try {
39 | setWaiting(true);
40 | await dispatch(getPostAction(id, memoryProfile));
41 | } catch (error) {
42 | localStorage.clear();
43 | navigate("/auth", { replace: true });
44 | }
45 | };
46 |
47 | useEffect(() => {
48 | const func = async () => {
49 | setComments(postDetails.comments);
50 | setWaiting(true);
51 | await dispatch(
52 | serachPostAction(postDetails.tags, postDetails.tags, memoryProfile, 1)
53 | );
54 | if (Object.keys(postDetails).length !== 0) setWaiting(false);
55 | };
56 | func();
57 | }, [postDetails._id]);
58 |
59 | useEffect(() => {
60 | fetchMyAPI();
61 | }, [id]);
62 |
63 | const handleComment = ({ target }) => {
64 | setComment(target.value);
65 | };
66 |
67 | const sendComment = async (e) => {
68 | e.preventDefault();
69 | if (comment !== "" && !/^\s/.test(comment)) {
70 | setComments([...comments, `${memoryProfile.user.name}: ${comment}`]);
71 | setWaitingBtn(true);
72 | await dispatch(errorResetAction());
73 | await dispatch(
74 | addCommentAction(
75 | `${memoryProfile.user.name}: ${comment}`,
76 | postDetails._id,
77 | memoryProfile
78 | )
79 | );
80 | commentRef.current.scrollIntoView({ behavior: "smooth" });
81 | setWaitingBtn(false);
82 | setComment("");
83 | } else await dispatch(unexpectedErrorAction(ERROR_SEND_COMMENT));
84 | };
85 |
86 | return (
87 | <>
88 |