Preliminary API without caching (getVotes doesn't work)
This commit is contained in:
parent
5536666a59
commit
7c00e79271
115 changed files with 25704 additions and 19236 deletions
49
client/package.json
Normal file
49
client/package.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "qoverflow-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.8.7",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"formik": "^2.2.9",
|
||||
"gh-pages": "^4.0.0",
|
||||
"marked": "^4.0.18",
|
||||
"react": "^18.2.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"superagent": "^8.0.0",
|
||||
"superagent-throttle": "^1.0.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
|
@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
|||
import { getUser, getLevel } from '../services/userServices';
|
||||
import { useState } from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import removeMd from 'remove-markdown';
|
||||
|
||||
export default function ListQuestion({ question, summaryLimit }) {
|
||||
const [level, setLevel] = useState();
|
||||
|
|
@ -60,8 +61,9 @@ export default function ListQuestion({ question, summaryLimit }) {
|
|||
[{status}] {title}
|
||||
</Typography>
|
||||
<Typography variant='body1'>
|
||||
{text.split(' ').slice(0, summaryLimit).join(' ') +
|
||||
'...'}
|
||||
{removeMd(
|
||||
text.split(' ').slice(0, summaryLimit).join(' ')
|
||||
) + '...'}
|
||||
</Typography>
|
||||
|
||||
<Typography variant='body1' textAlign='right'>
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
Typography,
|
||||
AccordionDetails,
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
export default function MailUnit(sender, createdAt, subject, text) {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>Subject: {subject}</Typography>
|
||||
<Typography>
|
||||
From {sender} at {new Date(createdAt * 1000)}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography variant='body1'>{text}</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
Typography,
|
||||
AccordionDetails,
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import { marked } from 'marked';
|
||||
|
||||
export default function MailUnit(sender, createdAt, subject, text) {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>Subject: {subject}</Typography>
|
||||
<Typography>
|
||||
From {sender} at {new Date(createdAt * 1000)}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography variant='body1'>{marked.parse(text)}</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,58 +1,58 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import Logo from '../assets/bdpa-logo.svg';
|
||||
import { SearchBar } from '.';
|
||||
import Gravatar from 'react-gravatar';
|
||||
|
||||
export default function Navbar({ logout, userData }) {
|
||||
function ButtonGroup() {
|
||||
return userData.username ? (
|
||||
<>
|
||||
<Button color='inherit' component={Link} to='/mail'>
|
||||
Mail
|
||||
</Button>
|
||||
<Button color='inherit' component={Link} to='/dashboard'>
|
||||
Account
|
||||
</Button>
|
||||
<Button color='inherit' onClick={logout}>
|
||||
Logout
|
||||
</Button>
|
||||
<IconButton component={Link} to='/'>
|
||||
<Gravatar size={40} email={userData.email} />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button color='inherit' component={Link} to='/login'>
|
||||
Login
|
||||
</Button>
|
||||
<Button color='inherit' component={Link} to='/register'>
|
||||
Register
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBar position='static'>
|
||||
<Toolbar>
|
||||
<IconButton component={Link} to='/'>
|
||||
<img src={Logo} alt='bdpa logo' width='40' height='40' />
|
||||
</IconButton>
|
||||
<Typography variant='h6' component='div'>
|
||||
qOverflow
|
||||
</Typography>
|
||||
<SearchBar />
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<ButtonGroup />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import Logo from '../assets/bdpa-logo.svg';
|
||||
import { SearchBar } from '.';
|
||||
import Gravatar from 'react-gravatar';
|
||||
|
||||
export default function Navbar({ logout, userData }) {
|
||||
function ButtonGroup() {
|
||||
return userData.username ? (
|
||||
<>
|
||||
<Button color='inherit' component={Link} to='/mail'>
|
||||
Mail
|
||||
</Button>
|
||||
<Button color='inherit' component={Link} to='/dashboard'>
|
||||
Account
|
||||
</Button>
|
||||
<Button color='inherit' onClick={logout}>
|
||||
Logout
|
||||
</Button>
|
||||
<IconButton component={Link} to='/'>
|
||||
<Gravatar size={40} email={userData.email} />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button color='inherit' component={Link} to='/login'>
|
||||
Login
|
||||
</Button>
|
||||
<Button color='inherit' component={Link} to='/register'>
|
||||
Register
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppBar position='static'>
|
||||
<Toolbar>
|
||||
<IconButton component={Link} to='/'>
|
||||
<img src={Logo} alt='bdpa logo' width='40' height='40' />
|
||||
</IconButton>
|
||||
<Typography variant='h6' component='div'>
|
||||
qOverflow
|
||||
</Typography>
|
||||
<SearchBar />
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<ButtonGroup />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,33 +1,33 @@
|
|||
import { useState } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { Box, CssBaseline } from '@mui/material';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
|
||||
import { Navbar } from '../controllers';
|
||||
|
||||
export default function Layout() {
|
||||
const [mode, setMode] = useState('light');
|
||||
const theme = createTheme({
|
||||
palette: { mode },
|
||||
breakpoints: {
|
||||
values: {
|
||||
xs: 0,
|
||||
sm: 768,
|
||||
md: 1024,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<main>
|
||||
<Navbar />
|
||||
<Box sx={{ height: '93vh' }}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
import { useState } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { Box, CssBaseline } from '@mui/material';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
|
||||
import { Navbar } from '../controllers';
|
||||
|
||||
export default function Layout() {
|
||||
const [mode, setMode] = useState('light');
|
||||
const theme = createTheme({
|
||||
palette: { mode },
|
||||
breakpoints: {
|
||||
values: {
|
||||
xs: 0,
|
||||
sm: 768,
|
||||
md: 1024,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<main>
|
||||
<Navbar />
|
||||
<Box sx={{ height: '93vh' }}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { QuestionProvider, UserProvider } from '.';
|
||||
|
||||
export default function ContextProvider({ children }) {
|
||||
return (
|
||||
<UserProvider>
|
||||
<QuestionProvider>
|
||||
{children}
|
||||
</QuestionProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
}
|
||||
import { QuestionProvider, UserProvider } from '.';
|
||||
|
||||
export default function ContextProvider({ children }) {
|
||||
return (
|
||||
<UserProvider>
|
||||
<QuestionProvider>
|
||||
{children}
|
||||
</QuestionProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
import { Form } from '.';
|
||||
import { recoverFields } from '../services/fields';
|
||||
import { recoverSchema } from '../services/schemas';
|
||||
|
||||
export default function ForgotPasswordFormController() {
|
||||
const recover = ({ username }) => {
|
||||
console.log(
|
||||
`Visit http://localhost:3000/recover/${username} to reset your password`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={recoverFields}
|
||||
onSubmit={recover}
|
||||
validationSchema={recoverSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { Form } from '.';
|
||||
import { recoverFields } from '../services/fields';
|
||||
import { recoverSchema } from '../services/schemas';
|
||||
|
||||
export default function ForgotPasswordFormController() {
|
||||
const recover = ({ username }) => {
|
||||
console.log(
|
||||
`Visit http://localhost:3000/recover/${username} to reset your password`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={recoverFields}
|
||||
onSubmit={recover}
|
||||
validationSchema={recoverSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,28 +1,28 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useUser } from '../contexts';
|
||||
import { getMail } from '../services/mailServices';
|
||||
import { MailUnit } from '../components';
|
||||
|
||||
export default function InboxController() {
|
||||
const { userData } = useUser();
|
||||
const [mail, setMail] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMail = async () => {
|
||||
const { success, messages } = await getMail(userData?.username);
|
||||
if (success) {
|
||||
setMail(() => messages);
|
||||
}
|
||||
}
|
||||
fetchMail();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{mail.map((message) => (
|
||||
<MailUnit {...message} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useUser } from '../contexts';
|
||||
import { getMail } from '../services/mailServices';
|
||||
import { MailUnit } from '../components';
|
||||
|
||||
export default function InboxController() {
|
||||
const { userData } = useUser();
|
||||
const [mail, setMail] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMail = async () => {
|
||||
const { success, messages } = await getMail(userData?.username);
|
||||
if (success) {
|
||||
setMail(() => messages);
|
||||
}
|
||||
}
|
||||
fetchMail();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{mail.map((message) => (
|
||||
<MailUnit {...message} />
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Form } from '.';
|
||||
import { useUser } from '../contexts';
|
||||
import { resetSchema } from '../services/schemas';
|
||||
import { resetFields } from '../services/fields';
|
||||
import { updateUser } from '../services/userServices';
|
||||
import { deriveKeyFromPassword } from '../services/auth';
|
||||
|
||||
export default function ResetFormController() {
|
||||
const navigate = useNavigate();
|
||||
const { username } = useParams();
|
||||
const { setUserData } = useUser();
|
||||
|
||||
const changePassword = async ({ password }) => {
|
||||
const body = await deriveKeyFromPassword(password);
|
||||
|
||||
const { user, success } = await updateUser(username, body);
|
||||
|
||||
if (success) {
|
||||
setUserData(() => user);
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={resetFields}
|
||||
onSubmit={changePassword}
|
||||
validationSchema={resetSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Form } from '.';
|
||||
import { useUser } from '../contexts';
|
||||
import { resetSchema } from '../services/schemas';
|
||||
import { resetFields } from '../services/fields';
|
||||
import { updateUser } from '../services/userServices';
|
||||
import { deriveKeyFromPassword } from '../services/auth';
|
||||
|
||||
export default function ResetFormController() {
|
||||
const navigate = useNavigate();
|
||||
const { username } = useParams();
|
||||
const { setUserData } = useUser();
|
||||
|
||||
const changePassword = async ({ password }) => {
|
||||
const body = await deriveKeyFromPassword(password);
|
||||
|
||||
const { user, success } = await updateUser(username, body);
|
||||
|
||||
if (success) {
|
||||
setUserData(() => user);
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={resetFields}
|
||||
onSubmit={changePassword}
|
||||
validationSchema={resetSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
import { Form } from '.';
|
||||
import { useUser } from '../contexts';
|
||||
import { mailSchema } from '../services/schemas';
|
||||
import { composeMailFields } from '../services/fields';
|
||||
import { postMail } from '../services/mailServices';
|
||||
|
||||
export default function SendMailController() {
|
||||
const { userData } = useUser();
|
||||
|
||||
const sendMail = ({ reciever, subject, text }) =>
|
||||
postMail(userData.username, reciever, subject, text);
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={composeMailFields}
|
||||
onSubmit={sendMail}
|
||||
validationSchema={mailSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { Form } from '.';
|
||||
import { useUser } from '../contexts';
|
||||
import { mailSchema } from '../services/schemas';
|
||||
import { composeMailFields } from '../services/fields';
|
||||
import { postMail } from '../services/mailServices';
|
||||
|
||||
export default function SendMailController() {
|
||||
const { userData } = useUser();
|
||||
|
||||
const sendMail = ({ reciever, subject, text }) =>
|
||||
postMail(userData.username, reciever, subject, text);
|
||||
|
||||
return (
|
||||
<Form
|
||||
fields={composeMailFields}
|
||||
onSubmit={sendMail}
|
||||
validationSchema={mailSchema}
|
||||
/>
|
||||
);
|
||||
}
|
||||
5681
package-lock.json
generated
5681
package-lock.json
generated
File diff suppressed because it is too large
Load diff
59
package.json
59
package.json
|
|
@ -1,48 +1,23 @@
|
|||
{
|
||||
"name": "qoverflow",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.8.7",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"formik": "^2.2.9",
|
||||
"gh-pages": "^4.0.0",
|
||||
"marked": "^4.0.18",
|
||||
"react": "^18.2.0",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"superagent": "^8.0.0",
|
||||
"superagent-throttle": "^1.0.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d build"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/spicecat/qOverflow.git"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/spicecat/qOverflow/issues"
|
||||
},
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"homepage": "https://github.com/spicecat/qOverflow#readme"
|
||||
}
|
||||
|
|
|
|||
36
server/app.js
Normal file
36
server/app.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var userRouter = require('./routes/user');
|
||||
var mailRouter = require('./routes/mail');
|
||||
var questionRouter = require('./routes/question');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
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('/user', userRouter);
|
||||
app.use('/mail', mailRouter);
|
||||
app.use('/question', questionRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
return res
|
||||
.status(404)
|
||||
.send({ success: false, error: 'This URL does not exist.' });
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
98
server/bin/www
Normal file
98
server/bin/www
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('server:server');
|
||||
var http = require('http');
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
mongoose.connect(process.env.DB_CONN_STRING, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
});
|
||||
|
||||
mongoose.connection.on(
|
||||
'error',
|
||||
console.error.bind(console, 'MongoDB connection error:')
|
||||
);
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
15
server/config.json
Normal file
15
server/config.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"askQuestion": 1,
|
||||
"answerQuestion": 2,
|
||||
"upvoteQuestion": 5,
|
||||
"downvoteQuestion": 1,
|
||||
"upvoteAnswer": 10,
|
||||
"downvoteAnswer": 5,
|
||||
"userDownvotes": 1,
|
||||
"acceptAnswer": 15,
|
||||
|
||||
"mailExpires": 10000,
|
||||
"userExpires": 100000,
|
||||
|
||||
"errorGeneric": "Oops! Something went wrong! Please try again."
|
||||
}
|
||||
16
server/db/models/Answer.js
Normal file
16
server/db/models/Answer.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const Answer = mongoose.Schema(
|
||||
{
|
||||
questionID: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' },
|
||||
creator: { type: String, required: true, ref: 'User' },
|
||||
createdAt: { type: Date, required: true },
|
||||
text: { type: String, required: true },
|
||||
upvotes: { type: Number, required: true, default: 0 },
|
||||
downvotes: { type: Number, required: true, default: 0 },
|
||||
accepted: { type: Boolean, require: true, default: false },
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Answer', Answer);
|
||||
20
server/db/models/Comment.js
Normal file
20
server/db/models/Comment.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const Comment = mongoose.Schema(
|
||||
{
|
||||
parentID: { type: mongoose.Schema.Types.ObjectId, ref: 'docModel' },
|
||||
creator: { type: String, required: true },
|
||||
createdAt: { type: Date, required: true },
|
||||
text: { type: String, required: true },
|
||||
upvotes: { type: Number, required: true, default: 0 },
|
||||
downvotes: { type: Number, required: true, default: 0 },
|
||||
docModel: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['Question', 'Answer'],
|
||||
},
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Comment', Comment);
|
||||
14
server/db/models/Mail.js
Normal file
14
server/db/models/Mail.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const Mail = mongoose.Schema(
|
||||
{
|
||||
sender: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' },
|
||||
reciever: { type: String, required: true },
|
||||
subject: { type: String, required: true },
|
||||
text: { type: String, required: true },
|
||||
createdAt: { type: Date, required: true },
|
||||
},
|
||||
{ timestamps: false }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Mail', Mail);
|
||||
19
server/db/models/Question.js
Normal file
19
server/db/models/Question.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const Question = mongoose.Schema(
|
||||
{
|
||||
title: { type: String, required: true },
|
||||
text: { type: String, required: true },
|
||||
creator: { type: String, required: true },
|
||||
status: { type: String, required: true },
|
||||
createdAt: { type: Date, required: true },
|
||||
views: { type: Number, required: true },
|
||||
answers: { type: Number, required: true },
|
||||
comments: { type: Number, required: true },
|
||||
upvotes: { type: Number, required: true },
|
||||
downvotes: { type: Number, required: true },
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Question', Question);
|
||||
7
server/db/models/ResetRequest.js
Normal file
7
server/db/models/ResetRequest.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const ResetRequestSchema = mongoose.Schema({
|
||||
user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('ResetRequest', ResetRequestSchema);
|
||||
15
server/db/models/Token.js
Normal file
15
server/db/models/Token.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const TokenSchema = mongoose.Schema(
|
||||
{
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: 'User',
|
||||
},
|
||||
expires: { type: Boolean, required: true, default: true },
|
||||
},
|
||||
{ timestamps: { createdAt: true, updatedAt: false } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Tokens', TokenSchema);
|
||||
12
server/db/models/User.js
Normal file
12
server/db/models/User.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const User = mongoose.Schema(
|
||||
{
|
||||
username: { type: String, required: true, unique: true },
|
||||
email: { type: String, required: true, unique: true },
|
||||
points: { type: String, required: true, default: 0 },
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('User', User);
|
||||
25
server/db/models/Vote.js
Normal file
25
server/db/models/Vote.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const Comment = mongoose.Schema(
|
||||
{
|
||||
parentID: { type: mongoose.Schema.Types.ObjectId, ref: 'docModel' },
|
||||
creator: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: 'User',
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['upvoted', 'downvoted'],
|
||||
},
|
||||
docModel: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['Question', 'Answer', 'Comment'],
|
||||
},
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
||||
module.exports = mongoose.model('Comment', Comment);
|
||||
56
server/middleware/basicAuth.js
Normal file
56
server/middleware/basicAuth.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
const deriveKeyFromPassword = require('../utils/auth');
|
||||
const createRequest = require('../utils/api');
|
||||
const config = require('../config.json');
|
||||
|
||||
const User = require('../db/models/User');
|
||||
|
||||
async function basicAuth(req, res, next) {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
return res.status(400).send('Your request is malformed.');
|
||||
}
|
||||
|
||||
const cacheUser = await User.findOne({ username });
|
||||
if (cacheUser) {
|
||||
const { key } = await deriveKeyFromPassword(password, cacheUser.salt);
|
||||
const checkCacheAuth = await createRequest(
|
||||
'post',
|
||||
`/users/${username}/auth`,
|
||||
{
|
||||
key,
|
||||
}
|
||||
);
|
||||
|
||||
if (checkCacheAuth.success) {
|
||||
req.user = cacheUser.username;
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
const { user, success } = await createRequest('get', `/users/${username}`);
|
||||
await User.create(user);
|
||||
|
||||
if (!success) {
|
||||
return res.status(500).send({
|
||||
success: false,
|
||||
error: config.errorGeneric,
|
||||
});
|
||||
}
|
||||
|
||||
const { key } = await deriveKeyFromPassword(password, user.salt);
|
||||
const checkAuth = await createRequest('post', `/users/${username}/auth`, {
|
||||
key,
|
||||
});
|
||||
|
||||
if (checkAuth.success) {
|
||||
req.user = user;
|
||||
return next();
|
||||
} else {
|
||||
return res.status(401).send({
|
||||
success: false,
|
||||
error: 'Login failed. Please check your username and password and try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = basicAuth;
|
||||
29
server/middleware/tokenAuth.js
Normal file
29
server/middleware/tokenAuth.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const Token = require('../db/models/Token');
|
||||
|
||||
async function tokenAuth(req, res, next) {
|
||||
const token = req.query.token || req.body.token;
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).send('Your request is missing session token.');
|
||||
}
|
||||
|
||||
const result = await Token.findByIdAndDelete(token);
|
||||
|
||||
const date = new Date();
|
||||
|
||||
if (!result) {
|
||||
return res.status(401).send('The user is unauthenticated.');
|
||||
} else if (
|
||||
result.expires &&
|
||||
date.setDate(date.getDate + 1) > result.createdAt
|
||||
) {
|
||||
await Token.findByIdAndDelete(token);
|
||||
return res.status(401).send('Session token has expired.');
|
||||
}
|
||||
|
||||
req.user = result.username;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = tokenAuth;
|
||||
8
server/openapi.json
Normal file
8
server/openapi.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "qOverflow API BDPA-STL",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
26
server/package.json
Normal file
26
server/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "server",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"bull": "^4.8.4",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"jade": "~1.11.0",
|
||||
"mongoose": "^6.4.4",
|
||||
"morgan": "~1.9.1",
|
||||
"superagent": "^8.0.0",
|
||||
"superagent-throttle": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^28.1.2",
|
||||
"nodemon": "^2.0.19",
|
||||
"supertest": "^6.2.4"
|
||||
}
|
||||
}
|
||||
12
server/routes/mail.js
Normal file
12
server/routes/mail.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
const tokenAuth = require('../middleware/tokenAuth');
|
||||
|
||||
const GetMail = require('./mail/Get');
|
||||
const SendMail = require('./mail/Send');
|
||||
|
||||
router.post('/', tokenAuth, SendMail);
|
||||
router.get('/', tokenAuth, GetMail);
|
||||
|
||||
module.exports = router;
|
||||
33
server/routes/mail/Get.js
Normal file
33
server/routes/mail/Get.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
const config = require('../../config.json');
|
||||
const { checkOutdated } = require('../../utils/checkOutdated');
|
||||
const getAllMail = require('../../utils/getAllMail');
|
||||
const Mail = require('../../db/models/Mail');
|
||||
|
||||
async function Get(req, res, next) {
|
||||
const { username } = req.params;
|
||||
|
||||
const messages = Mail.find({ reciever: username }).sort({
|
||||
createdAt: 'descending',
|
||||
});
|
||||
|
||||
if (mail.length === 0 && checkOutdated(mail[0], config.mailExpires)) {
|
||||
try {
|
||||
await getAllMail();
|
||||
} catch {
|
||||
return res
|
||||
.status(500)
|
||||
.send({ success: false, error: config.errorGeneric });
|
||||
}
|
||||
} else {
|
||||
return res.send({ success: true, messages });
|
||||
}
|
||||
|
||||
const newMessages = Mail.find({ reciever, username }).sort({
|
||||
createdAt: 'descending',
|
||||
});
|
||||
|
||||
return res.send({ success: true, messages: newMessages });
|
||||
}
|
||||
|
||||
module.exports = Get;
|
||||
27
server/routes/mail/Send.js
Normal file
27
server/routes/mail/Send.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const Mail = require('../../db/models/Mail');
|
||||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function Send(req, res, next) {
|
||||
const user = req.user;
|
||||
const { reciever, subject, text } = req.body;
|
||||
|
||||
if (!reciever | !subject | !text) {
|
||||
return res.status(400).send('Your request is missing information.');
|
||||
}
|
||||
|
||||
const { success, message } = await createRequest(
|
||||
'post',
|
||||
`/mail/${username}`,
|
||||
{
|
||||
sender: user,
|
||||
}
|
||||
);
|
||||
|
||||
Mail.create({ ...message, id: message.mail_id });
|
||||
|
||||
return success
|
||||
? res.send({ success: true })
|
||||
: res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = Send;
|
||||
40
server/routes/question.js
Normal file
40
server/routes/question.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
const tokenAuth = require('../middleware/tokenAuth');
|
||||
|
||||
const CreateAnswer = require('./question/CreateAnswer');
|
||||
const CreateAnswerComment = require('./question/CreateAnswerComment');
|
||||
const CreateComment = require('./question/CreateComment');
|
||||
const CreateQuestion = require('./question/CreateQuestion');
|
||||
const DeleteAnswerComment = require('./question/DeleteAnswerComment');
|
||||
const DeleteComment = require('./question/DeleteComment');
|
||||
const EditAnswer = require('./question/EditAnswer');
|
||||
const EditQuestion = require('./question/EditQuestion');
|
||||
const GetQuestion = require('./question/GetQuestion');
|
||||
const Search = require('./question/Search');
|
||||
const UpdateQuestionVote = require('./question/UpdateQuestionVote');
|
||||
const UpdateQuestionCommentVote = require('./question/UpdateQuestionCommentVote');
|
||||
const UpdateQuestionAnswerVote = require('./question/UpdateQuestionAnswerVote');
|
||||
const UpdateQuestionAnswerCommentVote = require('./question/UpdateQuestionAnswerCommentVote');
|
||||
|
||||
router.get('/search', Search);
|
||||
router.get('/:questionID', GetQuestion);
|
||||
router.patch('/:questionID', tokenAuth, EditQuestion);
|
||||
router.post('/', tokenAuth, CreateQuestion);
|
||||
router.post('/:questionID/comment', tokenAuth, CreateComment);
|
||||
router.post('/:questionID/answer', tokenAuth, CreateAnswer);
|
||||
router.post('/:questionID/:answerID/comment', tokenAuth, CreateAnswerComment);
|
||||
router.patch('/:questionID/:answerID', tokenAuth, EditAnswer);
|
||||
router.patch('/:questionID/vote');
|
||||
router.patch('/:questionID/comments/:commentID/vote');
|
||||
router.patch('/:questionID/answers/:answerID/vote');
|
||||
router.patch('/:questionID/answers/:answerID/comments/:commentID/vote');
|
||||
router.delete(
|
||||
'/:questionID/:answerID/:commentID',
|
||||
tokenAuth,
|
||||
DeleteAnswerComment
|
||||
);
|
||||
router.delete('/:questionID/:commentID', tokenAuth, DeleteComment);
|
||||
|
||||
module.exports = router;
|
||||
18
server/routes/question/CreateAnswer.js
Normal file
18
server/routes/question/CreateAnswer.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
async function CreateAnswer(req, res, next) {
|
||||
const user = req.user;
|
||||
const { title, text } = req.body;
|
||||
|
||||
if (!title || !text) {
|
||||
return res.status(400).send('Your request is missing information.');
|
||||
}
|
||||
|
||||
const { success } = await createRequest('post', `/questions`, {
|
||||
creator: user,
|
||||
title,
|
||||
text,
|
||||
});
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = CreateAnswer;
|
||||
22
server/routes/question/CreateAnswerComment.js
Normal file
22
server/routes/question/CreateAnswerComment.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
async function CreateAnswerComment(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID } = req.params;
|
||||
const { text } = req.body;
|
||||
|
||||
if (!text) {
|
||||
return res.status(400).send('Your request is missing information.');
|
||||
}
|
||||
|
||||
const { success } = await createRequest(
|
||||
'post',
|
||||
`/questions/${questionID}/answers/${answerID}/comments`,
|
||||
{
|
||||
creator: user,
|
||||
text,
|
||||
}
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = CreateAnswerComment;
|
||||
22
server/routes/question/CreateComment.js
Normal file
22
server/routes/question/CreateComment.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
async function CreateComment(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID } = req.params;
|
||||
const { text } = req.body;
|
||||
|
||||
if (!text) {
|
||||
return res.status(400).send('Your request is missing information.');
|
||||
}
|
||||
|
||||
const { success } = await createRequest(
|
||||
'post',
|
||||
`/questions/${questionID}/comments`,
|
||||
{
|
||||
creator: user,
|
||||
text,
|
||||
}
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = CreateComment;
|
||||
18
server/routes/question/CreateQuestion.js
Normal file
18
server/routes/question/CreateQuestion.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
async function CreateQuestion(req, res, next) {
|
||||
const user = req.user;
|
||||
const { title, text } = req.body;
|
||||
|
||||
if (!title || !text) {
|
||||
return res.status(400).send('Your request is missing information.');
|
||||
}
|
||||
|
||||
const { success } = await createRequest('post', `/questions`, {
|
||||
creator: user,
|
||||
title,
|
||||
text,
|
||||
});
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = CreateQuestion;
|
||||
13
server/routes/question/DeleteAnswerComment.js
Normal file
13
server/routes/question/DeleteAnswerComment.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
async function DeleteAnswerComment(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID, commentID } = req.params;
|
||||
|
||||
const { success } = await createRequest(
|
||||
'delete',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}`
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = DeleteAnswerComment;
|
||||
13
server/routes/question/DeleteComment.js
Normal file
13
server/routes/question/DeleteComment.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
async function DeleteComment(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, commentID } = req.params;
|
||||
|
||||
const { success } = await createRequest(
|
||||
'delete',
|
||||
`/questions/${questionID}/comments/${commentID}`
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = DeleteComment;
|
||||
15
server/routes/question/EditAnswer.js
Normal file
15
server/routes/question/EditAnswer.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
async function EditAnswer(req, res, next) {
|
||||
const user = req.user;
|
||||
const { token, ...body } = req.body;
|
||||
const { questionID, answerID } = req.params;
|
||||
|
||||
const { success } = await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}`,
|
||||
body
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = EditAnswer;
|
||||
15
server/routes/question/EditQuestion.js
Normal file
15
server/routes/question/EditQuestion.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
async function EditQuestion(req, res, next) {
|
||||
const user = req.user;
|
||||
const { token, ...body } = req.body;
|
||||
const { questionID } = req.params;
|
||||
|
||||
const { success } = await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}`,
|
||||
body
|
||||
);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = EditQuestion;
|
||||
16
server/routes/question/GetQuestion.js
Normal file
16
server/routes/question/GetQuestion.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function GetQuestion(req, res, next) {
|
||||
const { questionID } = req.params;
|
||||
|
||||
const { success, question } = createRequest(
|
||||
'get',
|
||||
`/questions/${questionID}`
|
||||
);
|
||||
|
||||
return success
|
||||
? res.send(question)
|
||||
: res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = GetQuestion;
|
||||
25
server/routes/question/GetVote.js
Normal file
25
server/routes/question/GetVote.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
async function GetVote(req, res, next) {
|
||||
const user = req.user;
|
||||
const { token, questionID, answerID, commentID, ...body } = req.query;
|
||||
|
||||
if (!questionID) {
|
||||
return res.status(400).send('Your request is missing something.');
|
||||
}
|
||||
|
||||
var requestURL = '';
|
||||
if (answerID && commentID) {
|
||||
requestURL = `/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`;
|
||||
} else if (answerID) {
|
||||
requestURL = `/questions/${questionID}/answers/${answerID}/vote/${user}`;
|
||||
} else if (commentID) {
|
||||
requestURL = `/questions/${questionID}/comments/${commentID}/vote/${user}`;
|
||||
} else {
|
||||
requestURL = `/questions/${questionID}/vote/${user}`;
|
||||
}
|
||||
|
||||
const { success } = await createRequest('get', requestURL, body);
|
||||
|
||||
return success ? res.send() : res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = GetVote;
|
||||
24
server/routes/question/Search.js
Normal file
24
server/routes/question/Search.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
const Question = require('../../db/models/Question');
|
||||
|
||||
async function Seach(req, res, next) {
|
||||
const { success, questions } = await createRequest(
|
||||
'get',
|
||||
`/questions/search`,
|
||||
req.params
|
||||
);
|
||||
|
||||
const questionSet = questions.map((question) => ({
|
||||
id: question.question_id,
|
||||
...question,
|
||||
}));
|
||||
|
||||
await Question.create(questionSet);
|
||||
|
||||
return success
|
||||
? res.send(questionSet)
|
||||
: res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
module.exports = Seach;
|
||||
64
server/routes/question/UpdateQuestionAnswerCommentVote.js
Normal file
64
server/routes/question/UpdateQuestionAnswerCommentVote.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function UpdateQuestionAnswerCommentVote(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID, commentID } = req.params;
|
||||
const { operation } = req.body;
|
||||
|
||||
if (!operation) {
|
||||
return res.status(400).send('Your request is missing something.');
|
||||
}
|
||||
|
||||
const currentVote = await createRequest(
|
||||
'get',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`
|
||||
);
|
||||
|
||||
if (!currentVote.success) {
|
||||
return res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
if (operation === 'upvote') {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
res.status(400).send('You have already upvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'downvotes' }
|
||||
);
|
||||
await createRequest('patch', `/users/${user}/points`, {
|
||||
operation: 'increment',
|
||||
amount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
res.status(400).send('You have already downvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateQuestionAnswerCommentVote;
|
||||
60
server/routes/question/UpdateQuestionAnswerVote.js
Normal file
60
server/routes/question/UpdateQuestionAnswerVote.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function UpdateQuestionAnswerCommentVote(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID, commentID } = req.params;
|
||||
const { operation } = req.body;
|
||||
|
||||
if (!operation) {
|
||||
return res.status(400).send('Your request is missing something.');
|
||||
}
|
||||
|
||||
const currentVote = await createRequest(
|
||||
'get',
|
||||
`/questions/${questionID}/answers/${answerID}/vote/${user}`
|
||||
);
|
||||
|
||||
if (!currentVote.success) {
|
||||
return res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
if (operation === 'upvote') {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
res.status(400).send('You have already upvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
res.status(400).send('You have already downvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/answers/${answerID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateQuestionAnswerCommentVote;
|
||||
60
server/routes/question/UpdateQuestionCommentVote.js
Normal file
60
server/routes/question/UpdateQuestionCommentVote.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function UpdateQuestionAnswerCommentVote(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID, commentID } = req.params;
|
||||
const { operation } = req.body;
|
||||
|
||||
if (!operation) {
|
||||
return res.status(400).send('Your request is missing something.');
|
||||
}
|
||||
|
||||
const currentVote = await createRequest(
|
||||
'get',
|
||||
`/questions/${questionID}/comments/${commentID}/vote/${user}`
|
||||
);
|
||||
|
||||
if (!currentVote.success) {
|
||||
return res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
if (operation === 'upvote') {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
res.status(400).send('You have already upvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
res.status(400).send('You have already downvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/comments/${commentID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateQuestionAnswerCommentVote;
|
||||
60
server/routes/question/UpdateQuestionVote.js
Normal file
60
server/routes/question/UpdateQuestionVote.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
const createRequest = require('../../utils/api');
|
||||
|
||||
async function UpdateQuestionAnswerCommentVote(req, res, next) {
|
||||
const user = req.user;
|
||||
const { questionID, answerID, commentID } = req.params;
|
||||
const { operation } = req.body;
|
||||
|
||||
if (!operation) {
|
||||
return res.status(400).send('Your request is missing something.');
|
||||
}
|
||||
|
||||
const currentVote = await createRequest(
|
||||
'get',
|
||||
`/questions/${questionID}/vote/${user}`
|
||||
);
|
||||
|
||||
if (!currentVote.success) {
|
||||
return res.status(500).send('Something went wrong.');
|
||||
}
|
||||
|
||||
if (operation === 'upvote') {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
res.status(400).send('You have already upvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (currentVote.vote === 'downvoted') {
|
||||
res.status(400).send('You have already downvoted this question.');
|
||||
} else {
|
||||
if (currentVote.vote === 'upvoted') {
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/vote/${user}`,
|
||||
{ operation: 'decrement', target: 'upvotes' }
|
||||
);
|
||||
}
|
||||
|
||||
await createRequest(
|
||||
'patch',
|
||||
`/questions/${questionID}/vote/${user}`,
|
||||
{ operation: 'increment', target: 'downvotes' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UpdateQuestionAnswerCommentVote;
|
||||
27
server/routes/user.js
Normal file
27
server/routes/user.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
const basicAuth = require('../middleware/basicAuth');
|
||||
const tokenAuth = require('../middleware/tokenAuth');
|
||||
|
||||
const Questions = require('./user/Questions');
|
||||
const Answers = require('./user/Answers');
|
||||
const GetUser = require('./user/GetUser');
|
||||
const Login = require('./user/Login');
|
||||
const Logout = require('./user/Logout');
|
||||
const Register = require('./user/Register');
|
||||
const RequestReset = require('./user/RequestReset');
|
||||
const ResetPassword = require('./user/ResetPassword');
|
||||
const Update = require('./user/Update');
|
||||
|
||||
router.get('/', GetUser);
|
||||
router.get('/questions', tokenAuth, Questions);
|
||||
router.get('/answers', tokenAuth, Answers);
|
||||
router.get('/login', basicAuth, Login);
|
||||
router.get('/logout', tokenAuth, Logout);
|
||||
router.post('/register', Register);
|
||||
router.get('/reset', RequestReset);
|
||||
router.post('/reset/:id', ResetPassword);
|
||||
router.patch('/update', tokenAuth, Update);
|
||||
|
||||
module.exports = router;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue