Full commit for review
This commit is contained in:
49
api/.eslintrc.json
Normal file
49
api/.eslintrc.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-location": ["error", "property"],
|
||||
"handle-callback-err": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"keyword-spacing": "error",
|
||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
||||
"max-statements-per-line": ["error", { "max": 2 }],
|
||||
"no-console": "off",
|
||||
"no-empty-function": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
|
||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"yoda": "error"
|
||||
}
|
||||
}
|
||||
4
api/generate-secret.sh
Normal file
4
api/generate-secret.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
secret=$(openssl rand -base64 32)
|
||||
echo "Generated secret key $secret"
|
||||
42
api/index.js
Normal file
42
api/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
const logger = require('morgan');
|
||||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
|
||||
const { log } = require('./modules/log');
|
||||
const { speedLimiter, checkSystemLoad } = require('./modules/requestHandler');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(speedLimiter);
|
||||
app.use(checkSystemLoad);
|
||||
app.use(logger('dev'));
|
||||
app.use(logger('combined', { stream: fs.createWriteStream(path.join(__dirname, 'logs/access.log'), { flags: 'a' }) }));
|
||||
app.use(cors({
|
||||
origin: '*',
|
||||
}));
|
||||
|
||||
|
||||
// routes
|
||||
app.use('/api/test', require('./routes/test').router);
|
||||
app.use('/api/users', require('./routes/users').router);
|
||||
app.use('/api/pilots', require('./routes/pilots').router);
|
||||
app.use('/api/airplanes', require('./routes/airplanes').router);
|
||||
app.use('/api/airlines', require('./routes/airlines').router);
|
||||
app.use('/api/airports', require('./routes/airports').router);
|
||||
app.use('/api/flights', require('./routes/flights').router);
|
||||
app.use('/api/seats', require('./routes/seats').router);
|
||||
|
||||
// run the API
|
||||
app.listen(process.env.PORT, async () => {
|
||||
log(`running at port ${process.env.PORT}`);
|
||||
});
|
||||
|
||||
// test
|
||||
// require('./modules/fetcher').post('http://127.0.0.1:1109/users/login', { 'usernameOrEmail':'foo', 'password':'bar' }).then(res => console.log(res));
|
||||
27
api/modules/database.js
Normal file
27
api/modules/database.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const mysql = require('mysql2');
|
||||
const mysql_promise = require('mysql2/promise');
|
||||
|
||||
const connection = mysql.createConnection({
|
||||
host: process.env.DATABASE_HOST,
|
||||
user: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
database: process.env.DATABASE_NAME,
|
||||
});
|
||||
const pool = mysql_promise.createPool({
|
||||
host: process.env.DATABASE_HOST,
|
||||
user: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
database: process.env.DATABASE_NAME,
|
||||
});
|
||||
|
||||
function createPool(host, user, password, db) {
|
||||
const newPool = mysql_promise.createPool({
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
database: db,
|
||||
});
|
||||
return newPool;
|
||||
}
|
||||
|
||||
module.exports = { connection, pool, createPool };
|
||||
71
api/modules/fetcher.js
Normal file
71
api/modules/fetcher.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const fetch = require('node-fetch');
|
||||
const { error } = require('./log');
|
||||
|
||||
async function get(url, token) {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json', authorization: `${token}` },
|
||||
};
|
||||
|
||||
return await fetch(url, options)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
return json;
|
||||
})
|
||||
.catch(err => error(err));
|
||||
}
|
||||
|
||||
async function post(url, body, token) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: { 'Content-Type': 'application/json', authorization: `${token}` },
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
return await fetch(url, options)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
return json;
|
||||
})
|
||||
.catch(err => error(err));
|
||||
}
|
||||
|
||||
async function patch(url, body, token) {
|
||||
const options = {
|
||||
method: 'PATCH',
|
||||
mode: 'cors',
|
||||
headers: { 'Content-Type': 'application/json', authorization: `${token}` },
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
return await fetch(url, options)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
return json;
|
||||
})
|
||||
.catch(err => error(err));
|
||||
}
|
||||
|
||||
async function put(url, body, token) {
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
mode: 'cors',
|
||||
headers: { 'Content-Type': 'application/json', authorization: `${token}` },
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
return await fetch(url, options)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
return json;
|
||||
})
|
||||
.catch(err => error(err));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get,
|
||||
post,
|
||||
patch,
|
||||
put,
|
||||
};
|
||||
79
api/modules/fileManager.js
Normal file
79
api/modules/fileManager.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const fs = require('fs');
|
||||
const download = require('download');
|
||||
const random = require('./random');
|
||||
|
||||
function fileExist(path) {
|
||||
try {
|
||||
fs.readFileSync(path);
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function fileDelete(path) {
|
||||
try {
|
||||
fs.unlinkSync(path);
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function fileDownload(url, name) {
|
||||
try {
|
||||
download(url, '../cdn/images/', { filename: name });
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function folderExist(path) {
|
||||
try {
|
||||
if (fs.existsSync(path)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getFilesFromFolder(path) {
|
||||
try {
|
||||
return fs.readdirSync(path);
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function randomFileFromFolder(path) {
|
||||
try {
|
||||
if (getFilesFromFolder(path)) {
|
||||
return random.random(0, getFilesFromFolder(path).length);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fileExist,
|
||||
fileDelete,
|
||||
fileDownload,
|
||||
folderExist,
|
||||
getFilesFromFolder,
|
||||
randomFileFromFolder,
|
||||
};
|
||||
31
api/modules/formatHandler.js
Normal file
31
api/modules/formatHandler.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const dns = require('dns');
|
||||
|
||||
function isEmailDomainValid(email) {
|
||||
const domain = email.split('@')[1];
|
||||
return new Promise((resolve, reject) => {
|
||||
dns.lookup(domain, (err, address) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
const isIPAddress = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(address);
|
||||
resolve(isIPAddress);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /\S+@\S+\.\S+/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
function isNumber(x) {
|
||||
return /^-?[\d.]+(?:e-?\d+)?$/.test(x);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isEmailDomainValid,
|
||||
isValidEmail,
|
||||
isNumber,
|
||||
};
|
||||
26
api/modules/log.js
Normal file
26
api/modules/log.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const pino = require('pino');
|
||||
|
||||
const logger = pino();
|
||||
|
||||
function log(x) {
|
||||
logger.info(x);
|
||||
}
|
||||
|
||||
function debug(x) {
|
||||
logger.debug(x);
|
||||
}
|
||||
|
||||
function warn(x) {
|
||||
logger.warn(x);
|
||||
}
|
||||
|
||||
function error(x) {
|
||||
logger.error(x);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
debug,
|
||||
warn,
|
||||
error,
|
||||
};
|
||||
100
api/modules/mailHandler.js
Normal file
100
api/modules/mailHandler.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const argon2 = require('argon2');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { random } = require('./random');
|
||||
const { createPool } = require('./database');
|
||||
|
||||
const pool = createPool('localhost', 'root', '', 'postfixadmin');
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP,
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: process.env.MAIL,
|
||||
pass: process.env.MAIL_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
async function createAddress(username, domain, password, name = '', backup_email = '', phone = '') {
|
||||
try {
|
||||
const hashedPassword = await argon2.hash(password, {
|
||||
type: argon2.argon2i,
|
||||
memoryCost: 2 ** 15,
|
||||
hashLength: 32,
|
||||
timeCost: 5,
|
||||
});
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `created`, `modified`, `active`, `phone`, `email_other`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ username, hashedPassword, name, `${domain}/${username}`, 0, username, domain, Date.now(), Date.now(), '1', phone, backup_email],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sendMail(email, head, body) {
|
||||
try {
|
||||
// setup email data
|
||||
const mailOptions = {
|
||||
from: `"AirJet" <${process.env.MAIL}>`,
|
||||
to: email,
|
||||
subject: head,
|
||||
text: body,
|
||||
};
|
||||
// send mail with defined transport object
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
}
|
||||
else {
|
||||
console.log('Email sent: ' + info.response);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sendVerification(email, userId) {
|
||||
try {
|
||||
const code = random(100000, 999999);
|
||||
if (sendMail(email, 'Your verification code for AirJet', `Verification code: ${code}\nLink: https://aostia.me/api/users/verify?u=${userId}&c=${code}`)) {
|
||||
return code;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sendResetVerification(email, code = random(100000, 999999)) {
|
||||
try {
|
||||
if (sendMail(email, 'Your reset verification code for AirJet', `Verification code: ${code}`)) {
|
||||
return code;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendMail,
|
||||
sendVerification,
|
||||
sendResetVerification,
|
||||
createAddress,
|
||||
};
|
||||
101
api/modules/permission.js
Normal file
101
api/modules/permission.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const { pool } = require('../modules/database.js');
|
||||
const { respondWithStatus } = require('./requestHandler');
|
||||
|
||||
// Middleware to verify the user permissions
|
||||
async function verifyPermissions(userId, perms_req) {
|
||||
|
||||
try {
|
||||
// Query the database to get the user
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (user.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query the database to get the perms and verify
|
||||
const [hasPerm] = await pool.execute(
|
||||
'SELECT COUNT(*) AS count FROM user_type_permissions WHERE user_type_id = ? AND permission_id = (SELECT id FROM permissions WHERE name = ?) LIMIT 1',
|
||||
[ user[0].user_type_id, perms_req ],
|
||||
);
|
||||
if (hasPerm.length === 0) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const hasPermission = (perms_req) => async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
|
||||
// Query the database to get the user
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (user.length === 0) {
|
||||
return await respondWithStatus(res, 401, 'User is invalid');
|
||||
}
|
||||
// Query the database to get the perms and verify
|
||||
const [hasPerm] = await pool.execute(
|
||||
'SELECT COUNT(*) AS count FROM user_type_permissions WHERE user_type_id = ? AND permission_id = (SELECT id FROM permissions WHERE name = ?) LIMIT 1',
|
||||
[ user[0].user_type_id, perms_req ],
|
||||
);
|
||||
if (req.originalUrl == '/api/users/me') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if (hasPerm.length === 0) {
|
||||
return await respondWithStatus(res, 403, 'Missing permission');
|
||||
}
|
||||
else if (hasPerm[0].count == 0) {
|
||||
return await respondWithStatus(res, 403, 'Missing permission');
|
||||
}
|
||||
else {next();}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
};
|
||||
|
||||
async function checkBanned(req, res, next) {
|
||||
const userId = req.userId;
|
||||
try {
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (user.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
if (user[0].is_banned) {
|
||||
return await respondWithStatus(res, 403, 'User is banned');
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
|
||||
async function isBanned(userId) {
|
||||
try {
|
||||
// Query the database to get the user
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (user.length === 0) {
|
||||
return true;
|
||||
}
|
||||
else if (user[0].is_banned == 1) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifyPermissions, hasPermission, checkBanned, isBanned };
|
||||
14
api/modules/random.js
Normal file
14
api/modules/random.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
function random(x, y) {
|
||||
return crypto.randomInt(x, y);
|
||||
}
|
||||
|
||||
function randomHEX(x) {
|
||||
return crypto.randomBytes(x).toString('hex');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
random,
|
||||
randomHEX,
|
||||
};
|
||||
55
api/modules/requestHandler.js
Normal file
55
api/modules/requestHandler.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const slowDown = require('express-slow-down');
|
||||
const http = require('http');
|
||||
const os = require('os');
|
||||
|
||||
const requestLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: 'Too many requests from this IP, please try again later',
|
||||
});
|
||||
|
||||
const speedLimiter = slowDown({
|
||||
windowMs: 60 * 1000,
|
||||
delayAfter: 30,
|
||||
delayMs: 500,
|
||||
skipFailedRequests: true,
|
||||
});
|
||||
|
||||
function checkSystemLoad(req, res, next) {
|
||||
const load = os.loadavg()[0];
|
||||
const cores = os.cpus().length;
|
||||
const threshold = cores * 0.7;
|
||||
|
||||
if (load > threshold) {
|
||||
return res.status(503).send(http.STATUS_CODES[503]);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
function respondWithStatus(res, statusCode, message) {
|
||||
const response = { status: statusCode, message: message };
|
||||
if (statusCode >= 400 && statusCode <= 599) {
|
||||
response.error = http.STATUS_CODES[statusCode];
|
||||
}
|
||||
return res.status(statusCode).json(response);
|
||||
}
|
||||
|
||||
function respondWithStatusJSON(res, statusCode, JSON) {
|
||||
const response = { status: statusCode, JSON };
|
||||
if (statusCode >= 400 && statusCode <= 599) {
|
||||
response.error = http.STATUS_CODES[statusCode];
|
||||
}
|
||||
return res.status(statusCode).json(response);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requestLimiter,
|
||||
speedLimiter,
|
||||
checkSystemLoad,
|
||||
respondWithStatus,
|
||||
respondWithStatusJSON,
|
||||
};
|
||||
103
api/modules/token.js
Normal file
103
api/modules/token.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
// const { Level } = require('level');
|
||||
const levelup = require('levelup');
|
||||
const leveldown = require('leveldown');
|
||||
const argon2 = require('argon2');
|
||||
|
||||
const { respondWithStatus } = require('./requestHandler');
|
||||
const { pool } = require('./database');
|
||||
|
||||
// Set up LevelDB instance
|
||||
// const db = new Level('./tokensDB');
|
||||
const db = levelup(leveldown('./tokensDB'));
|
||||
|
||||
// Generate a new JWT
|
||||
const generateToken = async (userId, password) => {
|
||||
const token = jwt.sign({ userId: userId, password: password }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
||||
await db.put(token, 'valid');
|
||||
return token;
|
||||
};
|
||||
|
||||
// Middleware to verify the JWT and set req.userId
|
||||
const verifyToken = async (req, res, next) => {
|
||||
const token = req.headers.authorization;
|
||||
if (!token) {
|
||||
return await respondWithStatus(res, 401, 'No token provided');
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.userId = decoded.userId;
|
||||
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM users WHERE id = ? LIMIT 1',
|
||||
[req.userId],
|
||||
);
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 404, 'User not found!');
|
||||
}
|
||||
const passwordMatch = await argon2.verify(rows[0].password, decoded.password);
|
||||
if (!passwordMatch) {
|
||||
return await respondWithStatus(res, 401, 'Token is invalid');
|
||||
}
|
||||
// Check if the token is close to expiring
|
||||
const now = Date.now().valueOf() / 1000;
|
||||
if (decoded.exp - now < 36000) {
|
||||
// Generate a new token if the old one is close to expiring
|
||||
const newToken = generateToken(req.userId, decoded.password);
|
||||
res.cookie('token', newToken, {
|
||||
expires: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
res.set('Authorization', newToken);
|
||||
}
|
||||
|
||||
// Check if the token has been revoked
|
||||
const tokenStatus = await db.get(token);
|
||||
if (tokenStatus != 'valid') {
|
||||
return await respondWithStatus(res, 401, 'Token has been revoked ');
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
return await respondWithStatus(res, 401, 'Invalid user');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to revoke a token
|
||||
const revokeToken = (token) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.put(token, 'revoked', (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Function to revoke all tokens of a user
|
||||
const revokeUserTokens = (userId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tokensToRevoke = [];
|
||||
db.createReadStream()
|
||||
.on('data', (data) => {
|
||||
const token = data.key;
|
||||
const decoded = jwt.decode(token);
|
||||
if (decoded.userId === userId) {
|
||||
tokensToRevoke.push(token);
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
Promise.all(tokensToRevoke.map(revokeToken))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { generateToken, verifyToken, revokeToken, revokeUserTokens };
|
||||
3047
api/package-lock.json
generated
Normal file
3047
api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
api/package.json
Normal file
33
api/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "airjet",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Lightemerald",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"argon2": "^0.30.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-slow-down": "^1.5.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"level": "^8.0.0",
|
||||
"leveldown": "^6.1.1",
|
||||
"levelup": "^5.1.1",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.1.2",
|
||||
"nodemailer": "^6.9.1",
|
||||
"path": "^0.12.7",
|
||||
"pino": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.34.0"
|
||||
}
|
||||
}
|
||||
144
api/routes/airlines.js
Normal file
144
api/routes/airlines.js
Normal file
@@ -0,0 +1,144 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_airlines'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airlines WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airlines not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_airlines'), async (req, res) => {
|
||||
const { name, code, logo } = req.body;
|
||||
if ([ name, code, logo ].every(Boolean)) {
|
||||
try {
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO airlines (name, code, logo) VALUES (?, ?, ?)',
|
||||
[ name, code, logo ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing airline');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airline created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:airlineId', verifyToken, checkBanned, hasPermission('view_airlines'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airlineId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airlines WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airline not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:airlineId', verifyToken, checkBanned, hasPermission('edit_airlines'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airlineId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM airlines WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airline not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE airlines SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airline');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airline updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:airlineId', verifyToken, checkBanned, hasPermission('edit_airlines'), async (req, res) => {
|
||||
const id = req.params.airlineId;
|
||||
const { name, code, logo } = req.body;
|
||||
if ([name, code, logo].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airlines WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airline not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE airlines SET name = ?, code = ?, logo = ? WHERE id = ?',
|
||||
[name, code, logo, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airline');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airline updated successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:airlineId', verifyToken, checkBanned, hasPermission('delete_airlines'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airlineId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airlines WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airline not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM airlines WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing airline');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airline removed successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
144
api/routes/airplanes.js
Normal file
144
api/routes/airplanes.js
Normal file
@@ -0,0 +1,144 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_airplanes'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airplanes WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airplanes not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_airplanes'), async (req, res) => {
|
||||
const { name, type, manufacturer, capacity, status, location } = req.body;
|
||||
if ([name, type, manufacturer, capacity, status, location].every(Boolean)) {
|
||||
try {
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO airplanes (name, type, manufacturer, capacity, status, location) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[ name, type, manufacturer, capacity, status, location ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing airplane');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airplane created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:airplaneId', verifyToken, checkBanned, hasPermission('view_airplanes'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airplaneId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airplanes WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airplane not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:airplaneId', verifyToken, checkBanned, hasPermission('edit_airplanes'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airplaneId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM airplanes WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airplane not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE airplanes SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airplane');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airplane updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:airplaneId', verifyToken, checkBanned, hasPermission('edit_airplanes'), async (req, res) => {
|
||||
const id = req.params.airplaneId;
|
||||
const { name, type, manufacturer, capacity, status, location } = req.body;
|
||||
if ([name, type, manufacturer, capacity, status, location].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airplanes WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airplane not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE airplanes SET name = ?, type = ?, manufacturer = ?, capacity = ?, status = ?, location = ? WHERE id = ?',
|
||||
[name, type, manufacturer, capacity, status, location, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airplane');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airplane updated successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:airplaneId', verifyToken, checkBanned, hasPermission('delete_airplanes'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airplaneId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airplanes WHERE id = ? LIMIT', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airplane not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM airplanes WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing airplane');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airplane deleted successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
144
api/routes/airports.js
Normal file
144
api/routes/airports.js
Normal file
@@ -0,0 +1,144 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_airports'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airports WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airports not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_airports'), async (req, res) => {
|
||||
const { name, code, city, country, latitude, longitude } = req.body;
|
||||
if ([name, code, city, country, latitude, longitude].every(Boolean)) {
|
||||
try {
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO airports (name, code, city, country, latitude, longitude) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[ name, code, city, country, latitude, longitude ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing airport');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airport created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:airportId', verifyToken, checkBanned, hasPermission('view_airports'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airportId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airports WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airports not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:airportId', verifyToken, checkBanned, hasPermission('edit_airports'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airportId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM airports WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airport not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE airports SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airport');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airport updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:airportId', verifyToken, checkBanned, hasPermission('edit_airports'), async (req, res) => {
|
||||
const id = req.params.airportId;
|
||||
const { name, code, city, country, latitude, longitude } = req.body;
|
||||
if ([name, code, city, country, latitude, longitude].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM airports WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airport not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE airports SET name = ?, code = ?, city = ?, country = ?, latitude = ?, longitude = ? WHERE id = ?',
|
||||
[name, code, city, country, latitude, longitude, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating airport');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airport updated successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:airportId', verifyToken, checkBanned, hasPermission('delete_airports'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.airportId;
|
||||
const [rows] = await pool.execute('SELECT * FROM airports WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Airport not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM airports WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing airport');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Airport deleted successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
144
api/routes/flights.js
Normal file
144
api/routes/flights.js
Normal file
@@ -0,0 +1,144 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_flights'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM flights');
|
||||
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 404, 'Flights not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_flights'), async (req, res) => {
|
||||
const { airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status } = req.body;
|
||||
if ([airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status].every(Boolean)) {
|
||||
try {
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO flights (airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing flight');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Flight created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:flightId', verifyToken, checkBanned, hasPermission('view_flights'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.flightId;
|
||||
const [rows] = await pool.execute('SELECT * FROM flights WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Flight not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:flightId', verifyToken, checkBanned, hasPermission('edit_flights'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.flightId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM flights WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Flight not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE flights SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating flight');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Flight updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:flightId', verifyToken, checkBanned, hasPermission('edit_flights'), async (req, res) => {
|
||||
const id = req.params.flightId;
|
||||
const { airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status } = req.body;
|
||||
if ([airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM flights WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Flight not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE flights SET airline_id = ?, pilot_id = ?, flight_no = ?, origin_id = ?, destination_id = ?, departure_time = ?, arrival_time = ?, duration_minutes= ?, price_economy = ?, price_business = ?, price_first_class = ?, status = ? WHERE id = ?',
|
||||
[airline_id, pilot_id, flight_no, origin_id, destination_id, departure_time, arrival_time, duration_minutes, price_economy, price_business, price_first_class, status, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating flight');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Flight updated successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:flightId', verifyToken, checkBanned, hasPermission('delete_flights'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.flightId;
|
||||
const [rows] = await pool.execute('SELECT * FROM flights WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Flight not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM flights WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing flight');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Flight deleted successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
145
api/routes/pilots.js
Normal file
145
api/routes/pilots.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_pilots'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM pilots WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Pilots not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_pilots'), async (req, res) => {
|
||||
const { first_name, last_name, email, phone, license_number, license_expiry, salary, status } = req.body;
|
||||
if ([first_name, last_name, email, phone, license_number, license_expiry, salary, status].every(Boolean)) {
|
||||
try {
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO pilots (first_name, last_name, email, phone, license_number, license_expiry, salary, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ first_name, last_name, email, phone, license_number, license_expiry, salary, status ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing pilot');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Pilot created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:pilotId', verifyToken, checkBanned, hasPermission('view_pilots'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.pilotId;
|
||||
const [rows] = await pool.execute('SELECT * FROM pilots WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Pilot not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:pilotId', verifyToken, checkBanned, hasPermission('edit_pilots'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.pilotId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM pilots WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Pilot not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE pilots SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating pilot');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Pilot updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:pilotId', verifyToken, checkBanned, hasPermission('edit_pilots'), async (req, res) => {
|
||||
const id = req.params.pilotId;
|
||||
const { first_name, last_name, email, phone, license_number, license_expiry, salary, status } = req.body;
|
||||
if ([first_name, last_name, email, phone, license_number, license_expiry, salary, status].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM pilots WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Pilot not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE pilots SET first_name = ?, last_name = ?, email = ?, phone = ?, license_number = ?, license_expiry = ?, salary = ?, status = ? WHERE id = ?',
|
||||
[first_name, last_name, email, phone, license_number, license_expiry, salary, status, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating pilot');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Pilot updated successfully');
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:pilotId', verifyToken, checkBanned, hasPermission('delete_pilots'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.pilotId;
|
||||
const [rows] = await pool.execute('SELECT * FROM pilots WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Pilot not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM pilots WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing pilot');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Pilot removed successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
156
api/routes/seats.js
Normal file
156
api/routes/seats.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { verifyToken } = require('../modules/token');
|
||||
const { hasPermission, checkBanned } = require('../modules/permission');
|
||||
const { respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_seats'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM seats WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Seats not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_seats'), async (req, res) => {
|
||||
const { user_id, flight_id, place_no, seat_class } = req.body;
|
||||
if ([ user_id, flight_id, place_no, seat_class ].every(Boolean)) {
|
||||
try {
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [user_id]);
|
||||
|
||||
if (user.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
|
||||
const [flight] = await pool.execute('SELECT * FROM flights WHERE id = ? LIMIT 1', [flight_id]);
|
||||
|
||||
if (flight.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Flight not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO seats (user_id, flight_id, place_no, class) VALUES (?, ?, ?, ?)',
|
||||
[ user_id, flight_id, place_no, seat_class ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing seat');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Seat created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:seatId', verifyToken, checkBanned, hasPermission('view_seats'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.seatId;
|
||||
const [rows] = await pool.execute('SELECT * FROM seats WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Seat not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows[0]);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:seatId', verifyToken, checkBanned, hasPermission('edit_seats'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.seatId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM seats WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Seat not found');
|
||||
}
|
||||
const fields = rows.map(row => Object.keys(row));
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE seats SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating seat');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Seat updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:seatId', verifyToken, checkBanned, hasPermission('edit_seats'), async (req, res) => {
|
||||
const id = req.params.seatId;
|
||||
const { user_id, flight_id, place_no, seat_class } = req.body;
|
||||
if ([ user_id, flight_id, place_no, seat_class ].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM seats WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Seat not found');
|
||||
}
|
||||
const [result] = await pool.execute(
|
||||
'UPDATE seats SET user_id = ?, flight_id = ?, place_no = ?, class = ? WHERE id = ?',
|
||||
[user_id, flight_id, place_no, seat_class, id],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating seat');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Seat updated successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:seatId', verifyToken, checkBanned, hasPermission('delete_seats'), async (req, res) => {
|
||||
try {
|
||||
const id = req.params.seatId;
|
||||
const [rows] = await pool.execute('SELECT * FROM seats WHERE id = ? LIMIT 1', [id]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Seat not found');
|
||||
}
|
||||
|
||||
const [result] = await pool.execute('DELETE FROM seats WHERE id = ?', [id]);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing seat');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Seat removed successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
35
api/routes/test.js
Normal file
35
api/routes/test.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
|
||||
res.status(200).json({ code: 200, message:'Received GET request' });
|
||||
|
||||
});
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
|
||||
res.status(200).json({ code: 200, message:'Received POST request' });
|
||||
|
||||
});
|
||||
|
||||
router.patch('/', (req, res) => {
|
||||
|
||||
res.status(200).json({ code: 200, message:'Received PUT request' });
|
||||
|
||||
});
|
||||
|
||||
router.put('/', (req, res) => {
|
||||
|
||||
res.status(200).json({ code: 200, message:'Received PUT request' });
|
||||
|
||||
});
|
||||
|
||||
router.delete('/', (req, res) => {
|
||||
|
||||
res.status(200).json({ code: 200, message:'Received DELETE request' });
|
||||
|
||||
});
|
||||
|
||||
module.exports.router = router;
|
||||
462
api/routes/users.js
Normal file
462
api/routes/users.js
Normal file
@@ -0,0 +1,462 @@
|
||||
const express = require('express');
|
||||
const argon2 = require('argon2');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { pool } = require('../modules/database');
|
||||
const { sendVerification, sendResetVerification } = require('../modules/mailHandler');
|
||||
const { isEmailDomainValid, isValidEmail, isNumber } = require('../modules/formatHandler');
|
||||
const { hasPermission, checkBanned, isBanned } = require('../modules/permission');
|
||||
const { verifyToken, generateToken, revokeUserTokens } = require('../modules/token');
|
||||
const { requestLimiter, respondWithStatus, respondWithStatusJSON } = require('../modules/requestHandler');
|
||||
|
||||
|
||||
router.post('/register', requestLimiter, async (req, res) => {
|
||||
const { username, email, password, first_name, last_name, phone = 'none' } = req.body;
|
||||
if ([ username, email, password, first_name, last_name ].every(Boolean)) {
|
||||
try {
|
||||
if (isValidEmail(email) && isEmailDomainValid(email)) {
|
||||
const [existingUsername] = await pool.execute('SELECT * FROM users WHERE username = ? LIMIT 1', [username]);
|
||||
if (existingUsername.length) {
|
||||
return await respondWithStatus(res, 400, 'Username is already taken');
|
||||
}
|
||||
|
||||
const [existingEmail] = await pool.execute('SELECT * FROM users WHERE email = ? LIMIT 1', [email]);
|
||||
if (existingEmail.length) {
|
||||
return await respondWithStatus(res, 400, 'Email is already taken');
|
||||
}
|
||||
const hashedPassword = await argon2.hash(password);
|
||||
const [unverifiedId] = await pool.execute(
|
||||
'SELECT id FROM user_types WHERE name = \'unverified\' LIMIT 1',
|
||||
);
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO users (first_name, last_name, username, email, password, user_type_id, phone) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[ first_name, last_name, username, email, hashedPassword, unverifiedId[0].id, phone ],
|
||||
);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error storing user');
|
||||
}
|
||||
const [rows] = await pool.execute('SELECT id FROM users WHERE email = ? LIMIT 1', [email]);
|
||||
const code = sendVerification(email, rows[0].id);
|
||||
pool.execute('INSERT INTO user_email_verifications (user_id, verification_code, type) VALUES (?, ?, ?)', [ rows[0].id, code, 'register' ]);
|
||||
return await respondWithStatus(res, 200, 'Successfully registered');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid email address');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/login', requestLimiter, async (req, res) => {
|
||||
const { usernameOrEmail, password } = req.body;
|
||||
if ([usernameOrEmail, password].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM users WHERE username = ? OR email = ? LIMIT 1',
|
||||
[usernameOrEmail, usernameOrEmail],
|
||||
);
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 404, 'Incorrect username or email');
|
||||
}
|
||||
const user = rows[0];
|
||||
const passwordMatch = await argon2.verify(user.password, password);
|
||||
if (!passwordMatch) {
|
||||
return await respondWithStatus(res, 401, 'Incorrect password');
|
||||
}
|
||||
if (isBanned(user.id)) {
|
||||
const token = await generateToken(user.id, password);
|
||||
res.cookie('token', token, {
|
||||
expires: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
return await respondWithStatusJSON(res, 200, {
|
||||
message: 'Login successful',
|
||||
token: token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 403, 'User is banned or an issue occured');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/verify', async (req, res) => {
|
||||
const { c, u } = req.query;
|
||||
if ([c, u].every(Boolean)) {
|
||||
try {
|
||||
let userId = u;
|
||||
if (!isNumber(u)) {
|
||||
const [user] = await pool.execute(
|
||||
'SELECT id FROM users WHERE username = ? OR email = ? LIMIT 1',
|
||||
[u, u],
|
||||
);
|
||||
if (!user.length) {
|
||||
return await respondWithStatus(res, 404, 'Incorrect username or email');
|
||||
}
|
||||
userId = user[0].id;
|
||||
}
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM user_email_verifications WHERE user_id = ? AND verification_code = ? LIMIT 1',
|
||||
[userId, c],
|
||||
);
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 400, 'Invalid code');
|
||||
}
|
||||
|
||||
if (rows[0].type == 'register') {
|
||||
const [customerId] = await pool.execute(
|
||||
'SELECT id FROM user_types WHERE name = \'customer\' LIMIT 1',
|
||||
);
|
||||
const [result] = await pool.execute('UPDATE users SET user_type_id = ? WHERE id = ?', [ customerId[0].id, userId ]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating user');
|
||||
}
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Successfully verified user');
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/verify', verifyToken, checkBanned, async (req, res) => {
|
||||
const { code } = req.body;
|
||||
const userId = req.userId;
|
||||
if ([code, userId].every(Boolean)) {
|
||||
try {
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM user_email_verifications WHERE user_id = ? AND verification_code = ? LIMIT 1',
|
||||
[ userId, code ],
|
||||
);
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 400, 'Invalid code');
|
||||
}
|
||||
const [customerId] = await pool.execute(
|
||||
'SELECT id FROM user_types WHERE name = \'customer\' LIMIT 1',
|
||||
);
|
||||
const [result] = await pool.execute('UPDATE users SET user_type_id = ? WHERE userId = ?', [ customerId[0].id, userId ]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating user');
|
||||
}
|
||||
return await respondWithStatus(res, 200, 'Successfully verified user');
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/changepassword', async (req, res) => {
|
||||
const { usernameOrEmail } = req.body;
|
||||
if ([ usernameOrEmail ].every(Boolean)) {
|
||||
try {
|
||||
const [user] = await pool.execute('SELECT * FROM users WHERE email = ? OR username = ? LIMIT 1', [usernameOrEmail, usernameOrEmail]);
|
||||
if (user.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
|
||||
let code;
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM user_email_verifications WHERE user_id = ? AND type = \'password\' LIMIT 1',
|
||||
[user[0].id],
|
||||
);
|
||||
if (!rows.length) {
|
||||
code = sendResetVerification(user[0].email);
|
||||
}
|
||||
else {
|
||||
code = sendResetVerification(user[0].email, rows[0].verification_code);
|
||||
}
|
||||
|
||||
if (code) {
|
||||
pool.execute('INSERT INTO user_email_verifications (user_id, verification_code, type) VALUES (?, ?, ?)', [ user[0].id, code, 'password' ]);
|
||||
return await respondWithStatus(res, 200, 'Successfully sent password reset email');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/changepassword', async (req, res) => {
|
||||
const { usernameOrEmail, password, code } = req.body;
|
||||
try {
|
||||
const [user] = await pool.execute(
|
||||
'SELECT id FROM users WHERE username = ? OR email = ? LIMIT 1',
|
||||
[usernameOrEmail, usernameOrEmail],
|
||||
);
|
||||
if (!user.length) {
|
||||
return await respondWithStatus(res, 404, 'Incorrect username or email');
|
||||
}
|
||||
const [rows] = await pool.execute(
|
||||
'SELECT * FROM user_email_verifications WHERE user_id = ? AND verification_code = ? AND type = \'password\' ORDER BY 1 DESC LIMIT 1',
|
||||
[user[0].id, code],
|
||||
);
|
||||
if (!rows.length) {
|
||||
return await respondWithStatus(res, 400, 'Invalid code');
|
||||
}
|
||||
const [result] = await pool.execute('DELETE FROM user_email_verifications WHERE user_id = ? AND verification_code = ?', [ user[0].id, code ]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing verification');
|
||||
}
|
||||
revokeUserTokens(user[0].id);
|
||||
const token = generateToken(user[0].id, password);
|
||||
res.cookie('token', token, {
|
||||
expires: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
return userPATCH(res, user[0].id, 'password', password);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', verifyToken, checkBanned, hasPermission('view_users'), async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE 1');
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'Users not found');
|
||||
}
|
||||
return await respondWithStatusJSON(res, 200, rows);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', verifyToken, checkBanned, hasPermission('add_users'), async (req, res) => {
|
||||
const { first_name, last_name, username, email, password, user_type } = req.body;
|
||||
if ([first_name, last_name, username, email, password, user_type].every(Boolean)) {
|
||||
try {
|
||||
const hashedPassword = await argon2.hash(password);
|
||||
await pool.execute(
|
||||
'INSERT INTO users (first_name, last_name, username, email, password, user_type) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[ first_name, last_name, username, email, hashedPassword, user_type ],
|
||||
);
|
||||
return await respondWithStatus(res, 200, 'User created successfully');
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Missing fields');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { type, value } = req.body;
|
||||
userPATCH(res, userId, type, value);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { first_name, last_name, username, email } = req.body;
|
||||
userPUT(res, userId, first_name, last_name, username, email);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/', verifyToken, checkBanned, async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
userDELETE(res, userId);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:userId', verifyToken, checkBanned, hasPermission('view_users'), async (req, res) => {
|
||||
try {
|
||||
let userId = req.params.userId;
|
||||
if (req.params.userId == 'me') {
|
||||
userId = req.userId;
|
||||
}
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
const user = rows[0];
|
||||
delete user.password;
|
||||
return await respondWithStatusJSON(res, 200, user);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/:userId', verifyToken, checkBanned, hasPermission('edit_users'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const { type, value } = req.body;
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
const excludedKeys = ['id'];
|
||||
const fields = rows.map(row =>
|
||||
Object.keys(row)
|
||||
.filter(key => !excludedKeys.includes(key)),
|
||||
);
|
||||
console.log(fields[0]);
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE users SET ${type} = ? WHERE id = ?`, [value, userId]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating user');
|
||||
}
|
||||
return respondWithStatus(res, 200, 'User updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type or disallowed');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:userId', verifyToken, checkBanned, hasPermission('edit_users'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
const { first_name, last_name, username, email } = req.body;
|
||||
if ([first_name, last_name, username, email].every(Boolean)) {
|
||||
userPUT(res, userId, first_name, last_name, username, email);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:userId', verifyToken, checkBanned, hasPermission('delete_users'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.userId;
|
||||
userDELETE(res, userId);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return await respondWithStatus(res, 500, 'An error has occured');
|
||||
}
|
||||
});
|
||||
|
||||
async function userPATCH(res, id, type, value) {
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [id]);
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
const excludedKeys = ['id', 'user_type_id', 'is_banned'];
|
||||
const fields = rows.map(row =>
|
||||
Object.keys(row)
|
||||
.filter(key => !excludedKeys.includes(key)),
|
||||
);
|
||||
if (type == 'password') {
|
||||
value = await argon2.hash(value);
|
||||
}
|
||||
if (fields[0].includes(type)) {
|
||||
const [result] = await pool.execute(`UPDATE users SET ${type} = ? WHERE id = ?`, [value, id]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating user');
|
||||
}
|
||||
return respondWithStatus(res, 200, 'User updated successfully');
|
||||
}
|
||||
else {
|
||||
return await respondWithStatus(res, 400, 'Invalid type or disallowed');
|
||||
}
|
||||
}
|
||||
|
||||
async function userPUT(res, userId, first_name, last_name, username, email, password = false) {
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
let sqlQuery, queryParams;
|
||||
if (password) {
|
||||
const hashedPassword = await argon2.hash(password);
|
||||
sqlQuery = 'UPDATE users SET first_name = ?, last_name = ?, username = ?, email = ?, password = ? WHERE id = ?';
|
||||
queryParams = [first_name, last_name, username, email, hashedPassword, userId];
|
||||
}
|
||||
else {
|
||||
sqlQuery = 'UPDATE users SET first_name = ?, last_name = ?, username = ?, email = ? WHERE id = ?';
|
||||
queryParams = [first_name, last_name, username, email, userId];
|
||||
}
|
||||
const [result] = await pool.execute(sqlQuery, queryParams);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error updating user');
|
||||
}
|
||||
return respondWithStatus(res, 200, 'User updated successfully');
|
||||
}
|
||||
|
||||
async function userDELETE(res, userId) {
|
||||
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
|
||||
if (rows.length === 0) {
|
||||
return await respondWithStatus(res, 404, 'User not found');
|
||||
}
|
||||
const [result] = await pool.execute('DELETE FROM users WHERE id = ?', [ userId ]);
|
||||
if (result.affectedRows === 0) {
|
||||
return await respondWithStatus(res, 500, 'Error removing user');
|
||||
}
|
||||
return respondWithStatus(res, 200, 'User deleted successfully');
|
||||
}
|
||||
|
||||
module.exports.router = router;
|
||||
1
api/tokensDB/CURRENT
Normal file
1
api/tokensDB/CURRENT
Normal file
@@ -0,0 +1 @@
|
||||
MANIFEST-000034
|
||||
0
api/tokensDB/LOCK
Normal file
0
api/tokensDB/LOCK
Normal file
3
api/tokensDB/LOG
Normal file
3
api/tokensDB/LOG
Normal file
@@ -0,0 +1,3 @@
|
||||
2023/03/12-14:35:27.203048 7f7857bff6c0 Recovering log #33
|
||||
2023/03/12-14:35:27.221582 7f7857bff6c0 Delete type=0 #33
|
||||
2023/03/12-14:35:27.221647 7f7857bff6c0 Delete type=3 #32
|
||||
3
api/tokensDB/LOG.old
Normal file
3
api/tokensDB/LOG.old
Normal file
@@ -0,0 +1,3 @@
|
||||
2023/03/12-14:34:25.260524 7f3e63bff6c0 Recovering log #31
|
||||
2023/03/12-14:34:25.289543 7f3e63bff6c0 Delete type=0 #31
|
||||
2023/03/12-14:34:25.289605 7f3e63bff6c0 Delete type=3 #30
|
||||
BIN
api/tokensDB/MANIFEST-000034
Normal file
BIN
api/tokensDB/MANIFEST-000034
Normal file
Binary file not shown.
Reference in New Issue
Block a user