Merge branch 'dev' into search
This commit is contained in:
commit
4384db3df0
29 changed files with 316 additions and 110 deletions
|
|
@ -16,6 +16,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"marked": "^4.0.18",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
|
|
|
|||
|
|
@ -3,11 +3,23 @@ import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from '
|
|||
import ReactTimeAgo from 'react-time-ago';
|
||||
|
||||
import { Markdown } from 'components';
|
||||
import { readMail } from 'services/mailServices';
|
||||
|
||||
export default function MailUnit({ sender, createdAt, subject, text }) {
|
||||
export default function MailUnit({
|
||||
mail_id,
|
||||
sender,
|
||||
createdAt,
|
||||
subject,
|
||||
text,
|
||||
read,
|
||||
}) {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
onClick={() => {readMail(mail_id)}}
|
||||
sx={read && {backgroundColor: 'lightGray'}}
|
||||
>
|
||||
<Box style={{ width: '100%' }} display={'flex'}>
|
||||
<Typography>From {sender}</Typography>
|
||||
<span style={{ width: '1vw' }} />
|
||||
|
|
@ -16,6 +28,8 @@ export default function MailUnit({ sender, createdAt, subject, text }) {
|
|||
<Typography>
|
||||
<b>{subject}</b>
|
||||
</Typography>
|
||||
<span style={{ width: '2vw' }} />
|
||||
<Typography>Read: {String(read)}</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@ import { IconButton, Typography } from '@mui/material';
|
|||
import Gravatar from 'react-gravatar';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Profile({ userData: { email, level, points } }) {
|
||||
export default function Profile({ userData: { badgeCount, email, level, points } }) {
|
||||
return (
|
||||
<>
|
||||
<IconButton component={Link} to='/dashboard'>
|
||||
<Gravatar email={email} size={20} style={{ borderRadius: '100%' }} />
|
||||
</IconButton>
|
||||
<Typography variant='body1'>
|
||||
<b>{level}</b> - {points}
|
||||
</Typography>
|
||||
<Typography display='inline'><b>{level}</b></Typography>
|
||||
<Typography display='inline' m={1}>{points}</Typography>
|
||||
<Typography color='#ffd700' display='inline'>{badgeCount?.gold}</Typography>
|
||||
/<Typography color='#c0c0c0' display='inline'>{badgeCount?.silver}</Typography>
|
||||
/<Typography color='#cd7f32' display='inline'>{badgeCount?.bronze}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Divider, ListItem, ListItemText } from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { Divider, IconButton, ListItem, ListItemText } from '@mui/material';
|
||||
|
||||
import { CreationInfoTag } from 'controllers';
|
||||
import { VoteControl } from 'controllers/QAControllers';
|
||||
|
|
@ -11,6 +12,7 @@ export default function AnswerComment({
|
|||
text,
|
||||
upvotes,
|
||||
getVote,
|
||||
onDelete,
|
||||
updateVote,
|
||||
}) {
|
||||
return (
|
||||
|
|
@ -21,6 +23,9 @@ export default function AnswerComment({
|
|||
{text}
|
||||
</ListItemText>
|
||||
<VoteControl {...{ downvotes, getVote, updateVote, upvotes }} />
|
||||
{onDelete && <IconButton onClick={onDelete}>
|
||||
<DeleteIcon />
|
||||
</IconButton>}
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Divider, ListItem, ListItemText } from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { Divider, IconButton, ListItem, ListItemText } from '@mui/material';
|
||||
|
||||
import { CreationInfoTag } from 'controllers';
|
||||
import { VoteControl } from 'controllers/QAControllers';
|
||||
|
|
@ -11,7 +12,8 @@ export default function Comment({
|
|||
text,
|
||||
upvotes,
|
||||
getVote,
|
||||
updateVote,
|
||||
onDelete,
|
||||
updateVote
|
||||
}) {
|
||||
return (
|
||||
<span key={comment_id}>
|
||||
|
|
@ -21,6 +23,9 @@ export default function Comment({
|
|||
{text}
|
||||
</ListItemText>
|
||||
<VoteControl {...{ downvotes, getVote, updateVote, upvotes, creator }} />
|
||||
{onDelete && <IconButton onClick={onDelete}>
|
||||
<DeleteIcon />
|
||||
</IconButton>}
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</span>
|
||||
|
|
|
|||
67
client/src/components/QAComponents/Suggest.jsx
Normal file
67
client/src/components/QAComponents/Suggest.jsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import {Tooltip, Button, Card, CardContent, Typography} from '@mui/material'
|
||||
import { editQuestion } from 'services/questionsServices';
|
||||
import EditFormController from "controllers/FormControllers/EditQuestionFormController";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUser, useQuestion } from 'contexts';
|
||||
import { Markdown } from 'components';
|
||||
export default function Suggest(){
|
||||
const [show, setShow] = useState(false);
|
||||
const [canSuggest, setCanSuggest] = useState(false)
|
||||
const {userData} = useUser();
|
||||
const {questionData} = useQuestion();
|
||||
const [ongoingEdit, setOngoingEdit] = useState({users : [], new : []})
|
||||
|
||||
useEffect(()=>{
|
||||
setCanSuggest(userData.level >= 7)
|
||||
|
||||
if(ongoingEdit.users.length > 0)setCanSuggest((userData.username !== ongoingEdit.users[0]))
|
||||
if(!questionData.loading) setOngoingEdit({users : questionData.edit, new : questionData.editText})
|
||||
},[userData, questionData, setCanSuggest])
|
||||
|
||||
async function toggleShow(){
|
||||
if(questionData.edit.length === 0){
|
||||
setShow(!show)
|
||||
}else{
|
||||
const { status } = await editQuestion(questionData.question_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tooltip
|
||||
title= {
|
||||
canSuggest
|
||||
? ''
|
||||
: 'You need to be level 7'}
|
||||
>
|
||||
<span>
|
||||
<Button disabled={!canSuggest} variant='contained' onClick={toggleShow}>
|
||||
Add Suggestion
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{show && (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<EditFormController />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{ongoingEdit.users.length > 0 &&
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Markdown {...{content: ongoingEdit.new[0]}}/>
|
||||
<Markdown {...{content: ongoingEdit.new[1]}}/>
|
||||
</CardContent>
|
||||
<Typography> {ongoingEdit.users} have voted to edit the question </Typography>
|
||||
</Card>
|
||||
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { List, ListItem } from '@mui/material';
|
||||
import Suggest from 'components/QAComponents/Suggest';
|
||||
import { AnswersList, CommentsList, CreateAnswer, Question } from 'controllers/QAControllers';
|
||||
|
||||
export default function QA() {
|
||||
|
|
@ -8,7 +9,8 @@ export default function QA() {
|
|||
<ListItem sx={{ pl: 4 }}>
|
||||
<CommentsList />
|
||||
</ListItem>
|
||||
<CreateAnswer />
|
||||
<CreateAnswer/>
|
||||
<Suggest/>
|
||||
<div id='answersList' />
|
||||
<AnswersList />
|
||||
</List>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ export default function UserProvider({ children }) {
|
|||
const [userData, setUserData] = useState(initialUserData)
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const loadUserData = async () => {
|
||||
const { user, error } = await remember();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
import { Form } from 'controllers/FormControllers';
|
||||
import { generateEditFields } from 'services/fields';
|
||||
import { editQuestion } from 'services/questionsServices';
|
||||
import { questionSchema } from 'services/schemas';
|
||||
import { useQuestion } from 'contexts';
|
||||
|
||||
export default function EditFormController() {
|
||||
const {questionData} = useQuestion();
|
||||
const {title, text} = questionData;
|
||||
|
||||
|
||||
|
||||
const editTheQuestion = async (fields) => {
|
||||
console.log("hello")
|
||||
const { status } = await editQuestion(questionData.question_id, fields);
|
||||
window.location.reload(false);
|
||||
};
|
||||
|
||||
const field = generateEditFields
|
||||
|
||||
return !questionData.loading && Form({
|
||||
fields: field,
|
||||
onSubmit: editTheQuestion,
|
||||
validationSchema: questionSchema,
|
||||
initialValues: {etext : text, etitle : title}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useFormik } from 'formik';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useQuestion } from 'contexts';
|
||||
import { Form } from 'components';
|
||||
import { useForm } from 'contexts';
|
||||
|
||||
|
|
@ -10,11 +10,15 @@ export default function FormController({
|
|||
validate,
|
||||
validationSchema,
|
||||
children,
|
||||
initialValues
|
||||
}) {
|
||||
const { setContent } = useForm();
|
||||
|
||||
|
||||
const { setContent } = useForm();
|
||||
|
||||
useEffect(() => {
|
||||
setContent('');
|
||||
|
||||
}, [setContent]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
|
|
@ -22,7 +26,7 @@ export default function FormController({
|
|||
};
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: fields.reduce((acc, { id }) => ({ ...acc, [id]: '' }), {}),
|
||||
initialValues: fields.reduce((acc, { id }) => ({ ...acc, [id]: initialValues[id] || '' }), {}),
|
||||
onSubmit,
|
||||
validate,
|
||||
validateOnChange: false,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
import { useUser } from 'contexts';
|
||||
import { AnswerComment } from 'components/QAComponents';
|
||||
import { getAnswerCommentVote, updateAnswerCommentVote } from 'services/questionsServices';
|
||||
import { getAnswerCommentVote, deleteAnswerComment, updateAnswerCommentVote } from 'services/questionsServices';
|
||||
|
||||
export default function AnswerCommentController({ answer_id, comment_id, question_id, ...props }) {
|
||||
const { userData } = useUser();
|
||||
|
||||
const getVote = () => getAnswerCommentVote(question_id, answer_id, comment_id);
|
||||
const updateVote = (data) => updateAnswerCommentVote(question_id, answer_id, comment_id, data);
|
||||
const onDelete = async () => {
|
||||
if (userData?.username === props?.creator) {
|
||||
const { status } = await deleteAnswerComment(question_id, answer_id, comment_id);
|
||||
if (status === 200)
|
||||
window.location.reload(false);
|
||||
}
|
||||
}
|
||||
|
||||
return <AnswerComment {...{ ...props, getVote, updateVote }} />;
|
||||
return <AnswerComment {...{ getVote, onDelete, updateVote, ...props }} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
import { useUser } from 'contexts';
|
||||
import { Comment } from 'components/QAComponents';
|
||||
import { getCommentVote, updateCommentVote } from 'services/questionsServices';
|
||||
import { getCommentVote, deleteQuestionComment, updateCommentVote } from 'services/questionsServices';
|
||||
|
||||
|
||||
export default function CommentController({ comment_id, question_id, ...props }) {
|
||||
const { userData } = useUser();
|
||||
|
||||
const getVote = () => getCommentVote(question_id, comment_id);
|
||||
const updateVote = (data) => updateCommentVote(question_id, comment_id, data);
|
||||
const onDelete = async () => {
|
||||
if (userData?.username === props?.creator) {
|
||||
const { status } = await deleteQuestionComment(question_id, comment_id);
|
||||
if (status === 200)
|
||||
window.location.reload(false);
|
||||
}
|
||||
}
|
||||
|
||||
return <Comment {...{ getVote, updateVote, ...props }} />;
|
||||
return <Comment {...{ getVote, onDelete, updateVote, ...props }} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ export default function QuestionController() {
|
|||
|
||||
useEffect(() => {
|
||||
const permissions = getPermissions(questionData, userData, ongoingVote);
|
||||
if(userData.username)canBounty = (userData.level >= 4 && status !== 'closed' && !questionData.hasBounty && !questionData.hasAcceptedAnswer)
|
||||
|
||||
if (userData.username) canBounty = (userData.level >= 4 && status !== 'closed' && !questionData.hasBounty && !questionData.hasAcceptedAnswer)
|
||||
|
||||
permissions.canBounty = canBounty;
|
||||
setPermissions(permissions);
|
||||
setVote();
|
||||
|
|
@ -77,14 +77,14 @@ export default function QuestionController() {
|
|||
ongoingVoteSet(reopen, 'reopen');
|
||||
}
|
||||
}
|
||||
function handleBounty(amount){
|
||||
if(show){
|
||||
const data = {user : userData.username, hasAcceptedAnswer: questionData.hasAcceptedAnswer , amount: amount , isOpen: (questionData.status !== 'closed') , hasBounty: questionData.hasBounty }
|
||||
function handleBounty(amount) {
|
||||
if (show) {
|
||||
const data = { user: userData.username, hasAcceptedAnswer: questionData.hasAcceptedAnswer, amount: amount, isOpen: (questionData.status !== 'closed'), hasBounty: questionData.hasBounty }
|
||||
addBounty(question_id, data)
|
||||
questionData.hasBounty = amount;
|
||||
userData.points -= amount;
|
||||
setShow(false)
|
||||
}else{
|
||||
} else {
|
||||
setShow(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ export default function QuestionController() {
|
|||
postComment,
|
||||
show,
|
||||
handleBounty
|
||||
|
||||
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,22 @@ const searchFields = [
|
|||
{ id: 'tags', label: 'Tags (separated by spaces)' },
|
||||
];
|
||||
|
||||
const generateEditFields =
|
||||
|
||||
[
|
||||
{
|
||||
id: 'etitle',
|
||||
label: 'Title',
|
||||
|
||||
},
|
||||
{
|
||||
id: 'etext',
|
||||
label: 'Text',
|
||||
multiline: true,
|
||||
|
||||
},
|
||||
]
|
||||
|
||||
export {
|
||||
answerFields,
|
||||
askQuestionFields,
|
||||
|
|
@ -142,4 +158,5 @@ export {
|
|||
recoverFields,
|
||||
resetFields,
|
||||
searchFields,
|
||||
generateEditFields
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
{
|
||||
"Level 1": 1,
|
||||
"Level 2": 15,
|
||||
"Level 3": 50,
|
||||
"Level 4": 125,
|
||||
"Level 5": 1000,
|
||||
"Level 6": 3000,
|
||||
"Level 7": 10000
|
||||
"Level 4": 75,
|
||||
"Level 5": 125,
|
||||
"Level 6": 1000,
|
||||
"Level 7": 2000,
|
||||
"Level 8" : 3000,
|
||||
"Level 9" : 10000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ import { createEndpoint } from './api';
|
|||
|
||||
const callMailAPI = createEndpoint('/mail');
|
||||
|
||||
const postMail = async (data) => callMailAPI('post', ``, data);
|
||||
|
||||
const getMail = async () => callMailAPI('get', ``);
|
||||
|
||||
export { getMail, postMail };
|
||||
const postMail = async (data) => callMailAPI('post', ``, data);
|
||||
|
||||
const readMail = async (mail_id) => callMailAPI('patch', `/${mail_id}`, { read: 'true' });
|
||||
|
||||
|
||||
export { getMail, postMail, readMail };
|
||||
|
|
|
|||
|
|
@ -11,8 +11,17 @@ const postQuestion = async (data) => callQuestionsAPI('post', ``, data);
|
|||
|
||||
const getQuestion = async (question_id) => callQuestionsAPI('get', `/${question_id}`);
|
||||
|
||||
const addBounty = async (question_id, data) =>
|
||||
callQuestionsAPI('patch', `/${question_id}/addBounty`, data);
|
||||
const addBounty = async (question_id,data) => callQuestionsAPI(
|
||||
'patch',
|
||||
`/${question_id}/addBounty`,
|
||||
data
|
||||
|
||||
)
|
||||
const editQuestion = async (question_id,data) => callQuestionsAPI(
|
||||
'patch',
|
||||
`/${question_id}/edit`,
|
||||
data
|
||||
)
|
||||
|
||||
const updateQuestion = async (question_id, data) =>
|
||||
callQuestionsAPI('patch', `/${question_id}`, data);
|
||||
|
|
@ -110,4 +119,5 @@ export {
|
|||
openQuestion,
|
||||
protectQuestion,
|
||||
addBounty,
|
||||
editQuestion
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ import { createEndpoint } from './api';
|
|||
const callUsersAPI = createEndpoint('/users');
|
||||
|
||||
const getLoginAttempts = () => {
|
||||
let loginAttempts = Number(Cookies.get('loginAttempts')) || 0;
|
||||
let loginTimeout = Number(Cookies.get('loginTimeout')) - Date.now() || 0;
|
||||
let loginAttempts = Number(Cookies.get('loginAttempts')) || 0;
|
||||
let loginTimeout = Number(Cookies.get('loginTimeout')) - Date.now() || 0;
|
||||
|
||||
if (loginTimeout < 0) {
|
||||
Cookies.remove('loginAttempts');
|
||||
Cookies.remove('loginTimeout');
|
||||
loginAttempts = 0;
|
||||
loginTimeout = 0;
|
||||
}
|
||||
if (loginTimeout < 0) {
|
||||
Cookies.remove('loginAttempts');
|
||||
Cookies.remove('loginTimeout');
|
||||
loginAttempts = 0;
|
||||
loginTimeout = 0;
|
||||
}
|
||||
|
||||
return { loginAttempts, loginTimeout };
|
||||
return { loginAttempts, loginTimeout };
|
||||
};
|
||||
|
||||
const getUser = async (username) => callUsersAPI('get', `/${username}`);
|
||||
|
|
@ -27,28 +27,28 @@ const getUserBadges = async () => callUsersAPI('get', `/badges`);
|
|||
|
||||
const getUserQuestions = async () => callUsersAPI('get', `/questions`);
|
||||
|
||||
const deleteUser = async () => callUsersAPI('delete',`/delete`)
|
||||
const deleteUser = async () => callUsersAPI('delete', `/delete`)
|
||||
|
||||
const incrementLoginAttempts = () => {
|
||||
const { loginAttempts } = getLoginAttempts();
|
||||
Cookies.set('loginAttempts', loginAttempts + 1);
|
||||
if (loginAttempts >= 2) Cookies.set('loginTimeout', Date.now() + 1000 * 60 * 5);
|
||||
const { loginAttempts } = getLoginAttempts();
|
||||
Cookies.set('loginAttempts', loginAttempts + 1);
|
||||
if (loginAttempts >= 2) Cookies.set('loginTimeout', Date.now() + 1000 * 60 * 5);
|
||||
};
|
||||
|
||||
const login = async ({ username, password }) => {
|
||||
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const { token, ...data } = await callUsersAPI(
|
||||
'post',
|
||||
`/login`,
|
||||
{ remember: false },
|
||||
`basic ${encoded}`
|
||||
);
|
||||
if (token) {
|
||||
Cookies.set('token', token);
|
||||
Cookies.remove('loginAttempts');
|
||||
Cookies.remove('loginTimeout');
|
||||
}
|
||||
return data;
|
||||
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const { token, ...data } = await callUsersAPI(
|
||||
'post',
|
||||
`/login`,
|
||||
{ remember: false },
|
||||
`basic ${encoded}`
|
||||
);
|
||||
if (token) {
|
||||
Cookies.set('token', token);
|
||||
Cookies.remove('loginAttempts');
|
||||
Cookies.remove('loginTimeout');
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const logout = async () => callUsersAPI('post', `/login`, { remember: false });
|
||||
|
|
@ -58,13 +58,13 @@ const updateUser = async (data) => callUsersAPI('patch', ``, data);
|
|||
const register = async (data) => callUsersAPI('post', ``, data);
|
||||
|
||||
const remember = async () => {
|
||||
if (Cookies.get('token')) {
|
||||
const { user, error } = await callUsersAPI('get', `/remember`);
|
||||
if (user)
|
||||
return { user };
|
||||
Cookies.remove('token');
|
||||
return { error }
|
||||
}
|
||||
if (Cookies.get('token')) {
|
||||
const { user, error } = await callUsersAPI('get', `/remember`);
|
||||
if (user)
|
||||
return { user };
|
||||
Cookies.remove('token');
|
||||
return { error }
|
||||
}
|
||||
}
|
||||
|
||||
const requestReset = async (data) => callUsersAPI('post', `/reset`, data);
|
||||
|
|
@ -72,18 +72,18 @@ const requestReset = async (data) => callUsersAPI('post', `/reset`, data);
|
|||
const resetPassword = async (id, data) => callUsersAPI('post', `/reset/${id}`, data);
|
||||
|
||||
export {
|
||||
getLoginAttempts,
|
||||
getUser,
|
||||
getUserAnswers,
|
||||
getUserBadges,
|
||||
getUserQuestions,
|
||||
incrementLoginAttempts,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
remember,
|
||||
updateUser,
|
||||
requestReset,
|
||||
resetPassword,
|
||||
deleteUser
|
||||
getLoginAttempts,
|
||||
getUser,
|
||||
getUserAnswers,
|
||||
getUserBadges,
|
||||
getUserQuestions,
|
||||
incrementLoginAttempts,
|
||||
login,
|
||||
logout,
|
||||
register,
|
||||
remember,
|
||||
updateUser,
|
||||
requestReset,
|
||||
resetPassword,
|
||||
deleteUser
|
||||
};
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -35,6 +35,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"marked": "^4.0.18",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
|
|
@ -32177,6 +32178,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"marked": "^4.0.18",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "*",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ mongoose.connection.on(
|
|||
console.error.bind(console, '[ERROR]: MongoDB connection error - ')
|
||||
);
|
||||
|
||||
//cron.schedule('*/30 * * * * *', refreshQuestions);
|
||||
//cron.schedule('*/2 * * * *', refreshUsers);
|
||||
// cron.schedule('*/30 * * * * *', refreshQuestions);
|
||||
// cron.schedule('*/2 * * * *', refreshUsers);
|
||||
|
||||
server.listen(port, console.log(`Listening on port: ${port}`));
|
||||
server.on('error', onError);
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ const Answer = mongoose.Schema(
|
|||
);
|
||||
|
||||
Answer.post('findOneAndUpdate', (doc) => {
|
||||
const badges = calculateAnswerBadges(doc.points);
|
||||
|
||||
User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } });
|
||||
if (doc) {
|
||||
const badges = calculateAnswerBadges(doc.upvotes - doc.downvotes);
|
||||
User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Answer', Answer);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const Question = mongoose.Schema(
|
|||
reopen: [String],
|
||||
close: [String],
|
||||
edit: [String],
|
||||
editText: { type: String },
|
||||
editText: [String],
|
||||
tags: [String],
|
||||
question_id: { type: String, required: true },
|
||||
status: { type: String, required: true, default: 'open' },
|
||||
|
|
@ -24,15 +24,16 @@ const Question = mongoose.Schema(
|
|||
title: { type: String, required: true },
|
||||
upvotes: { type: Number, required: true, default: 0 },
|
||||
views: { type: Number, required: true, default: 0 },
|
||||
hasBounty: {type: Number, required: false}
|
||||
hasBounty: { type: Number, required: false },
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
Question.post('findOneAndUpdate', (doc) => {
|
||||
const badges = calculateQuestionBadges(doc.points);
|
||||
|
||||
User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } });
|
||||
if(doc){
|
||||
const badges = calculateQuestionBadges(doc.upvotes - doc.downvotes);
|
||||
User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Question', Question);
|
||||
|
|
|
|||
|
|
@ -17,10 +17,8 @@ const User = mongoose.Schema(
|
|||
|
||||
User.post('findOneAndUpdate', async (doc) => {
|
||||
const badges = calculateUserBadges(doc.points);
|
||||
|
||||
doc.badges = [...new Set([...doc.badges, ...badges])];
|
||||
|
||||
doc.save();
|
||||
await doc.save();
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('User', User);
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ async function Get(req, res) {
|
|||
|
||||
// Fetch mail if expired
|
||||
if (Number(lastMailFetch) + config.mailExpires < Date.now()) {
|
||||
const messages = await getAllMail({ username });
|
||||
return res.send({ messages });
|
||||
} else {
|
||||
|
||||
await getAllMail({ username })
|
||||
}
|
||||
const messages = await Mail.find({ receiver: username }).sort({
|
||||
createdAt: 'desc',
|
||||
});
|
||||
|
||||
return res.send({ messages });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Get;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const getUserLevel = require('server/utils/getUserLevel');
|
|||
async function EditQuestionStatusClosed(req, res) {
|
||||
const { user } = req;
|
||||
const { question_id } = req.params;
|
||||
const { text } = req.body;
|
||||
const { etext, etitle } = req.body;
|
||||
|
||||
// Verify user has required level
|
||||
if (getUserLevel(user.points) < 7) {
|
||||
|
|
@ -20,12 +20,12 @@ async function EditQuestionStatusClosed(req, res) {
|
|||
|
||||
// Toggle question vote
|
||||
if (question.edit.length === 0) {
|
||||
if (!text) {
|
||||
if (!etext || !etitle) {
|
||||
return res.status(400).send(config.errorIncomplete);
|
||||
}
|
||||
|
||||
await Question.findByIdAndUpdate(question_id, {
|
||||
editText: text,
|
||||
editText: etext,
|
||||
$push: { edit: user.username },
|
||||
});
|
||||
|
||||
|
|
@ -48,11 +48,13 @@ async function EditQuestionStatusClosed(req, res) {
|
|||
if (question.edit.length === 2) {
|
||||
const question = await Question.findByIdAndUpdate(question_id, {
|
||||
edit: [],
|
||||
text,
|
||||
etext,
|
||||
etitle
|
||||
});
|
||||
|
||||
const { success } = await createRequest('patch', `/questions/${question_id}`, {
|
||||
text,
|
||||
etext,
|
||||
etitle
|
||||
});
|
||||
|
||||
return success
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ router.post('/', Register);
|
|||
router.patch('/', tokenAuth, Edit);
|
||||
router.get('/questions', tokenAuth, Questions);
|
||||
router.get('/answers', tokenAuth, Answers);
|
||||
router.get('/badge', tokenAuth, GetBadges);
|
||||
router.get('/badges', tokenAuth, GetBadges);
|
||||
router.post('/login', basicAuth, Login);
|
||||
router.get('/remember', tokenAuth, Remember);
|
||||
router.delete('/logout', tokenAuth, Logout);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ async function GetBadges(req, res) {
|
|||
const response = badges.reduce(
|
||||
(acc, badge) => {
|
||||
if (user.badges.includes(badge.title)) {
|
||||
acc.obtained.append(badge);
|
||||
acc.obtained.push(badge);
|
||||
} else {
|
||||
acc.unobtained.append(badge);
|
||||
acc.unobtained.push(badge);
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
|
@ -18,7 +18,7 @@ async function GetBadges(req, res) {
|
|||
{ obtained: [], unobtained: [] }
|
||||
);
|
||||
|
||||
return res.send(response);
|
||||
return res.send({ badges: response });
|
||||
}
|
||||
|
||||
module.exports = GetBadges;
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ async function GetUser(req, res) {
|
|||
email: newUser.email,
|
||||
points: newUser.points,
|
||||
level: getUserLevel(newUser.points),
|
||||
badgeCount,
|
||||
},
|
||||
badgeCount,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,38 @@
|
|||
const getUserLevel = require('server/utils/getUserLevel');
|
||||
const Badge = require('../../db/models/Badge');
|
||||
|
||||
async function Remember(req, res) {
|
||||
const { user } = req;
|
||||
|
||||
const badges = await Badge.find({ title: { $in: user.badges } });
|
||||
|
||||
const badgeCount = badges.reduce(
|
||||
(acc, badge) => {
|
||||
if (badge.rank === 'Gold') {
|
||||
acc.gold = acc.gold + 1;
|
||||
} else if (badge.rank === 'Silver') {
|
||||
acc.silver = acc.silver + 1;
|
||||
} else if (badge.rank === 'Bronze') {
|
||||
acc.bronze = acc.bronze + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
gold: 0,
|
||||
silver: 0,
|
||||
bronze: 0,
|
||||
}
|
||||
);
|
||||
|
||||
return res.send({
|
||||
user: {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
points: user.points,
|
||||
level: getUserLevel(user.points),
|
||||
}
|
||||
badgeCount,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue