From 6b91cd4d436e75efdde6f5d8829f9d146ba0acc2 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 19:35:11 -0500 Subject: [PATCH 01/10] Reworked base api url --- server/app.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/server/app.js b/server/app.js index c76a398..41e5606 100644 --- a/server/app.js +++ b/server/app.js @@ -20,13 +20,15 @@ app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); -app.use('/users', userRouter); -app.use('/mail', mailRouter); -app.use('/questions', questionRouter); +app.use('/public', express.static(path.join(__dirname, 'public'))); + +app.use('/api/users', userRouter); +app.use('/api/mail', mailRouter); +app.use('/api/questions', questionRouter); app.use(express.static(path.join(__dirname, 'public/build'))); -app.get('*', (_, res) => { - res.sendFile(path.join(__dirname, 'public/build/index.html')); +app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, './public/build/index.html')); }); module.exports = app; From 9748af470d46fb398bc725836064fa570d41aa72 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 19:36:26 -0500 Subject: [PATCH 02/10] Updated openapi base url --- server/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/openapi.yml b/server/openapi.yml index f324b2a..38e468c 100644 --- a/server/openapi.yml +++ b/server/openapi.yml @@ -3,7 +3,7 @@ servers: - description: SwaggerHub API Auto Mocking url: https://virtserver.swaggerhub.com/C4theBomb/qOverflow/1.0.0 - description: Localhost - url: http://localhost:3001 + url: http://localhost:3001/api info: version: '1.0.0' title: qOverflow API From 0c8582bb46a01ae690902557aef0adc690adce03 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 20:29:11 -0500 Subject: [PATCH 03/10] Fixed vote problem --- server/routes/question/EditQuestionVote.js | 307 ++++++++++++--------- 1 file changed, 176 insertions(+), 131 deletions(-) diff --git a/server/routes/question/EditQuestionVote.js b/server/routes/question/EditQuestionVote.js index a52f0bc..a50e5fe 100644 --- a/server/routes/question/EditQuestionVote.js +++ b/server/routes/question/EditQuestionVote.js @@ -1,51 +1,11 @@ const config = require('server/config.json'); const User = require('server/db/models/User'); const Vote = require('server/db/models/Vote'); -const { getQuestion, refreshQuestion } = require('server/utils/question'); +const Question = require('server/db/models/Question'); const createRequest = require('server/utils/api'); const getUserLevel = require('server/utils/getUserLevel'); -async function downvote(res, user) { - { - const { success } = await createRequest('patch', URL, { - operation: 'increment', - target: 'downvotes', - }); - if (!success) return res.status(500).send(config.errorGeneric); - await Vote.create({ - parent_id: question_id, - creator: user.username, - status: 'downvoted', - docModel: 'Question', - }); - } - { - const { success } = await createRequest( - 'patch', - `/users/${user.username}/points`, - { - operation: 'decrement', - amount: 1, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); - await User.findByIdAndUpdate(user.id, { $inc: { points: -1 } }); - } -} - -async function upvote(res, user) { - const { success } = await createRequest('patch', URL, { - operation: 'increment', - target: 'upvotes', - }); - if (!success) return res.status(500).send(config.errorGeneric); - await Vote.create({ - parent_id: question_id, - creator: user.username, - status: 'upvoted', - docModel: 'Question', - }); -} +async function upvote(res, user) {} async function EditQuestionVote(req, res) { const { user } = req; @@ -54,19 +14,24 @@ async function EditQuestionVote(req, res) { if (!operation) return res.status(400).send(config.errorIncomplete); + // Filter out requests that do not have the permissions const userLevel = getUserLevel(user.points); - if (operation === 'upvote' && userLevel < 2) + if (operation === 'upvote' && userLevel < 2) { return res.status(403).send(config.errorForbidden); + } - if (operation === 'downvote' && userLevel < 4) + if (operation === 'downvote' && userLevel < 4) { return res.status(403).send(config.errorForbidden); + } const URL = `/questions/${question_id}/vote/${user.username}`; - const question = await getQuestion(question_id); + // Verify that the question exists + const question = await Question.findById(question_id); if (!question) return res.status(404).send(config.errorNotFound); + // Find the cached vote and record the status let cachedVote = await Vote.findOneAndDelete({ parent_id: question_id, creator: user.username, @@ -77,28 +42,54 @@ async function EditQuestionVote(req, res) { cachedVote = !error ? { status: vote } : { status: null }; } - if (cachedVote === 'upvoted') { - { - const { success } = await createRequest('patch', URL, { - operation: 'decrement', - target: 'upvotes', - }); - if (!success) return res.status(500).send(config.errorGeneric); - } + if (cachedVote.status === 'upvoted') { + // Undo the previous upvote status + const { success } = await createRequest('patch', URL, { + operation: 'decrement', + target: 'upvotes', + }); + + if (!success) return res.status(500).send(); + if (operation === 'downvote') { - await downvote(res, user); - { - const { success } = await createRequest( - 'patch', - `/users/${question.creator}/points`, - { - operation: 'decrement', - amount: 6, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); + // Change the vote status to downvoted + const { success } = await createRequest('patch', URL, { + operation: 'increment', + target: 'downvotes', + }); + + if (!success) { + return res.status(500).send(config.errorGeneric); } + + // Cache the new vote + await Vote.create({ + parent_id: question_id, + creator: user.username, + status: 'downvoted', + docModel: 'Question', + }); + + // Decrement the question creator's points + await createRequest('patch', `/users/${question.creator}/points`, { + operation: 'decrement', + amount: 6, + }); + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: -6 } } + ); + + // Decrement the user's points + await createRequest('patch', `/users/${user.username}/points`, { + operation: 'decrement', + amount: 1, + }); + await User.findByIdAndUpdate(user.id, { $inc: { points: -1 } }); + + return res.send({ vote: 'downvoted' }); } else { + // Decrement the question creator's points const { success } = await createRequest( 'patch', `/users/${question.creator}/points`, @@ -107,84 +98,138 @@ async function EditQuestionVote(req, res) { amount: 5, } ); + + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: -5 } } + ); + if (!success) return res.status(500).send(config.errorGeneric); - return res.send({ vote: 'upvoted' }); - } - } else if (cachedVote === 'downvoted') { - { - const { success } = await createRequest('patch', URL, { - operation: 'decrement', - target: 'downvotes', - }); - if (!success) return res.status(500).send(config.errorGeneric); + + return res.send({ vote: null }); } + } else if (cachedVote.status === 'downvoted') { + // Undo the previous vote status + const { success } = await createRequest('patch', URL, { + operation: 'decrement', + target: 'downvotes', + }); + + if (!success) return res.status(500).send(config.errorGeneric); + if (operation === 'upvote') { - await upvote(res, user); - { - const { success } = await createRequest( - 'patch', - `/users/${question.creator}/points`, - { - operation: 'increment', - amount: 6, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); - } + // Increment upvotes + const { success } = await createRequest('patch', URL, { + operation: 'increment', + target: 'upvotes', + }); + + if (!success) return res.status(500).send(config.errorGeneric); + + // Cache the new vote + await Vote.create({ + parent_id: question_id, + creator: user.username, + status: 'upvoted', + docModel: 'Question', + }); + + // Increment question creator's points + await createRequest('patch', `/users/${question.creator}/points`, { + operation: 'increment', + amount: 6, + }); + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: 6 } } + ); + return res.send({ vote: 'upvoted' }); } else { - { - const { success } = await createRequest( - 'patch', - `/users/${question.creator}/points`, - { - operation: 'increment', - amount: 1, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); - } - { - const { success } = await createRequest( - 'patch', - `/users/${user.username}/points`, - { - operation: 'increment', - amount: 1, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); - await User.findByIdAndUpdate(user.id, { $inc: { points: 1 } }); - } + // Increment the question creator's points + await createRequest('patch', `/users/${question.creator}/points`, { + operation: 'increment', + amount: 1, + }); + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: 1 } } + ); + + // Increment the user's points + await createRequest('patch', `/users/${user.username}/points`, { + operation: 'increment', + amount: 1, + }); + await User.findByIdAndUpdate(user.id, { $inc: { points: 1 } }); + + return res.send({ vote: null }); } } else { if (operation === 'upvote') { - await upvote(res, user); - { - const { success } = await createRequest( - 'patch', - `/users/${question.creator}/points`, - { - operation: 'increment', - amount: 5, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); - } + // Increment upvotes + const { success } = await createRequest('patch', URL, { + operation: 'increment', + target: 'upvotes', + }); + + if (!success) return res.status(500).send(config.errorGeneric); + + // Cache the new vote + await Vote.create({ + parent_id: question_id, + creator: user.username, + status: 'upvoted', + docModel: 'Question', + }); + + // Increment question creator's points + await createRequest('patch', `/users/${question.creator}/points`, { + operation: 'increment', + amount: 5, + }); + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: 5 } } + ); + return res.send({ vote: 'upvoted' }); } else if (operation === 'downvote') { - await downvote(res, user); - { - const { success } = await createRequest( - 'patch', - `/users/${question.creator}/points`, - { - operation: 'decrement', - amount: 1, - } - ); - if (!success) return res.status(500).send(config.errorGeneric); + // Change the vote status to downvoted + const { success } = await createRequest('patch', URL, { + operation: 'increment', + target: 'downvotes', + }); + + if (!success) { + return res.status(500).send(config.errorGeneric); } + + // Cache the new vote + await Vote.create({ + parent_id: question_id, + creator: user.username, + status: 'downvoted', + docModel: 'Question', + }); + + // Decrement the question creator's points + await createRequest('patch', `/users/${question.creator}/points`, { + operation: 'decrement', + amount: 1, + }); + await User.findOneAndUpdate( + { username: question.creator }, + { $inc: { points: -1 } } + ); + + // Decrement the user's points + await createRequest('patch', `/users/${user.username}/points`, { + operation: 'decrement', + amount: 1, + }); + await User.findByIdAndUpdate(user.id, { $inc: { points: -1 } }); + return res.send({ vote: 'downvoted' }); } } From 339055169e384e46d2db6feeace1ceb828c9eb68 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 20:29:44 -0500 Subject: [PATCH 04/10] Fixed navbar components --- client/src/components/MobileNavbar.jsx | 26 +++++++++----------- client/src/components/ModeToggle.jsx | 34 -------------------------- client/src/components/Navbar.jsx | 24 +++++++++++++++--- client/src/components/index.js | 2 -- 4 files changed, 32 insertions(+), 54 deletions(-) delete mode 100644 client/src/components/ModeToggle.jsx diff --git a/client/src/components/MobileNavbar.jsx b/client/src/components/MobileNavbar.jsx index fe9fa42..e998764 100644 --- a/client/src/components/MobileNavbar.jsx +++ b/client/src/components/MobileNavbar.jsx @@ -19,23 +19,19 @@ import AppRegistrationIcon from '@mui/icons-material/AppRegistration'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; import SearchIcon from '@mui/icons-material/Search'; -import { useState } from 'react'; import Gravatar from 'react-gravatar'; import { Link } from 'react-router-dom'; import Logo from 'assets/bdpa-logo.svg'; -import { useMode } from 'contexts'; - -export default function MobileNavbar({ logout, userData }) { - const [open, setOpen] = useState(false); - const { mode, setMode } = useMode(); - - const handleClick = () => setOpen((initial) => !initial); - - const onClick = () => { - setMode(() => (mode === 'light' ? 'dark' : 'light')); - }; +export default function MobileNavbar({ + logout, + userData, + open, + setOpen, + mode, + setMode, +}) { function NavbarControls() { return userData.username ? ( <> @@ -121,11 +117,11 @@ export default function MobileNavbar({ logout, userData }) { - - + @@ -137,7 +133,7 @@ export default function MobileNavbar({ logout, userData }) { - + {mode === 'light' ? ( diff --git a/client/src/components/ModeToggle.jsx b/client/src/components/ModeToggle.jsx deleted file mode 100644 index e605928..0000000 --- a/client/src/components/ModeToggle.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { IconButton } from '@mui/material'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import { useMode } from 'contexts'; - -export default function ModeToggle() { - const { mode, setMode } = useMode(); - - const onClick = () => { - setMode(() => (mode === 'light' ? 'dark' : 'light')); - }; - - if (mode === 'light') { - return ( - - - - ); - } else { - return ( - - - - ); - } -} diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index e1960f0..35fe12a 100644 --- a/client/src/components/Navbar.jsx +++ b/client/src/components/Navbar.jsx @@ -9,10 +9,12 @@ import { import MailIcon from '@mui/icons-material/Mail'; import { Link } from 'react-router-dom'; import Logo from 'assets/bdpa-logo.svg'; -import { ModeToggle, Profile } from 'components'; +import { Profile } from 'components'; import { SearchBar } from 'controllers'; +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import LightModeIcon from '@mui/icons-material/LightMode'; -export default function Navbar({ logout, userData }) { +export default function Navbar({ logout, userData, mode, setMode }) { function NavbarControls() { return userData.username ? ( <> @@ -61,7 +63,23 @@ export default function Navbar({ logout, userData }) { - + {mode === 'dark' ? ( + + + + ) : ( + + + + )} diff --git a/client/src/components/index.js b/client/src/components/index.js index 951b83d..e36c6d3 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -3,7 +3,6 @@ import Form from './Form'; import ListAnswer from './ListAnswer'; import ListQuestion from './ListQuestion'; import MailUnit from './MailUnit'; -import ModeToggle from './ModeToggle'; import Navbar from './Navbar'; import NoData from './NoData'; import PaginatedList from './PaginatedList'; @@ -18,7 +17,6 @@ export { ListAnswer, ListQuestion, MailUnit, - ModeToggle, Navbar, NoData, PaginatedList, From 27b2a7075df61147fc154e00a19bb6a8beb16060 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 20:29:54 -0500 Subject: [PATCH 05/10] Fixed navbar and centrallized logic --- client/src/controllers/NavbarController.js | 27 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/client/src/controllers/NavbarController.js b/client/src/controllers/NavbarController.js index bab2f0e..4ca7775 100644 --- a/client/src/controllers/NavbarController.js +++ b/client/src/controllers/NavbarController.js @@ -1,14 +1,24 @@ -import { useUser, useError } from 'contexts'; +import { useUser, useError, useMode } from 'contexts'; import { Navbar, MobileNavbar } from 'components'; import { logout } from 'services/userServices'; import { useNavigate } from 'react-router-dom'; import { useMediaQuery } from '@mui/material'; import Cookies from 'js-cookie'; +import { useState } from 'react'; export default function NavbarController() { - const { userData, setUserData } = useUser(); - const { setError } = useError(); const navigate = useNavigate(); + const { userData, setUserData } = useUser(); + const { mode, setMode } = useMode(); + const { setError } = useError(); + + const [open, setOpen] = useState(false); + + const toggleOpen = () => setOpen((initial) => !initial); + + const toggleMode = () => { + setMode(() => (mode === 'light' ? 'dark' : 'light')); + }; const sm = useMediaQuery((theme) => theme.breakpoints.only('sm')); const md = useMediaQuery((theme) => theme.breakpoints.only('md')); @@ -24,6 +34,13 @@ export default function NavbarController() { }; return md || sm - ? Navbar({ logout: logoutUser, userData }) - : MobileNavbar({ logout: logoutUser, userData }); + ? Navbar({ logout: logoutUser, userData, mode, setMode: toggleMode }) + : MobileNavbar({ + logout: logoutUser, + userData, + open, + setOpen: toggleOpen, + mode, + toggleMode, + }); } From 9b143d127be5f29a4bc251ac13b69deaa9d9e7b8 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Tue, 2 Aug 2022 20:47:37 -0500 Subject: [PATCH 06/10] Fixed createAnswer button --- .../components/QAComponents/CreateAnswer.jsx | 23 ++++++++++--------- server/utils/api.js | 2 -- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/client/src/components/QAComponents/CreateAnswer.jsx b/client/src/components/QAComponents/CreateAnswer.jsx index 65a6949..bf0058a 100644 --- a/client/src/components/QAComponents/CreateAnswer.jsx +++ b/client/src/components/QAComponents/CreateAnswer.jsx @@ -1,20 +1,21 @@ -import { Button, Card, CardContent, Tooltip } from '@mui/material' +import { Button, Card, CardContent, Tooltip } from '@mui/material'; -import { AnswerForm } from 'controllers/FormControllers' -import MdPreview from 'components/MdPreview' - -export default function CreateAnswer({ - canAnswer, - show, - toggleShow, -}) { +import { AnswerForm } from 'controllers/FormControllers'; +import MdPreview from 'components/MdPreview'; +export default function CreateAnswer({ canAnswer, show, toggleShow }) { return (
- +