Merged dev into main
This commit is contained in:
commit
b813ddb3a8
26 changed files with 380 additions and 162 deletions
|
|
@ -3,6 +3,7 @@
|
|||
## BDPA NHSCC 2022 Problem:
|
||||
|
||||
- [Problem Statement](https://github.com/nhscc/problem-statements/tree/main/2022/qoverflow)
|
||||
- [Backend Documentation](https://hscc8udvc7gs.docs.apiary.io/)
|
||||
|
||||
## Solution
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"nvm": "^0.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Card, CardContent } from '@mui/material';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Inbox, SendMail } from 'controllers';
|
||||
import { MdPreview } from 'components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useUser } from 'contexts';
|
||||
|
|
|
|||
|
|
@ -3,19 +3,33 @@ import { Form } from 'controllers/FormControllers';
|
|||
import { composeMailFields } from 'services/fields';
|
||||
import { postMail } from 'services/mailServices';
|
||||
import { mailSchema } from 'services/schemas';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
export default function SendMailController() {
|
||||
const navigate = useNavigate();
|
||||
const { userData } = useUser();
|
||||
|
||||
const sendMail = async (data) => {
|
||||
const { status } = await postMail(data);
|
||||
if (status === 200)
|
||||
navigate('/');
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (checkAuth()) {
|
||||
navigate('/users/login', {
|
||||
state: {
|
||||
name: 'ask',
|
||||
msg: 'you need to be authenticated to access this feature',
|
||||
prevPath: '/ask',
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
function checkAuth() {
|
||||
if (!userData.username) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Form({
|
||||
fields: composeMailFields,
|
||||
onSubmit: sendMail,
|
||||
validationSchema: mailSchema
|
||||
onSubmit: postMail,
|
||||
validationSchema: mailSchema,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import ResetForm from './ResetFormController';
|
|||
import SendMail from './SendMailController';
|
||||
import SearchForm from './SearchFormController';
|
||||
import UpdateForm from './UpdateFormController';
|
||||
|
||||
import AnswerForm from './AnwerFormController';
|
||||
export {
|
||||
AnswerForm,
|
||||
AskForm,
|
||||
|
|
@ -19,5 +19,6 @@ export {
|
|||
ResetForm,
|
||||
SendMail,
|
||||
SearchForm,
|
||||
UpdateForm
|
||||
UpdateForm,
|
||||
AnswerForm,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,28 +8,28 @@ const answerFields = [
|
|||
{
|
||||
id: 'text',
|
||||
label: 'Answer',
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
];
|
||||
|
||||
const askQuestionFields = [
|
||||
{
|
||||
id: 'title',
|
||||
label: 'Title'
|
||||
label: 'Title',
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
label: 'Text',
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
];
|
||||
|
||||
const commentFields = [
|
||||
{
|
||||
id: 'text',
|
||||
label: 'Comment',
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
];
|
||||
|
||||
const composeMailFields = [
|
||||
|
|
@ -39,38 +39,38 @@ const composeMailFields = [
|
|||
},
|
||||
{
|
||||
id: 'subject',
|
||||
label: 'Subject:',
|
||||
label: 'Subject',
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
label: 'Body',
|
||||
multiline: true,
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const loginFields = [
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username'
|
||||
title: 'Username',
|
||||
},
|
||||
{
|
||||
id: 'password',
|
||||
title: 'Password',
|
||||
type: 'password'
|
||||
}
|
||||
type: 'password',
|
||||
},
|
||||
];
|
||||
|
||||
const recoverFields = [
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username'
|
||||
}
|
||||
title: 'Username',
|
||||
},
|
||||
];
|
||||
|
||||
const registerFields = [
|
||||
{
|
||||
id: 'username',
|
||||
title: 'Username'
|
||||
title: 'Username',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
|
|
@ -85,7 +85,7 @@ const registerFields = [
|
|||
{
|
||||
id: 'captcha',
|
||||
title: 'CAPTCHA',
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const resetFields = [
|
||||
|
|
@ -94,7 +94,7 @@ const resetFields = [
|
|||
helperText: (value) => `Password strength: ${passwordStrength(value)}`,
|
||||
title: 'Password',
|
||||
type: 'password',
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const patchFields = [
|
||||
|
|
@ -107,8 +107,8 @@ const patchFields = [
|
|||
helperText: (value) => `Password strength: ${passwordStrength(value)}`,
|
||||
title: 'Password',
|
||||
type: 'password',
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
const searchFields = [
|
||||
{
|
||||
|
|
@ -120,13 +120,13 @@ const searchFields = [
|
|||
label: 'Text',
|
||||
},
|
||||
{
|
||||
id: 'createdAt',
|
||||
id: 'createdAt',
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
id: 'creator',
|
||||
label: 'Creator',
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
export {
|
||||
|
|
@ -139,5 +139,5 @@ export {
|
|||
registerFields,
|
||||
recoverFields,
|
||||
resetFields,
|
||||
searchFields
|
||||
searchFields,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,17 +2,8 @@ import { createEndpoint } from './api';
|
|||
|
||||
const callMailAPI = createEndpoint('/mail');
|
||||
|
||||
const postMail = async (data) =>
|
||||
callMailAPI(
|
||||
'post',
|
||||
``,
|
||||
data
|
||||
);
|
||||
const postMail = async (data) => callMailAPI('post', ``, data);
|
||||
|
||||
const getMail = async () =>
|
||||
callMailAPI(
|
||||
'get',
|
||||
``
|
||||
);
|
||||
const getMail = async () => callMailAPI('get', ``);
|
||||
|
||||
export { getMail, postMail };
|
||||
|
|
|
|||
117
package-lock.json
generated
117
package-lock.json
generated
|
|
@ -30,6 +30,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"nvm": "^0.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
|
|
@ -5549,6 +5550,14 @@
|
|||
"version": "1.2.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/charm": {
|
||||
"version": "0.1.2",
|
||||
"license": "MIT/X11"
|
||||
|
|
@ -6020,6 +6029,14 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"license": "MIT"
|
||||
|
|
@ -9472,6 +9489,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-retina": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
|
||||
"integrity": "sha512-/tCmbIETZwCd8uHWO+GvbRa7jxwHFHdfetHfiwoP0aN9UDf3prUJMtKn7iBFYipYhqY1bSTjur8hC/Dakt8eyw=="
|
||||
},
|
||||
"node_modules/is-root": {
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
|
|
@ -12491,6 +12513,16 @@
|
|||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"dependencies": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-definitions": {
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
|
|
@ -15575,6 +15607,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"funding": [
|
||||
|
|
@ -15856,6 +15900,19 @@
|
|||
"version": "2.0.4",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-gravatar": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-gravatar/-/react-gravatar-2.6.3.tgz",
|
||||
"integrity": "sha512-yITonigS2LmG7Fw0gWfZfcVwy1mpiBHNVmoFyetitQjXu7JCYoE6jtub0GIfq+ydpnQSYyJT3kwpX6zj1wXR4w==",
|
||||
"dependencies": {
|
||||
"is-retina": "^1.0.3",
|
||||
"md5": "^2.1.0",
|
||||
"query-string": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"license": "MIT",
|
||||
|
|
@ -18020,6 +18077,14 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"license": "MIT",
|
||||
|
|
@ -23572,6 +23637,11 @@
|
|||
"character-parser": {
|
||||
"version": "1.2.1"
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
|
||||
},
|
||||
"charm": {
|
||||
"version": "0.1.2"
|
||||
},
|
||||
|
|
@ -23865,6 +23935,11 @@
|
|||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.1.1"
|
||||
},
|
||||
|
|
@ -25907,6 +25982,11 @@
|
|||
"is-regexp": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"is-retina": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
|
||||
"integrity": "sha512-/tCmbIETZwCd8uHWO+GvbRa7jxwHFHdfetHfiwoP0aN9UDf3prUJMtKn7iBFYipYhqY1bSTjur8hC/Dakt8eyw=="
|
||||
},
|
||||
"is-root": {
|
||||
"version": "2.1.0"
|
||||
},
|
||||
|
|
@ -27809,6 +27889,16 @@
|
|||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"requires": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"mdast-util-definitions": {
|
||||
"version": "5.1.1",
|
||||
"requires": {
|
||||
|
|
@ -29461,7 +29551,7 @@
|
|||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.9.1",
|
||||
"acorn": "*",
|
||||
"acorn": "^8.8.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"formik": "^2.2.9",
|
||||
|
|
@ -29470,6 +29560,7 @@
|
|||
"js-cookie": "^3.0.1",
|
||||
"nvm": "^0.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-gravatar": "*",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
|
|
@ -29495,6 +29586,15 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==",
|
||||
"requires": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3"
|
||||
},
|
||||
|
|
@ -29665,6 +29765,16 @@
|
|||
"react-fast-compare": {
|
||||
"version": "2.0.4"
|
||||
},
|
||||
"react-gravatar": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-gravatar/-/react-gravatar-2.6.3.tgz",
|
||||
"integrity": "sha512-yITonigS2LmG7Fw0gWfZfcVwy1mpiBHNVmoFyetitQjXu7JCYoE6jtub0GIfq+ydpnQSYyJT3kwpX6zj1wXR4w==",
|
||||
"requires": {
|
||||
"is-retina": "^1.0.3",
|
||||
"md5": "^2.1.0",
|
||||
"query-string": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"requires": {
|
||||
|
|
@ -31087,6 +31197,11 @@
|
|||
"statuses": {
|
||||
"version": "2.0.1"
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"requires": {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@
|
|||
"start": "npm start -w",
|
||||
"start:server": "npm start -w server",
|
||||
"start:client": "npm start -w client",
|
||||
"start:prod": "npm run start:server",
|
||||
"prestart:prod": "npm run build -w client",
|
||||
"start:prod": "npm run prod -w server",
|
||||
"prestart:prod": "npm run build",
|
||||
"dev": "npm start -workspaces",
|
||||
"build": "npm run build -w client"
|
||||
},
|
||||
"repository": {
|
||||
|
|
@ -25,4 +26,4 @@
|
|||
"client",
|
||||
"server"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ app.use(logger('dev'));
|
|||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/users', userRouter);
|
||||
app.use('/mail', mailRouter);
|
||||
|
|
|
|||
|
|
@ -34,10 +34,16 @@ const server = http.createServer(app);
|
|||
mongoose.connect(process.env.DB_CONN_STRING, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
dbName: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
mongoose.connection.once('open', () => console.log('MongoDB connected'));
|
||||
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
|
||||
mongoose.connection.once('open', () =>
|
||||
console.log('[INFO]: MongoDB connected')
|
||||
);
|
||||
mongoose.connection.on(
|
||||
'error',
|
||||
console.error.bind(console, '[ERROR]: MongoDB connection error - ')
|
||||
);
|
||||
|
||||
cron.schedule('*/30 * * * * *', refreshQuestions);
|
||||
cron.schedule('*/2 * * * *', refreshUsers);
|
||||
|
|
@ -84,6 +90,7 @@ function onError(error) {
|
|||
|
||||
function onListening() {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
const bind =
|
||||
typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const User = mongoose.Schema(
|
|||
{
|
||||
username: { type: String, required: true, unique: true },
|
||||
email: { type: String, required: true, unique: true },
|
||||
points: { type: Number, required: true, default: 1 },
|
||||
points: { type: Number, required: true, default: 0 },
|
||||
salt: { type: String, required: true },
|
||||
lastMailFetch: { type: Date, default: new Date(0) },
|
||||
lastAnswerFetch: { type: Date, default: new Date(0) },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const Token = require('server/db/models/Token');
|
||||
const User = require('server/db/models/User');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const config = require('server/config.json');
|
||||
|
||||
async function tokenAuth(req, res, next) {
|
||||
|
|
@ -9,10 +8,9 @@ async function tokenAuth(req, res, next) {
|
|||
const token = authHeader?.split(' ')[1];
|
||||
|
||||
// Verify token is included in request
|
||||
if (!token)
|
||||
return res.status(401).send(config.errorUnauthed);
|
||||
if (!token) return res.status(401).send(config.errorUnauthed);
|
||||
|
||||
const result = await Token.findOne({ token: token });
|
||||
const result = await Token.findOne({ token });
|
||||
|
||||
// Verify token expiration
|
||||
const date = new Date();
|
||||
|
|
|
|||
|
|
@ -45,13 +45,11 @@ paths:
|
|||
schema:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
minLength: 4
|
||||
maxLength: 16
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
|
|
@ -159,6 +157,27 @@ paths:
|
|||
$ref: '#/components/responses/ServerError'
|
||||
tags:
|
||||
- users
|
||||
/users/remember:
|
||||
get:
|
||||
summary: Retrieve authenticated data of the user
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'500':
|
||||
$ref: '#/components/responses/ServerError'
|
||||
tags:
|
||||
- users
|
||||
/users/logout:
|
||||
delete:
|
||||
summary: Login a user and receiver an authentication token
|
||||
|
|
@ -344,11 +363,11 @@ paths:
|
|||
$ref: '#/components/responses/ServerError'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}:
|
||||
/questions/{questionID}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
get:
|
||||
summary: Get a question by its question_id
|
||||
summary: Get a question by its questionID
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
|
|
@ -364,7 +383,7 @@ paths:
|
|||
tags:
|
||||
- questions
|
||||
patch:
|
||||
summary: Modify a question by its question_id
|
||||
summary: Modify a question by its questionID
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
|
|
@ -386,9 +405,9 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}/close:
|
||||
/questions/{questionID}/close:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
patch:
|
||||
summary: Toggle vote to close a question
|
||||
security:
|
||||
|
|
@ -404,9 +423,9 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}/protect:
|
||||
/questions/{questionID}/protect:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
patch:
|
||||
summary: Toggle vote to protect a question
|
||||
security:
|
||||
|
|
@ -422,9 +441,9 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}/reopen:
|
||||
/questions/{questionID}/reopen:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
patch:
|
||||
summary: Toggle vote to reopen a question
|
||||
security:
|
||||
|
|
@ -440,9 +459,9 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}/vote:
|
||||
/questions/{questionID}/vote:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
get:
|
||||
summary: Get a user's vote on a question
|
||||
security:
|
||||
|
|
@ -491,9 +510,9 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- questions
|
||||
/questions/{question_id}/comments:
|
||||
/questions/{questionID}/comments:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
get:
|
||||
summary: Get all comments of a question
|
||||
responses:
|
||||
|
|
@ -536,10 +555,10 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- comments
|
||||
/questions/{question_id}/comments/{comment_id}:
|
||||
/questions/{questionID}/comments/{commentID}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/comment_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/commentID'
|
||||
delete:
|
||||
summary: Delete a question comment
|
||||
security:
|
||||
|
|
@ -555,10 +574,10 @@ paths:
|
|||
$ref: '#/components/responses/ServerError'
|
||||
tags:
|
||||
- comments
|
||||
/questions/{question_id}/comments/{comment_id}/vote:
|
||||
/questions/{questionID}/comments/{commentID}/vote:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/comment_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/commentID'
|
||||
get:
|
||||
summary: Get a user's vote on a question comment
|
||||
security:
|
||||
|
|
@ -609,9 +628,9 @@ paths:
|
|||
tags:
|
||||
- comments
|
||||
- votes
|
||||
/questions/{question_id}/answers:
|
||||
/questions/{questionID}/answers:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
get:
|
||||
summary: Get all answers of a question
|
||||
responses:
|
||||
|
|
@ -654,12 +673,12 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- answers
|
||||
/questions/{question_id}/answers/{answer_id}:
|
||||
/questions/{questionID}/answers/{answerID}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
patch:
|
||||
summary: Modify an answer by its answer_id
|
||||
summary: Modify an answer by its answerID
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
|
|
@ -688,10 +707,10 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- answers
|
||||
/questions/{question_id}/answers/{answer_id}/accept:
|
||||
/questions/{questionID}/answers/{answerID}/accept:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
patch:
|
||||
summary: Accept an answer as correct
|
||||
security:
|
||||
|
|
@ -707,10 +726,10 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- answers
|
||||
/questions/{question_id}/answers/{answer_id}/vote:
|
||||
/questions/{questionID}/answers/{answerID}/vote:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
get:
|
||||
summary: Get a user's vote on an answer
|
||||
security:
|
||||
|
|
@ -761,10 +780,10 @@ paths:
|
|||
tags:
|
||||
- answers
|
||||
- votes
|
||||
/questions/{question_id}/answers/{answer_id}/comments:
|
||||
/questions/{questionID}/answers/{answerID}/comments:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
get:
|
||||
summary: Get all comments of a answer
|
||||
responses:
|
||||
|
|
@ -807,11 +826,11 @@ paths:
|
|||
$ref: '#/components/responses/BadRequest'
|
||||
tags:
|
||||
- comments
|
||||
/questions/{question_id}/answers/{answer_id}/comments/{comment_id}:
|
||||
/questions/{questionID}/answers/{answerID}/comments/{commentID}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/comment_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
- $ref: '#/components/parameters/commentID'
|
||||
delete:
|
||||
summary: Delete an answer comment
|
||||
security:
|
||||
|
|
@ -827,11 +846,11 @@ paths:
|
|||
$ref: '#/components/responses/ServerError'
|
||||
tags:
|
||||
- comments
|
||||
/questions/{question_id}/answers/{answer_id}/comments/{comment_id}/vote:
|
||||
/questions/{questionID}/answers/{answerID}/comments/{commentID}/vote:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/question_id'
|
||||
- $ref: '#/components/parameters/answer_id'
|
||||
- $ref: '#/components/parameters/comment_id'
|
||||
- $ref: '#/components/parameters/questionID'
|
||||
- $ref: '#/components/parameters/answerID'
|
||||
- $ref: '#/components/parameters/commentID'
|
||||
get:
|
||||
summary: Get a user's vote on an answer comment
|
||||
security:
|
||||
|
|
@ -903,7 +922,7 @@ components:
|
|||
id:
|
||||
type: string
|
||||
example: '62d8727d6c584b3b12eb23be'
|
||||
question_id:
|
||||
questionID:
|
||||
type: string
|
||||
example: '62d5c7ad1b8e0da8804bde22'
|
||||
creator:
|
||||
|
|
@ -926,7 +945,7 @@ components:
|
|||
id:
|
||||
type: string
|
||||
example: '62d87292afa7f2e7e81f1470'
|
||||
parent_id:
|
||||
parentID:
|
||||
type: string
|
||||
example: '62d5c7ad1b8e0da8804bde22'
|
||||
creator:
|
||||
|
|
@ -1021,6 +1040,8 @@ components:
|
|||
format: email
|
||||
points:
|
||||
type: integer
|
||||
level:
|
||||
type: integer
|
||||
Vote:
|
||||
type: string
|
||||
nullable: true
|
||||
|
|
@ -1077,8 +1098,8 @@ components:
|
|||
type: string
|
||||
example: 'That resource was not found. Please check your request and try again.'
|
||||
parameters:
|
||||
question_id:
|
||||
name: question_id
|
||||
questionID:
|
||||
name: questionID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
|
|
@ -1086,8 +1107,8 @@ components:
|
|||
example: '62d61d428849dda1c15a8b64'
|
||||
description: >-
|
||||
This should be the object id of the desired question.
|
||||
answer_id:
|
||||
name: answer_id
|
||||
answerID:
|
||||
name: answerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
|
|
@ -1095,8 +1116,8 @@ components:
|
|||
example: '62d61ffd5f20e06f3cb350c7'
|
||||
description: >-
|
||||
This should be the object id of the desired answer.
|
||||
comment_id:
|
||||
name: comment_id
|
||||
commentID:
|
||||
name: commentID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
"superagent-throttle": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "pm2 start ./bin/www",
|
||||
"dev": "nodemon ./bin/www"
|
||||
"start": "nodemon ./bin/www",
|
||||
"prod": "pm2 start ./bin/www"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^28.1.3",
|
||||
|
|
|
|||
|
|
@ -22,16 +22,12 @@ async function CreateAnswer(req, res) {
|
|||
if (question.status === 'closed')
|
||||
return res.status(403).send(config.errorForbidden);
|
||||
|
||||
if (
|
||||
question.status === 'protected' &&
|
||||
getUserLevel(user.points) < 5
|
||||
) {
|
||||
if (question.status === 'protected' && getUserLevel(user.points) < 5) {
|
||||
return res.status(403).send(config.errorForbidden);
|
||||
}
|
||||
|
||||
// Verify request includes required information
|
||||
if (!text)
|
||||
return res.status(400).send(config.errorIncomplete);
|
||||
if (!text) return res.status(400).send(config.errorIncomplete);
|
||||
|
||||
// Create question with BDPA server
|
||||
const { success, answer } = await createRequest(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ const User = require('server/db/models/User');
|
|||
const createRequest = require('server/utils/api');
|
||||
|
||||
async function CreateQuestion(req, res) {
|
||||
const { user: {username, user_id} } = req;
|
||||
const {
|
||||
user: { username, user_id },
|
||||
} = req;
|
||||
const { title, text } = req.body;
|
||||
|
||||
if (!title || !text)
|
||||
return res.status(400).send(config.errorIncomplete);
|
||||
if (!title || !text) return res.status(400).send(config.errorIncomplete);
|
||||
|
||||
const { success, question } = await createRequest('post', `/questions`, {
|
||||
creator: username,
|
||||
|
|
@ -26,7 +27,7 @@ async function CreateQuestion(req, res) {
|
|||
amount: 1,
|
||||
});
|
||||
|
||||
await User.findByIdAndUpdate(user_id, { $inc: { points: 1 } });
|
||||
await User.findByIdAndUpdate(user.id, { points: { $inc: 1 } });
|
||||
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,27 +8,32 @@ async function GetUser(req, res) {
|
|||
|
||||
const cachedUser = await User.findOne({ username });
|
||||
|
||||
if (cachedUser) return res.send({
|
||||
user: {
|
||||
username: cachedUser.username,
|
||||
email: cachedUser.email,
|
||||
points: cachedUser.points,
|
||||
level: getUserLevel(cachedUser.points),
|
||||
}
|
||||
});
|
||||
if (cachedUser)
|
||||
return res.send({
|
||||
user: {
|
||||
username: cachedUser.username,
|
||||
email: cachedUser.email,
|
||||
points: cachedUser.points,
|
||||
level: getUserLevel(cachedUser.points),
|
||||
},
|
||||
});
|
||||
|
||||
const { success, user } = await createRequest('get', `/users/${username}`);
|
||||
|
||||
if (!success) return res.status(500).send(config.errorGeneric);
|
||||
|
||||
const newUser = await User.create({ ...user, id: user.user_id });
|
||||
const newUser = await User.findByIdAndUpdate(user.user_id, user, {
|
||||
upsert: true,
|
||||
new: true,
|
||||
});
|
||||
|
||||
return res.send({
|
||||
user: {
|
||||
username: newUser.username,
|
||||
email: newUser.email,
|
||||
points: newUser.points,
|
||||
level: getUserLevel(newUser.points),
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ async function Register(req, res) {
|
|||
|
||||
const { salt, key } = await deriveKeyFromPassword(password);
|
||||
|
||||
const { success, error } = await createRequest('post', '/users', {
|
||||
const { success, error, user } = await createRequest('post', '/users', {
|
||||
username,
|
||||
email,
|
||||
salt,
|
||||
|
|
@ -29,7 +29,7 @@ async function Register(req, res) {
|
|||
return res.status(500).send(config.errorGeneric);
|
||||
}
|
||||
|
||||
await User.create({ username, email, salt });
|
||||
await User.create(user);
|
||||
|
||||
return res.sendStatus(201);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ async function refreshUsers(_job, done) {
|
|||
|
||||
module.exports = {
|
||||
refreshQuestions,
|
||||
refreshUsers
|
||||
}
|
||||
refreshUsers,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,38 +12,75 @@ async function getAllData(Model, url, id, dataName) {
|
|||
...item,
|
||||
id: item[id],
|
||||
}));
|
||||
|
||||
for (const item of data)
|
||||
await Model.findByIdAndUpdate(item[id], item, { upsert: true });
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getAllAnswers({ question_id, username }) {
|
||||
async function getAllAnswers(question_id, username) {
|
||||
if (question_id)
|
||||
return getAllData(Answer, `/questions/${question_id}/answers`, 'answer_id', 'answers')
|
||||
return getAllData(
|
||||
Answer,
|
||||
`/questions/${question_id}/answers`,
|
||||
'answer_id',
|
||||
'answers'
|
||||
);
|
||||
else if (username)
|
||||
return getAllData(Answer, `/users/${username}/answers`, 'answer_id', 'answers')
|
||||
return getAllData(
|
||||
Answer,
|
||||
`/users/${username}/answers`,
|
||||
'answer_id',
|
||||
'answers'
|
||||
);
|
||||
}
|
||||
|
||||
async function getAllComments({ answer_id, question_id }) {
|
||||
async function getAllComments(answer_id, question_id) {
|
||||
if (answer_id)
|
||||
return getAllData(Comment, `/questions/${question_id}/answers/${answer_id}/comments`, 'comment_id', 'comments')
|
||||
return getAllData(
|
||||
Comment,
|
||||
`/questions/${question_id}/answers/${answer_id}/comments`,
|
||||
'comment_id',
|
||||
'comments'
|
||||
);
|
||||
else
|
||||
return getAllData(Comment, `/questions/${question_id}/comments`, 'comment_id', 'comments')
|
||||
return getAllData(
|
||||
Comment,
|
||||
`/questions/${question_id}/comments`,
|
||||
'comment_id',
|
||||
'comments'
|
||||
);
|
||||
}
|
||||
|
||||
async function getAllMail({ username }) {
|
||||
return getAllData(Mail, `/mail/${username}`, 'mail_id', 'messages')
|
||||
async function getAllMail({ question_id }) {
|
||||
return getAllData(
|
||||
Mail,
|
||||
`/questions/${question_id}/comments`,
|
||||
'mail_id',
|
||||
'mail'
|
||||
);
|
||||
}
|
||||
|
||||
async function getAllQuestions({ username }) {
|
||||
async function getAllQuestions(username) {
|
||||
if (username)
|
||||
return getAllData(Question, `/users/${username}/questions`, 'question_id', 'questions')
|
||||
else
|
||||
return getAllData(Question, `/questions/search`, 'question_id', 'questions')
|
||||
return getAllData(
|
||||
Question,
|
||||
`/users/${username}/questions`,
|
||||
'question_id',
|
||||
'questions'
|
||||
);
|
||||
else {
|
||||
return getAllData(
|
||||
Question,
|
||||
`/questions/search`,
|
||||
'question_id',
|
||||
'questions'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllUsers() {
|
||||
return getAllData(User, `/users`, 'user_id', 'users')
|
||||
return getAllData(User, `/users`, 'user_id', 'users');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -51,5 +88,6 @@ module.exports = {
|
|||
getAllComments,
|
||||
getAllMail,
|
||||
getAllQuestions,
|
||||
getAllUsers
|
||||
getAllUsers,
|
||||
getAllMail,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@ const Answer = require('server/db/models/Answer');
|
|||
|
||||
async function getAnswer(question_id, answer_id) {
|
||||
let cachedAnswer;
|
||||
try { cachedAnswer = await Answer.findById(answer_id); }
|
||||
catch { return; }
|
||||
|
||||
try {
|
||||
cachedAnswer = await Answer.findById(answer_id);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve uncached answer and patch to cache
|
||||
if (!cachedAnswer) {
|
||||
|
|
@ -14,7 +18,14 @@ async function getAnswer(question_id, answer_id) {
|
|||
`/questions/${question_id}/answers/${answer_id}`
|
||||
);
|
||||
if (!success) return;
|
||||
return Answer.create({ ...answer, id: answer.answer_id, question_id });
|
||||
return Answer.findByIdAndUpdate(
|
||||
answer.answer_id,
|
||||
{ ...answer, id: answer.answer_id },
|
||||
{
|
||||
upsert: true,
|
||||
new: true,
|
||||
}
|
||||
);
|
||||
} else return cachedAnswer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ const Question = require('server/db/models/Question');
|
|||
|
||||
async function getQuestion(question_id) {
|
||||
let cachedQuestion;
|
||||
try { cachedQuestion = await Question.findById(question_id); }
|
||||
catch { return; }
|
||||
try {
|
||||
cachedQuestion = await Question.findById(question_id);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve uncached question and patch to cache
|
||||
if (!cachedQuestion) {
|
||||
|
|
@ -13,8 +16,17 @@ async function getQuestion(question_id) {
|
|||
'get',
|
||||
`/questions/${question_id}`
|
||||
);
|
||||
|
||||
if (!success) return;
|
||||
return Question.create({ ...question, id: question.question_id });
|
||||
|
||||
return Question.findByIdAndUpdate(
|
||||
question.question_id,
|
||||
{ ...question, id: question.question_id },
|
||||
{
|
||||
upsert: true,
|
||||
new: true,
|
||||
}
|
||||
);
|
||||
} else return cachedQuestion;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
const getAnswer = require('./getAnswer');
|
||||
const getComment = require('./getComment');
|
||||
const getQuestion = require('./getQuestion');
|
||||
const refreshQuestion = require('./refreshQuestion');
|
||||
|
||||
module.exports = {
|
||||
getAnswer,
|
||||
getComment,
|
||||
getQuestion,
|
||||
refreshQuestion
|
||||
};
|
||||
refreshQuestion,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ const Question = require('server/db/models/Question');
|
|||
const createRequest = require('server/utils/api');
|
||||
|
||||
async function refreshQuestion(question_id) {
|
||||
const { question, success } = await createRequest('get', `/questions/${question_id}`);
|
||||
const { question, success } = await createRequest(
|
||||
'get',
|
||||
`/questions/${question_id}`
|
||||
);
|
||||
if (!success) return false;
|
||||
|
||||
// Refresh cache
|
||||
|
|
@ -10,7 +13,10 @@ async function refreshQuestion(question_id) {
|
|||
...question,
|
||||
id: question.question_id,
|
||||
};
|
||||
return Question.findByIdAndUpdate(question.id, newQuestion, { upsert: true });
|
||||
|
||||
return Question.findByIdAndUpdate(question.id, newQuestion, {
|
||||
upsert: true,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = refreshQuestion;
|
||||
|
|
|
|||
Reference in a new issue