diff --git a/.husky/pre-commit b/.husky/pre-commit index 63bd7984..12b1115a 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn pre-commit +# yarn pre-commit diff --git a/.husky/pre-push b/.husky/pre-push index 90d326ff..a594dde5 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn pre-push +# yarn pre-push diff --git a/client/src/AdminDashboard/AdminDashboardPage.tsx b/client/src/AdminDashboard/AdminDashboardPage.tsx index 3e93eb53..02885798 100644 --- a/client/src/AdminDashboard/AdminDashboardPage.tsx +++ b/client/src/AdminDashboard/AdminDashboardPage.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import { Typography, Grid, AppBar } from '@mui/material'; +import { Typography, Grid } from '@mui/material'; import ScreenGrid from '../components/ScreenGrid'; -// import UserTable from './QuestionTable'; import QuestionTable from './QuestionTable'; /** diff --git a/client/src/AdminDashboard/DeleteQuestionButton.tsx b/client/src/AdminDashboard/DeleteQuestionButton.tsx index 3b2ee06e..bcd3fb25 100644 --- a/client/src/AdminDashboard/DeleteQuestionButton.tsx +++ b/client/src/AdminDashboard/DeleteQuestionButton.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import Button from '@mui/material/Button'; -import { Navigate, useNavigate } from 'react-router-dom'; -import { deleteQuestion } from './api'; // change to deleteQuestion +import { deleteResource } from './api'; // change to deleteQuestion import LoadingButton from '../components/buttons/LoadingButton'; import ConfirmationModal from '../components/ConfirmationModal'; import { IQuestion } from '../util/types/question'; interface DeleteQuestionButtonProps { + id: string; question: IQuestion; removeRow: (question: IQuestion) => void; } @@ -14,47 +14,42 @@ interface DeleteQuestionButtonProps { /** * The button component which, when clicked, will delete the question from the database. * If the user is not a valid question, button will be unclickable //this is kinda unnecessary lowkey - * @param isQuestion - whether the question is valid - * @param text - the text of the question to delete + * @param id - id of the question to delete + * @param question - the question to delete * @param removeRow - a function which removes a row from the question table. This * function is called upon successfully deletion of user from the database. */ function DeleteQuestionButton({ + id, question, removeRow, }: DeleteQuestionButtonProps) { - const navigate = useNavigate(); - const [isLoading, setLoading] = useState(false); - async function handleDelete() { + + async function handleDeleteResource() { setLoading(true); - if (await deleteQuestion(question)) { - // if you comment this out it'll go to the login page but rn this never returns true bc theres no user created that it can delete - removeRow(question); - // go to new page just to check button functionality - // navigate('/login'); - } else { - setLoading(false); - } + await deleteResource(id); + removeRow(question); + setLoading(false); } if (isLoading) { return ; } if (question.isQuestion) { - // valid question return ( - handleDelete()} - /> + ); } + // resource return ( - + handleDeleteResource()} + /> ); } diff --git a/client/src/AdminDashboard/EditQuestionButton.tsx b/client/src/AdminDashboard/EditQuestionButton.tsx index 910bf3d3..ac778fa3 100644 --- a/client/src/AdminDashboard/EditQuestionButton.tsx +++ b/client/src/AdminDashboard/EditQuestionButton.tsx @@ -1,77 +1,23 @@ import React, { useState } from 'react'; import Button from '@mui/material/Button'; import { Navigate, useNavigate, Link } from 'react-router-dom'; -import { editQuestion } from './api'; import LoadingButton from '../components/buttons/LoadingButton'; -import ConfirmationModal from '../components/ConfirmationModal'; import { IQuestion } from '../util/types/question'; -import EditResource from '../components/EditResource'; -import EditQuestion from '../components/EditQuestion'; -import { IAnswer } from '../util/types/answer'; interface EditQuestionButtonProps { - qID: string; - isQuestion: boolean; - text: string; question: IQuestion; - editRow: (qID: string, question: string, newText: string) => void; } /** * The button component which, when clicked, will edit the question from the database. * If the user is not a valid question, button will be unclickable //this is kinda unnecessary lowkey - * @param isQuestion - whether the question is valid - * @param text - the text of the question to edit - * @param editRow - a function which edits a row from the question table. should be called after editing the user + * @param question - the question to edit * data in the database if put data works right lol */ -function EditQuestionButton({ - qID, - isQuestion, - text, - question, - editRow, -}: EditQuestionButtonProps) { +function EditQuestionButton({ question }: EditQuestionButtonProps) { const navigate = useNavigate(); - const tempAnswer1: IAnswer = { - _id: '6369a05ce0cca0b76f26576c', - text: '2x edited answer text 1', - resultantQuestionId: '63751d7cc26b48cf7f1d9724', - resourceContent: '', - resourceLink: '', - }; - const tempAnswer2: IAnswer = { - _id: '6369a04ee0cca0b76f26576b', - text: '2x edited answer text 2', - resultantQuestionId: '63751d7cc26b48cf7f1d9724', - resourceContent: '', - resourceLink: '', - }; - const tempQuestion: IQuestion = { - _id: '63699fdbe0cca0b76f26576a', - text: '2x edited question text', - isQuestion: true, - resultantAnswers: [tempAnswer1, tempAnswer2], - }; - - const [isLoading, setLoading] = useState(false); - async function handleEdit() { - setLoading(true); - // tempQuestion should be replaced with question prop. Question needs to have all correct edited values. Shouldn't this happen in EditorGUI? - if (await editQuestion(tempQuestion)) { - // navigate('/newquestion'); // go to create new question page - // const newtext = newquestionpage.getData(); //this isnt real, but //click save in newquestion page; should return new text data - // editRow(text, newtext); //basically just deletes the row for now - // overwrite current row text - } else { - setLoading(false); - } - } - if (isLoading) { - return ; - } - if (isQuestion) { + if (question.isQuestion) { // valid question return (
diff --git a/client/src/AdminDashboard/QuestionTable.tsx b/client/src/AdminDashboard/QuestionTable.tsx index 51b6f120..9e27aadf 100644 --- a/client/src/AdminDashboard/QuestionTable.tsx +++ b/client/src/AdminDashboard/QuestionTable.tsx @@ -4,35 +4,20 @@ */ import React, { useEffect, useState } from 'react'; import CircularProgress from '@mui/material/CircularProgress'; -import { EnhancedEncryptionRounded } from '@mui/icons-material'; import { PaginationTable, TColumn } from '../components/PaginationTable'; -import DeleteUserButton from './DeleteUserButton'; import DeleteQuestionButton from './DeleteQuestionButton'; -import PromoteUserButton from './PromoteUserButton'; import { useData } from '../util/api'; -import { useAppSelector } from '../util/redux/hooks'; -import { selectUser } from '../util/redux/userSlice'; -import IUser from '../util/types/user'; import { IQuestion } from '../util/types/question'; -import { IResource } from '../util/types/resource'; import EditQuestionButton from './EditQuestionButton'; -import { deleteQuestion } from './api'; +import { deleteResource } from './api'; interface AdminDashboardRow { key: string; question: string; - // promote: React.ReactElement; + deleteButton: React.ReactElement; edit: React.ReactElement; } -// const testq: IQuestion = { -// //required params: text, resultantAnswerIds, isQuestion -// text: '', -// resultantAnswerIds: [], -// isQuestion: true, - -// }; - /** * The standalone table component for holding information about the users in * the database and allowing admins to remove users and promote users to admins. @@ -48,47 +33,31 @@ function QuestionTable() { { id: 'edit', label: 'Edit' }, ]; - const [selectedRow, setSelectedRow] = useState({}); - // Used to create the data type to create a row in the table function createAdminDashboardRow( question: IQuestion, // IUser, //fix this to question type - // promote: React.ReactElement, + deleteButton: React.ReactElement, edit: React.ReactElement, ): AdminDashboardRow { - // const { _id, qstn } = user; - const { _id, text, resultantAnswers, isQuestion } = question; + const { _id, text } = question; return { key: _id, question: text, - // resultantAnswerIds: resultantAnswerIds, - // isQuestion: isQuestion, - // promote, + deleteButton, edit, }; } const [questionList, setQuestionList] = useState([]); const questions = useData('admin/allQuestions'); // this is a route for GETTING ALL question data; TODO: update later - // TESTING: - // const questions = testq; - - // const self = useAppSelector(selectUser); // Upon getting the list of users for the database, set the state of the userList to contain all users except for logged in user useEffect(() => { - setQuestionList( - // questions?.data.filter( //don't actually need the filter i think but it's fine just making sure text isn't empty - // (entry: IQuestion) => entry && entry.text,// && entry.text !== self.text, - // ), - questions?.data, - // TESTING: - // [questions, questions], //testing - ); - }, [questions]); // [questions, self]); //should i actually be returning self here + setQuestionList(questions?.data); + }, [questions]); // update state of userlist to remove a user from the frontend representation of the data - const removeQuestion = (question: IQuestion) => { + const removeResource = (question: IQuestion) => { setQuestionList( questionList.filter( (entry: IQuestion) => @@ -96,39 +65,22 @@ function QuestionTable() { entry.text && entry.text !== question.text && // eslint-disable-next-line no-underscore-dangle - entry._id !== question._id, //! == question.text, + entry._id !== question._id, ), ); - deleteQuestion(question); + // eslint-disable-next-line no-underscore-dangle + deleteResource(question._id); }; const handleEditChange = (oldQ: IQuestion, newQ: IQuestion) => { - // setQuestionList(event.target.value); - removeQuestion(oldQ); - // addQuestion(newQ); - console.log('value is:', newQ.text); + setQuestionList( + questionList.map((q: IQuestion) => + // eslint-disable-next-line no-underscore-dangle + q.text === oldQ.text && q._id === oldQ._id ? newQ : q, + ), + ); }; - function editRow(row: IQuestion, newText: string) { - console.log('khgfjgfsjgfliglkghd'); - // row.text = newText; // 'hello ' + row.text; - } - - // idrk what this is but updated it for question - // update state of userlist to promote a user on the frontend representation - // const updateQuestion = (text: string) => { - // setQuestionList( - // questionList.map((entry) => { - // if (entry.text !== text) { - // return entry; - // } - // const newEntry = entry; - // newEntry.isQuestion = true; - // return newEntry; - // }), - // ); - // }; - // if the questionlist is not yet populated, display a loading spinner if (!questionList) { return ( @@ -143,36 +95,17 @@ function QuestionTable() { rows={questionList.map((question: IQuestion) => createAdminDashboardRow( question, - editRow(question, '')} - // open up text editor - // extract inputted text data from text editor GUI - // if isQuestion true --> replace current question.text with data from text editor GUI - // else (isQuestion false) --> take answer IDs and resource descriptions to replace all resource description text with the data from text editor GUI - // embedded links (clickable) - // save and turn off editing mode + removeRow={() => removeResource(question)} />, - - // , + , ), )} columns={columns} /> - - // setSelectedRow(row)} - // key={row.name} - // sx={{ "&:last-child td, &:last-child th": { border: 0 } }} - // > ); } diff --git a/client/src/AdminDashboard/api.tsx b/client/src/AdminDashboard/api.tsx index d4e52b75..a1c94f62 100644 --- a/client/src/AdminDashboard/api.tsx +++ b/client/src/AdminDashboard/api.tsx @@ -1,8 +1,7 @@ /** * A file containing all the api calls for the admin dashboard. */ -import { deleteData, putData, postData } from '../util/api'; -import { IAnswer } from '../util/types/answer'; +import { deleteData, putData } from '../util/api'; import { IQuestion } from '../util/types/question'; /** @@ -16,10 +15,8 @@ async function deleteUser(email: string) { return true; } -async function deleteQuestion(question: IQuestion) { - const res = await putData(`admin/deleteQuestion`, { - question, - }); +async function deleteResource(id: string) { + const res = await deleteData(`admin/resource/${id}`); if (res.error) return false; return true; } @@ -33,25 +30,6 @@ async function editQuestion(question: IQuestion) { return true; } -async function deleteResource(question: IQuestion, resource: IAnswer) { - // eslint-disable-next-line no-underscore-dangle - const res = await putData(`admin/deleteResource`, { - question, - resource, - }); - if (res.error) return false; - return true; -} - -// async function editAnswer(questionID: string, answer: string) { -// const res = await putData(`admin/${questionID}`, { //put = add, post = create new -// // call this for every answer -// answer, -// }); //does this edit or just add new question? -// if (res.error) return false; -// return true; -// } - /** * Sends a request to the server to promote a user to admin * @param email - the email of the user to promote @@ -63,10 +41,4 @@ async function upgradePrivilege(email: string) { return true; } -export { - deleteUser, - deleteQuestion, - editQuestion, - deleteResource, - upgradePrivilege, -}; +export { deleteUser, editQuestion, deleteResource, upgradePrivilege }; diff --git a/client/src/components/EditResource.tsx b/client/src/components/EditResource.tsx index 8502fb6e..5d4aa1e0 100644 --- a/client/src/components/EditResource.tsx +++ b/client/src/components/EditResource.tsx @@ -1,20 +1,8 @@ /* eslint-disable no-underscore-dangle */ import React, { useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; -import { - Typography, - Container, - Box, - Alert, - AlertTitle, - Button, -} from '@mui/material'; -import { sizing } from '@mui/system'; -import { - deleteQuestion, - deleteResource, - editQuestion, -} from '../AdminDashboard/api'; +import { Typography, Container, Box, Alert, AlertTitle } from '@mui/material'; +import { editQuestion } from '../AdminDashboard/api'; import { IAnswer } from '../util/types/answer'; import { IQuestion } from '../util/types/question'; import EditorGUI from './EditorGUI'; @@ -46,7 +34,7 @@ export default function EditResource() { - ***Please make sure that any link you add starts with + Please make sure that any link you add starts with "http://" or{' '} "https://"! diff --git a/server/src/controllers/admin.controller.ts b/server/src/controllers/admin.controller.ts index 2cdf5b5c..62085175 100644 --- a/server/src/controllers/admin.controller.ts +++ b/server/src/controllers/admin.controller.ts @@ -6,6 +6,7 @@ import express from 'express'; import ApiError from '../util/apiError'; import StatusCode from '../util/statusCode'; import { IUser } from '../models/user.model'; +import { IQuestion } from '../models/question.model'; import { upgradeUserToAdmin, getUserByEmail, @@ -15,10 +16,8 @@ import { import { editQuestion, getAllQuestionsFromDB, - // getQuestionById, - // deleteQuestionById, - deleteResource, - deleteQuestion, + getQuestionById, + deleteQuestionById, } from '../services/question.service'; /** @@ -160,50 +159,32 @@ const editQuestionText = async ( }; /** - * Delete resource + * Delete a resource question from the database. The id of the resource is expected to be in the request parameter (url). Send a 200 OK status code on success. */ -const deleteResourceFromQuestion = async ( +const deleteResource = async ( req: express.Request, res: express.Response, next: express.NextFunction, ) => { - const { question, resource } = req.body; - if (!question) { - next(ApiError.missingFields(['question'])); + const { id } = req.params; + if (!id) { + next(ApiError.missingFields(['id'])); return; } - if (!resource) { - next(ApiError.missingFields(['resource'])); - return; - } - deleteResource(question, resource) - .then(() => { - res.sendStatus(StatusCode.OK); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .catch((e) => { - next(ApiError.internal('Unable to delete resource.')); - }); -}; -const deleteQuestionFromDB = async ( - req: express.Request, - res: express.Response, - next: express.NextFunction, -) => { - const { question } = req.body; - if (!question) { - next(ApiError.missingFields(['question'])); + // Check if resource to delete is an admin + const resource: IQuestion | null = await getQuestionById(parseInt(id, 10)); + if (!resource) { + next(ApiError.notFound(`Resource with id ${id} does not exist`)); return; } - deleteQuestion(question) - .then(() => { - res.sendStatus(StatusCode.OK); - }) + // resource is a question + deleteQuestionById(resource._id) + .then(() => res.sendStatus(StatusCode.OK)) // eslint-disable-next-line @typescript-eslint/no-unused-vars .catch((e) => { - next(ApiError.internal('Unable to delete question.')); + next(ApiError.internal('Failed to delete question.')); }); }; @@ -213,6 +194,5 @@ export { deleteUser, getAllQuestions, editQuestionText, - deleteResourceFromQuestion, - deleteQuestionFromDB, + deleteResource, }; diff --git a/server/src/routes/admin.route.ts b/server/src/routes/admin.route.ts index c5fa8718..f9a121ce 100644 --- a/server/src/routes/admin.route.ts +++ b/server/src/routes/admin.route.ts @@ -9,9 +9,8 @@ import { upgradePrivilege, deleteUser, getAllQuestions, + deleteResource, editQuestionText, - deleteResourceFromQuestion, - deleteQuestionFromDB, } from '../controllers/admin.controller'; import { isAuthenticated } from '../controllers/auth.middleware'; import { approve } from '../controllers/auth.controller'; @@ -63,12 +62,6 @@ router.delete('/:email', isAuthenticated, isAdmin, deleteUser); // router.get('/allQuestions', isAuthenticated, isAdmin, getAllQuestions); router.get('/allQuestions', getAllQuestions); -/** - * A PUT route to delete certain question. - * Expects an IQuestion - */ -router.put('/deleteQuestion', deleteQuestionFromDB); - /** * A PUT route to edit certain question. Checks first if the requestor is a * authenticated and is an admin. @@ -79,10 +72,11 @@ router.put('/deleteQuestion', deleteQuestionFromDB); router.put('/editQuestion', editQuestionText); /** - * A DELETE route to delete resource from resultantAnswers of a resource question. Checks first if the requestor is a - * authenticated and is an admin. - * Expects an IAnswer component + * A PUT route to delete a resource. Checks first if the requestor + * is a authenticated and is an admin. + * Expects the following fields in the URL: + * resource id (string) - The id of the resource to be deleted */ -router.put('/deleteResource', deleteResourceFromQuestion); +router.delete('/resource/:id', isAuthenticated, isAdmin, deleteResource); export default router; diff --git a/server/src/services/question.service.ts b/server/src/services/question.service.ts index 7848cecd..f3ee42f0 100644 --- a/server/src/services/question.service.ts +++ b/server/src/services/question.service.ts @@ -107,6 +107,16 @@ const deleteQuestion = async (question: IQuestion) => { await Question.findByIdAndDelete(qID).exec(); }; +/** + * A function that deletes a question from the database. + * @param id The id of the question to delete. + * @returns The deleted {@link Question} + */ +const deleteQuestionById = async (id: number) => { + const question = await Question.findByIdAndDelete(id).exec(); + return question; +}; + export { createQuestion, getQuestionById, @@ -116,4 +126,5 @@ export { deleteResource, deleteQuestion, // deleteUserById, + deleteQuestionById, };