From 98e0e5113a73bacab931d11c2aaee68ba090a125 Mon Sep 17 00:00:00 2001 From: balaharisankar Date: Tue, 30 Jul 2024 23:00:43 +0530 Subject: [PATCH 1/2] Upvote and Downvotes fixed --- backend/app.js | 16 +- backend/app/models/question.js | 4 + .../routes/Q&A/question/downvoteQuestion.js | 26 +-- .../app/routes/Q&A/question/upvoteQuestion.js | 2 +- frontend/src/pages/Q&A/Q&A.jsx | 2 +- frontend/src/service/Faq.jsx | 210 +++++++++--------- 6 files changed, 131 insertions(+), 129 deletions(-) diff --git a/backend/app.js b/backend/app.js index 2c6a34c5..ea39cea0 100644 --- a/backend/app.js +++ b/backend/app.js @@ -15,8 +15,20 @@ app.use(express.static('uploads')); // Set security headers app.use(helmet()); +// cookie +app.use(cookieParser()); + // CORS -app.use(cors()); +// app.use(cors()); +app.use(cors({credentials:true,origin:process.env.FRONTEND_URL})); + +app.use(function(req, res, next) { + res.header('Access-Control-Allow-Credentials', true); + res.header('Access-Control-Allow-Origin', process.env.FRONTEND_URL); + res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,UPDATE,OPTIONS'); + res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'); + next(); +}); // Body Parser app.use(express.json({ limit: '50mb' })); @@ -25,8 +37,6 @@ app.use(express.urlencoded({ limit: '50mb', extended: true })); // Response time app.use(responseTime({ suffix: false })); -// cookie -app.use(cookieParser()); // Use routes app.use('/', routes); diff --git a/backend/app/models/question.js b/backend/app/models/question.js index 43051a07..4fe46114 100644 --- a/backend/app/models/question.js +++ b/backend/app/models/question.js @@ -25,6 +25,10 @@ const questionSchema = new Schema( type: Number, default: 0, }, + downvotes:{ + type:Number, + default:0 + } }, { timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } } ); diff --git a/backend/app/routes/Q&A/question/downvoteQuestion.js b/backend/app/routes/Q&A/question/downvoteQuestion.js index d29b7869..3dcc23b9 100644 --- a/backend/app/routes/Q&A/question/downvoteQuestion.js +++ b/backend/app/routes/Q&A/question/downvoteQuestion.js @@ -6,25 +6,11 @@ const { getVoteCookieName } = require('../../../../helpers/middlewares/cookie'); module.exports = async (req, res, next) => { const { questionId } = req.body; - const [err] = await to( - question.updateOne({ _id: questionId }, [ - { - $set: { - upvotes: { - $cond: [ - { - $gt: ['$upvotes', 0], - }, - { - $subtract: ['$upvotes', 1], - }, - 0, - ], - }, - }, - }, - ]) - ); + const existingQues=await question.findById(questionId) + if(!existingQues.downvotes){ + const [err] = await to(question.updateOne({ _id: questionId },{$set:{downvotes:0}})); + } + const [err] = await to(question.updateOne({ _id: questionId }, { $inc: { downvotes: 1 } })); if (err) { console.log(err); const error = new ErrorHandler(constants.ERRORS.DATABASE, { @@ -36,7 +22,7 @@ module.exports = async (req, res, next) => { return next(error); } - res.cookie(getVoteCookieName('question', questionId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000 }); + res.cookie(getVoteCookieName('question', questionId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000, sameSite: "none", secure: true }); res.status(200).send({ message: 'Question has been down voted', }); diff --git a/backend/app/routes/Q&A/question/upvoteQuestion.js b/backend/app/routes/Q&A/question/upvoteQuestion.js index 77a1b2a2..671dca9d 100644 --- a/backend/app/routes/Q&A/question/upvoteQuestion.js +++ b/backend/app/routes/Q&A/question/upvoteQuestion.js @@ -16,7 +16,7 @@ module.exports = async (req, res, next) => { return next(error); } - res.cookie(getVoteCookieName('question', questionId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000 }); + res.cookie(getVoteCookieName('question', questionId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000,sameSite:"none",secure:true }); res.status(200).send({ message: 'Question has been upvoted', diff --git a/frontend/src/pages/Q&A/Q&A.jsx b/frontend/src/pages/Q&A/Q&A.jsx index d9d2e1bb..0423c21d 100644 --- a/frontend/src/pages/Q&A/Q&A.jsx +++ b/frontend/src/pages/Q&A/Q&A.jsx @@ -191,7 +191,7 @@ function Ques(props) { className="vote-btn" onClick={() => handleDownvote(item._id)} > - 👎 {item?.downvote} + 👎 {item?.downvotes} diff --git a/frontend/src/service/Faq.jsx b/frontend/src/service/Faq.jsx index f23e7c8b..d3ee876c 100644 --- a/frontend/src/service/Faq.jsx +++ b/frontend/src/service/Faq.jsx @@ -2,127 +2,127 @@ import { END_POINT } from "../config/api"; import { showToast } from "./toastService"; export async function postFaq(formData, setToast, toast) { - try { - const response = await fetch(`${END_POINT}/faq/postFaq`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify(formData), + try { + const response = await fetch(`${END_POINT}/faq/postFaq`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + setToast({ + ...toast, + toastMessage: "FAQ has been added", + toastStatus: true, + toastType: "success", }); - - if (response.ok) { - setToast({ - ...toast, - toastMessage: "FAQ has been added", - toastStatus: true, - toastType: "success", - }); - return { success: true }; - } else { - setToast({ - ...toast, - toastMessage: "Database Error", - toastStatus: true, - toastType: "error", - }); - return { success: false, error: "Database Error" }; - } - } catch (error) { + return { success: true }; + } else { setToast({ ...toast, - toastMessage: "Network Error", + toastMessage: "Database Error", toastStatus: true, toastType: "error", }); - return { success: false, error: "Network Error" }; + return { success: false, error: "Database Error" }; } + } catch (error) { + setToast({ + ...toast, + toastMessage: "Network Error", + toastStatus: true, + toastType: "error", + }); + return { success: false, error: "Network Error" }; } +} export async function getFaq() { - try { - const response = await fetch(`${END_POINT}/faq/getFaq`); - if (!response.ok) { - throw new Error("Failed to fetch FAQs"); - } - const data = await response.json(); - return data.Faq; - } catch (error) { - console.error("Failed to fetch FAQs:", error.message); + try { + const response = await fetch(`${END_POINT}/faq/getFaq`); + if (!response.ok) { throw new Error("Failed to fetch FAQs"); } + const data = await response.json(); + return data.Faq; + } catch (error) { + console.error("Failed to fetch FAQs:", error.message); + throw new Error("Failed to fetch FAQs"); + } } -export const deleteFaq = async (faqId, setToast, toast) => { - const url = `${END_POINT}/faq/deleteFaq`; - const body = { faqId: faqId }; - const headers = { - "Content-Type": "application/json", - authorization: `Bearer ${localStorage.getItem("token")}`, - }; - try { - const response = await fetch(url, { - method: "PUT", - headers: headers, - body: JSON.stringify(body), - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - setToast({ - ...toast, - toastMessage: data.message, - toastStatus: true, - toastType: "success", - }); - return data.message; - } catch (error) { - console.error("Failed to delete FAQ:", error.message); - setToast({ - ...toast, - toastMessage: "Failed to delete FAQ", - toastStatus: true, - toastType: "error", - }); - throw new Error("Failed to delete FAQ"); +export const deleteFaq = async (faqId, setToast, toast) => { + const url = `${END_POINT}/faq/deleteFaq`; + const body = { faqId: faqId }; + const headers = { + "Content-Type": "application/json", + authorization: `Bearer ${localStorage.getItem("token")}`, + }; + try { + const response = await fetch(url, { + method: "PUT", + headers: headers, + body: JSON.stringify(body), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } + const data = await response.json(); + setToast({ + ...toast, + toastMessage: data.message, + toastStatus: true, + toastType: "success", + }); + return data.message; + } catch (error) { + console.error("Failed to delete FAQ:", error.message); + setToast({ + ...toast, + toastMessage: "Failed to delete FAQ", + toastStatus: true, + toastType: "error", + }); + throw new Error("Failed to delete FAQ"); + } }; export const updateFaq = async (faqId, updatedFaqDetails, setToast, toast) => { - try { - const response = await fetch(`${END_POINT}/faq/updateFaq`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify({ faqId, ...updatedFaqDetails }), - }); - - if (!response.ok) { - throw new Error("Failed to update FAQ"); - } + try { + const response = await fetch(`${END_POINT}/faq/updateFaq`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ faqId, ...updatedFaqDetails }), + }); - const data = await response.json(); - setToast({ - ...toast, - toastMessage: data.message, - toastStatus: true, - toastType: "success", - }); - return data.message; - } catch (error) { - console.error("Failed to update FAQ:", error.message); - setToast({ - ...toast, - toastMessage: "Failed to update FAQ", - toastStatus: true, - toastType: "error", - }); - throw new Error("Failed to update FAQ"); + if (!response.ok) { + throw new Error("Failed to update FAQ"); } + + const data = await response.json(); + setToast({ + ...toast, + toastMessage: data.message, + toastStatus: true, + toastType: "success", + }); + return data.message; + } catch (error) { + console.error("Failed to update FAQ:", error.message); + setToast({ + ...toast, + toastMessage: "Failed to update FAQ", + toastStatus: true, + toastType: "error", + }); + throw new Error("Failed to update FAQ"); + } }; export const getAllQuestions = async (setToast, toast) => { @@ -292,6 +292,7 @@ export const upvote = async (questionId, handleToast) => { headers: { "Content-Type": "application/json", }, + credentials: "include", body: JSON.stringify({ questionId }), }); if (!response.ok) { @@ -300,7 +301,7 @@ export const upvote = async (questionId, handleToast) => { showToast(handleToast, "Upvote Successfully"); return response.json(); } catch (error) { - showToast(handleToast, "Failed to upvote question", "error"); + showToast(handleToast, "You have already voted", "error"); throw new Error("Failed to upvote question"); } }; @@ -312,6 +313,7 @@ export const downvote = async (questionId, handleToast) => { headers: { "Content-Type": "application/json", }, + credentials: "include", body: JSON.stringify({ questionId }), }); if (!response.ok) { @@ -320,7 +322,7 @@ export const downvote = async (questionId, handleToast) => { showToast(handleToast, "Downvote Successfully"); return response.json(); } catch (error) { - showToast(handleToast, "Failed to downvote question", "error"); + showToast(handleToast, "You have already voted", "error"); throw new Error("Failed to downvote question"); } -}; \ No newline at end of file +}; From 1887bfd938a13e7468986deaa2293833038456b8 Mon Sep 17 00:00:00 2001 From: balaharisankar Date: Sat, 10 Aug 2024 15:54:31 +0530 Subject: [PATCH 2/2] Keep me logged in feature added --- backend/app/models/Admin.js | 4 +++ backend/app/routes/admin/changePassword.js | 2 +- backend/app/routes/admin/getAdmins.js | 15 ++++++-- backend/app/routes/admin/resetPassword.js | 2 +- .../routes/auth/@validationSchema/index.js | 1 + backend/app/routes/auth/login.js | 7 ++-- backend/helpers/middlewares/auth.js | 3 +- .../Setting/ResetPassword/ResetPassword.jsx | 4 +++ frontend/src/pages/Login/Login.jsx | 36 ++++++++++++++++--- frontend/src/pages/Login/login.module.scss | 7 ++++ 10 files changed, 68 insertions(+), 13 deletions(-) diff --git a/backend/app/models/Admin.js b/backend/app/models/Admin.js index e3d0478f..716327aa 100644 --- a/backend/app/models/Admin.js +++ b/backend/app/models/Admin.js @@ -37,6 +37,10 @@ const adminSchema = new Schema( type: String, trim: true, }, + refreshToken:{ + type:String, + trim:true + } }, { timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } } ); diff --git a/backend/app/routes/admin/changePassword.js b/backend/app/routes/admin/changePassword.js index 02735c5f..fdf2ffa4 100644 --- a/backend/app/routes/admin/changePassword.js +++ b/backend/app/routes/admin/changePassword.js @@ -36,7 +36,7 @@ module.exports = async (req, res, next) => { const hashedPassword = await argon2.hash(newPassword); const [err] = await to( - Admin.findOneAndUpdate({ email: userRecord.email }, { $set: { passwordHash: hashedPassword } }) + Admin.findOneAndUpdate({ email: userRecord.email }, { $set: { passwordHash: hashedPassword,refreshToken:"" } }) ); if (err) { diff --git a/backend/app/routes/admin/getAdmins.js b/backend/app/routes/admin/getAdmins.js index 8aebb616..fd9a5295 100644 --- a/backend/app/routes/admin/getAdmins.js +++ b/backend/app/routes/admin/getAdmins.js @@ -2,6 +2,7 @@ const to = require('await-to-js').default; const Admin = require('../../models/Admin'); const { ErrorHandler } = require('../../../helpers/error'); const constants = require('../../../constants'); +const { getTokenFromHeader } = require('../../../helpers/middlewares/auth') const getAdminsAggregate = (match, page) => { const pipeline = [ @@ -15,7 +16,7 @@ const getAdminsAggregate = (match, page) => { email: 1, contact: 1, isSuperAdmin: 1, - image:1 + image: 1 }, }, { $skip: constants.PAGINATION_LIMIT.GET_ADMINS * (Number(page) - 1) }, @@ -37,10 +38,20 @@ module.exports = async (req, res, next) => { email: req.query.email || '', }; } + const token = await getTokenFromHeader(req) const [err, response] = await to(Admin.aggregate(getAdminsAggregate(match, page))); if (err) { const error = new ErrorHandler(constants.ERRORS.DATABASE, { - statusCode: '500', + statusCode: 500, + message: 'The server encountered an unexpected condition which prevented it from fulfilling the request.', + errStack: err, + }); + return next(error); + } + const refreshToken = await Admin.findOne({ email: response[0].email }) + if (token != refreshToken?.refreshToken) { + const error = new ErrorHandler(constants.ERRORS.DATABASE, { + statusCode: 500, message: 'The server encountered an unexpected condition which prevented it from fulfilling the request.', errStack: err, }); diff --git a/backend/app/routes/admin/resetPassword.js b/backend/app/routes/admin/resetPassword.js index a6db3a0c..dfa9e41e 100644 --- a/backend/app/routes/admin/resetPassword.js +++ b/backend/app/routes/admin/resetPassword.js @@ -31,7 +31,7 @@ module.exports = async (req, res, next) => { const hashedPassword = await argon2.hash(newPassword); // Finding and updating the admin password - const [err] = await to(Admin.findOneAndUpdate({ email }, { passwordHash: hashedPassword }, { new: true })); + const [err] = await to(Admin.findOneAndUpdate({ email }, { passwordHash: hashedPassword,refreshToken:"" }, { new: true })); // Throwing error in admin not found if (err) { diff --git a/backend/app/routes/auth/@validationSchema/index.js b/backend/app/routes/auth/@validationSchema/index.js index b2106529..f6e042b8 100644 --- a/backend/app/routes/auth/@validationSchema/index.js +++ b/backend/app/routes/auth/@validationSchema/index.js @@ -3,6 +3,7 @@ const Joi = require('joi'); const loginSchema = Joi.object().keys({ email: Joi.string().required(), password: Joi.string().required(), + keepMeLoggedIn:Joi.boolean() }); module.exports = loginSchema; diff --git a/backend/app/routes/auth/login.js b/backend/app/routes/auth/login.js index 57001a07..dc1b73ea 100644 --- a/backend/app/routes/auth/login.js +++ b/backend/app/routes/auth/login.js @@ -2,10 +2,10 @@ const argon2 = require('argon2'); const Admin = require('../../models/Admin'); const { ErrorHandler } = require('../../../helpers/error'); const constants = require('../../../constants'); -const { generateJWT } = require('../../../helpers/middlewares/auth'); +const { generateJWT,generateJWTWithOutExpire } = require('../../../helpers/middlewares/auth'); module.exports = async (req, res, next) => { - const { email, password } = req.body; + const { email, password,keepMeLoggedIn } = req.body; const userRecord = await Admin.findOne({ email }); if (!userRecord) { const error = new ErrorHandler(constants.ERRORS.INPUT, { @@ -34,7 +34,8 @@ module.exports = async (req, res, next) => { isSuperAdmin: userRecord.isSuperAdmin, phone: userRecord.contact, }; - const JWT = generateJWT(JWTPayload); + const JWT = keepMeLoggedIn?generateJWTWithOutExpire(JWTPayload):generateJWT(JWTPayload); + const updateRefreshToken=await Admin.findByIdAndUpdate(userRecord.id,{refreshToken:JWT}) const response = { ...JWTPayload, token: JWT }; return res.status(200).send(response); }; diff --git a/backend/helpers/middlewares/auth.js b/backend/helpers/middlewares/auth.js index fa38cfc3..a2a545c7 100644 --- a/backend/helpers/middlewares/auth.js +++ b/backend/helpers/middlewares/auth.js @@ -6,6 +6,7 @@ const constants = require('../../constants'); const generateJWT = (payload, expiry = config.JWT_EXPIRES_IN) => sign(payload, config.JWT_SECRET_KEY, { expiresIn: expiry }); +const generateJWTWithOutExpire = (payload) => sign(payload, config.JWT_SECRET_KEY) const getTokenFromHeader = async (req) => { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { return req.headers.authorization.split(' ')[1]; @@ -38,4 +39,4 @@ const authMiddleware = async (req, res, next) => { next(); }; -module.exports = { authMiddleware, generateJWT, verifyToken }; +module.exports = { authMiddleware, generateJWT, verifyToken,generateJWTWithOutExpire ,getTokenFromHeader}; diff --git a/frontend/src/pages/Admin/Components/Setting/ResetPassword/ResetPassword.jsx b/frontend/src/pages/Admin/Components/Setting/ResetPassword/ResetPassword.jsx index 3312a3b9..5ab0801c 100644 --- a/frontend/src/pages/Admin/Components/Setting/ResetPassword/ResetPassword.jsx +++ b/frontend/src/pages/Admin/Components/Setting/ResetPassword/ResetPassword.jsx @@ -4,6 +4,8 @@ import { Button2 } from "../../../../../components/util/Button"; import { END_POINT } from "./../../../../../config/api"; import { SimpleToast } from "./../../../../../components/util/Toast/Toast"; import style from "./reset-password.module.scss"; +import { useDispatch } from "react-redux"; +import { logout } from "../../../../../store/actions/auth"; export function ResetPassword() { const [oldPassword, setOldPassword] = useState(""); @@ -16,6 +18,7 @@ export function ResetPassword() { const oldPasswordInput = useRef("oldpassword"); const newPasswordInput = useRef("newpassword"); const confirmPasswordInput = useRef("confirmpassword"); + const dispatch = useDispatch(); const token = useSelector((state) => state.token); @@ -63,6 +66,7 @@ export function ResetPassword() { if (response.status === 200) { setOpenSuccessToast(true); setPasswordChange(true); + logout(dispatch); } response .json() diff --git a/frontend/src/pages/Login/Login.jsx b/frontend/src/pages/Login/Login.jsx index 72e7577d..6459c407 100644 --- a/frontend/src/pages/Login/Login.jsx +++ b/frontend/src/pages/Login/Login.jsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from "react"; import { Button2 } from "../../components/util/Button/index"; +import { Checkbox } from "@material-ui/core"; import style from "./login.module.scss"; import { useDispatch, useSelector } from "react-redux"; import * as actions from "../../store/actions/actions"; @@ -17,10 +18,11 @@ export function Login(props) { const dispatch = useDispatch(); const dark = props.theme; const [errorObj, setErrorObj] = useState({}); - const [isLoading,setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); const validationSchema = { email: Joi.string().email().required(), password: Joi.string().required(), + keepMeLoggedIn: Joi.boolean(), }; const isFormValid = () => { @@ -105,7 +107,7 @@ export function Login(props) { .json() .then((res) => { if (response.status === 200) { - const firstName = res.name.split(' ')[0]; + const firstName = res.name.split(" ")[0]; localStorage.setItem("token", res.token); localStorage.setItem("isSuperAdmin", res.isSuperAdmin); localStorage.setItem("firstName", firstName); @@ -120,12 +122,14 @@ export function Login(props) { }) .catch((err) => { console.error(err); - setOpenError3Toast(true)}) + setOpenError3Toast(true); + }) ) .catch((err) => { setOpenError1Toast(true); console.error("must be a backend problem🤔:", err); - }).finally(()=> { + }) + .finally(() => { setIsLoading(false); }); } @@ -137,7 +141,9 @@ export function Login(props) { return ( <> -
{isLoading?:null}
+
+ {isLoading ? : null} +
+
+ { + setCredential({ + ...credential, + keepMeLoggedIn: e.target.checked, + }); + }} + /> + +