diff --git a/README.md b/README.md
index ce59227..b4aa532 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,161 @@
-# airjet
+# README
+Ce logiciel contient à la fois une API et une Web App pour la gestion d'un système de management de compagnie aérienne.
+
+## Sommaire:
+
+- API
+ - Endpoints
+ - Modules
+- Web App
+- Installation
+- Variables d'environnement
+- Outil d'organisation de projet
+
+
+
+---
+
+
+
+## API
+
+L'API est construite en utilisant ExpressJS et MariaDB. Elle a les endpoints suivants:
+
+- **/users**: Endpoint pour la gestion des utilisateurs. Permet la création, la modification, la suppression et la récupération des informations sur les utilisateurs.
+- **/pilots**: Endpoint pour la gestion des pilotes. Permet la création, la modification, la suppression et la récupération des informations sur les pilotes.
+- **/airlines**: Endpoint pour la gestion des compagnies aériennes. Permet la création, la modification, la suppression et la récupération des informations sur les compagnies aériennes.
+- **/airplanes**: Endpoint pour la gestion des avions. Permet la création, la modification, la suppression et la récupération des informations sur les avions.
+- **/airports**: Endpoint pour la gestion des aéroports. Permet la création, la modification, la suppression et la récupération des informations sur les aéroports.
+- **/flights**: Endpoint pour la gestion des vols. Permet la création, la modification, la suppression et la récupération des informations sur les vols.
+- **/seats**: Endpoint pour la gestion des sièges. Permet la création, la modification, la suppression et la récupération des informations sur les sièges.
+
+
+
+L'API utilise également les modules créer specifiquement suivants:
+
+- **database**: Gère la connexion à la base de données MariaDB et fournit les méthodes pour la création de pool asynchrone SQL.
+- **fetcher**: Gère les méthodes de requete HTTP.
+- **fileManager**: Gère la gestion des fichiers.
+- **mailHandler**: Gère l'envoi d'e-mails, tels que les confirmations de de création de comptes et de réservation.
+- **permission**: Gère les autorisations d'accès aux différents endpoints.
+- **ratelimit**: Gère la limitation du taux de requêtes.
+- **token**: Gère la génération et la vérification des tokens d'authentification.
+
+
+
+---
+
+
+
+## Web App
+
+La Web App contient une page d'accueil qui permet aux utilisateurs de se connecter ou de s'inscrire, ainsi qu'une page de tableau de bord.
+
+La Web App utilise l'API pour communiquer avec la base de données et récupérer les informations nécessaires.
+
+- **html/**: Contient les fichiers HTML.
+- **scripts/**: Contient les fichiers de script JavaScript.
+- **styles/**: Contient les fichiers de styles CSS.
+- **images/**: Contient les assets images utilisé.
+
+
+
+---
+
+
+
+## Installation
+
+Pour installer airjet, suivez les étapes suivantes:
+
+1. Installer nodejs >= 18.16.0
+2. Installer les dependencies avec `npm i`
+3. Configurez les variables d'environnement dans un fichier .env à la racine de l'API.
+4. Creer une base de donnees `arijet` et importer le SQL `db.sql` dans celle-ci via votre serveur MariaDB/MySQL.
+5. Exécutez le serveur en utilisant la commande `node index`.
+
+Pour le fonctionnement correct des cookies et localStorage il est recommendé d'utiliser un serveur web.
+
+Il est aussi necessaire d'adapter les variables api dans app.js et dash.js pour utiliser l'url sur laquelle le serveur api est accessible.
+
+Lors de la création d'un utilisateur il recevera le role id 1, ce role n'as pas de permission par defaut mais celle-ci peuvent etre ajouter. Lors de la verification de l'email le role id passe a 2. Le role admin a toute les permissions il est d'id 6.
+
+
+
+---
+
+
+
+## Variables d'environnement
+
+Les variables d'environnement suivantes doivent être configurées dans le fichier .env:
+
+- **DATABASE_HOST**: L'adresse du serveur MariaDB/MySQL.
+- **DATABASE_NAME**: Nom de la base de données.
+- **DATABASE_USER**: Le nom d'utilisateur pour se connecter à la base de données.
+- **DATABASE_PASSWORD**: Le mot de passe pour se connecter à la base de données.
+- **JWT_SECRET**: Une chaîne de caractères aléatoire utilisée pour signer les tokens JWT (JSON Web Tokens), vous pouvez utilisé `./generate-secret.sh` sur un shell UNIX.
+- **PORT**: Le port sur lequel le serveur doit écouter les connexions entrantes.
+- **SMTP**: L'adresse SMTP du serveur de messagerie électronique à utiliser pour l'envoi de courriels.
+- **MAIL**: L'adresse électronique à partir de laquelle envoyer les courriels.
+- **MAIL_PASS**: Le mot de passe associé à l'adresse électronique pour se connecter au serveur de messagerie.
+
+
+
+---
+
+
+
+## Outil d'organisation de projet
+
+- **Mattermost**:
+
+---
+
+
+
+## To-do list
+
+- [ ] API
+ - [ ] Endpoints
+ - [x] Users (ajouter/modifier/supprimer)
+ - [x] Login
+ - [x] Register
+ - [x] Verification
+ - [x] Change password
+ - [x] Pilots (ajouter/modifier/supprimer)
+ - [x] Airlines (ajouter/modifier/supprimer)
+ - [x] Airplanes (ajouter/modifier/supprimer)
+ - [x] Airports (ajouter/modifier/supprimer)
+ - [x] Flights (ajouter/modifier/supprimer)
+ - [x] Seats (ajouter/modifier/supprimer)
+ - [ ] Orders
+ - [ ] Roles
+ - [x] Modules
+ - [x] Database
+ - [x] Fetcher
+ - [x] FileManager
+ - [x] MailHandler
+ - [x] Permission
+ - [ ] RoleManager
+ - [x] RateLimit
+ - [x] Token
+- [ ] Webapp
+ - [x] Home page
+ - [x] Login
+ - [x] Register
+ - [x] Reset password
+ - [ ] dashboard
+ - [x] Tiles system
+ - [ ] Profile
+ - [ ] Notifications
+ - [ ] Order
+ - [ ] Flight manager
+ - [ ] Data manager
+ - [ ] Logout
+ - [ ] Verification page
\ No newline at end of file
diff --git a/api/.eslintrc.json b/api/.eslintrc.json
new file mode 100644
index 0000000..ae431cc
--- /dev/null
+++ b/api/.eslintrc.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/api/generate-secret.sh b/api/generate-secret.sh
new file mode 100644
index 0000000..ca755e1
--- /dev/null
+++ b/api/generate-secret.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+secret=$(openssl rand -base64 32)
+echo "Generated secret key $secret"
diff --git a/api/index.js b/api/index.js
new file mode 100644
index 0000000..5c0b23a
--- /dev/null
+++ b/api/index.js
@@ -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));
\ No newline at end of file
diff --git a/api/modules/database.js b/api/modules/database.js
new file mode 100644
index 0000000..15ec9c3
--- /dev/null
+++ b/api/modules/database.js
@@ -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 };
diff --git a/api/modules/fetcher.js b/api/modules/fetcher.js
new file mode 100644
index 0000000..1cf1a2f
--- /dev/null
+++ b/api/modules/fetcher.js
@@ -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,
+};
\ No newline at end of file
diff --git a/api/modules/fileManager.js b/api/modules/fileManager.js
new file mode 100644
index 0000000..5bd78d9
--- /dev/null
+++ b/api/modules/fileManager.js
@@ -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,
+};
diff --git a/api/modules/formatHandler.js b/api/modules/formatHandler.js
new file mode 100644
index 0000000..46c2fe0
--- /dev/null
+++ b/api/modules/formatHandler.js
@@ -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,
+};
\ No newline at end of file
diff --git a/api/modules/log.js b/api/modules/log.js
new file mode 100644
index 0000000..394da0e
--- /dev/null
+++ b/api/modules/log.js
@@ -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,
+};
\ No newline at end of file
diff --git a/api/modules/mailHandler.js b/api/modules/mailHandler.js
new file mode 100644
index 0000000..6c3e038
--- /dev/null
+++ b/api/modules/mailHandler.js
@@ -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,
+};
\ No newline at end of file
diff --git a/api/modules/permission.js b/api/modules/permission.js
new file mode 100644
index 0000000..9114b50
--- /dev/null
+++ b/api/modules/permission.js
@@ -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 };
\ No newline at end of file
diff --git a/api/modules/random.js b/api/modules/random.js
new file mode 100644
index 0000000..22842ba
--- /dev/null
+++ b/api/modules/random.js
@@ -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,
+};
diff --git a/api/modules/requestHandler.js b/api/modules/requestHandler.js
new file mode 100644
index 0000000..fe94bfb
--- /dev/null
+++ b/api/modules/requestHandler.js
@@ -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,
+};
\ No newline at end of file
diff --git a/api/modules/token.js b/api/modules/token.js
new file mode 100644
index 0000000..d112d73
--- /dev/null
+++ b/api/modules/token.js
@@ -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 };
\ No newline at end of file
diff --git a/api/package-lock.json b/api/package-lock.json
new file mode 100644
index 0000000..9a6e676
--- /dev/null
+++ b/api/package-lock.json
@@ -0,0 +1,3047 @@
+{
+ "name": "airjet",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "airjet",
+ "version": "1.0.0",
+ "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",
+ "expressjs": "^1.0.1",
+ "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"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
+ "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+ "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@mapbox/node-pre-gyp": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
+ "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "make-dir": "^3.1.0",
+ "node-fetch": "^2.6.7",
+ "nopt": "^5.0.0",
+ "npmlog": "^5.0.1",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.11"
+ },
+ "bin": {
+ "node-pre-gyp": "bin/node-pre-gyp"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@phc/format": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
+ "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/abstract-level": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz",
+ "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "catering": "^2.1.0",
+ "is-buffer": "^2.0.5",
+ "level-supports": "^4.0.0",
+ "level-transcoder": "^1.0.1",
+ "module-error": "^1.0.1",
+ "queue-microtask": "^1.2.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/abstract-leveldown": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz",
+ "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "catering": "^2.0.0",
+ "is-buffer": "^2.0.5",
+ "level-concat-iterator": "^3.0.0",
+ "level-supports": "^2.0.1",
+ "queue-microtask": "^1.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/abstract-leveldown/node_modules/level-supports": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz",
+ "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
+ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/argon2": {
+ "version": "0.30.3",
+ "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.30.3.tgz",
+ "integrity": "sha512-DoH/kv8c9127ueJSBxAVJXinW9+EuPA3EMUxoV2sAY1qDE5H9BjTyVF/aD2XyHqbqUWabgBkIfcP3ZZuGhbJdg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@mapbox/node-pre-gyp": "^1.0.10",
+ "@phc/format": "^1.0.0",
+ "node-addon-api": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+ "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/body-parser/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browser-level": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz",
+ "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==",
+ "dependencies": {
+ "abstract-level": "^1.0.2",
+ "catering": "^2.1.1",
+ "module-error": "^1.0.2",
+ "run-parallel-limit": "^1.1.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/catering": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
+ "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/classic-level": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz",
+ "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "abstract-level": "^1.0.2",
+ "catering": "^2.1.0",
+ "module-error": "^1.0.1",
+ "napi-macros": "~2.0.0",
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-disposition/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+ "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+ "dependencies": {
+ "cookie": "0.4.1",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deferred-leveldown": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-7.0.0.tgz",
+ "integrity": "sha512-QKN8NtuS3BC6m0B8vAnBls44tX1WXAFATUsJlruyAYbZpysWV3siH6o/i3g9DCHauzodksO60bdj5NazNbjCmg==",
+ "dependencies": {
+ "abstract-leveldown": "^7.2.0",
+ "inherits": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
+ "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.34.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz",
+ "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/eslintrc": "^1.4.1",
+ "@humanwhocodes/config-array": "^0.11.8",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.18.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+ "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.1",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.5.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
+ "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
+ "engines": {
+ "node": ">= 12.9.0"
+ },
+ "peerDependencies": {
+ "express": "^4 || ^5"
+ }
+ },
+ "node_modules/express-slow-down": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/express-slow-down/-/express-slow-down-1.5.0.tgz",
+ "integrity": "sha512-GCoa2a+mf7CE7C00TIoPMbcgQSXxEVolSAbP97uHyzVy87ssp0/IDtJ/GBxjv+gnfM2R1l2QcEfYixFJK75n7w==",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/express/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/express/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/expressjs": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/expressjs/-/expressjs-1.0.1.tgz",
+ "integrity": "sha512-eFnQ5bMJxTZ29XwRJPV8ee/OURBBMS6Fm+b5rvMMEyz6u2IxPEh2SRzMZt9WvgnV+SMLmnzkALE1DnGG1HxJCw==",
+ "deprecated": "This is a typosquat on the popular Express package. This is not maintained nor is the original Express package."
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fast-redact": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz",
+ "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/finalhandler/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs-minipass/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/gauge": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
+ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
+ "signal-exit": "^3.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
+ "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/level": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz",
+ "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==",
+ "dependencies": {
+ "browser-level": "^1.0.1",
+ "classic-level": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/level"
+ }
+ },
+ "node_modules/level-concat-iterator": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz",
+ "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==",
+ "dependencies": {
+ "catering": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/level-errors": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.1.tgz",
+ "integrity": "sha512-tqTL2DxzPDzpwl0iV5+rBCv65HWbHp6eutluHNcVIftKZlQN//b6GEnZDM2CvGZvzGYMwyPtYppYnydBQd2SMQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/level-iterator-stream": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz",
+ "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==",
+ "dependencies": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/level-iterator-stream/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/level-supports": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
+ "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/level-transcoder": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz",
+ "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "module-error": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/leveldown": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz",
+ "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "abstract-leveldown": "^7.2.0",
+ "napi-macros": "~2.0.0",
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/levelup": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.1.1.tgz",
+ "integrity": "sha512-0mFCcHcEebOwsQuk00WJwjLI6oCjbBuEYdh/RaRqhjnyVlzqf41T1NnDtCedumZ56qyIh8euLFDqV1KfzTAVhg==",
+ "dependencies": {
+ "catering": "^2.0.0",
+ "deferred-leveldown": "^7.0.0",
+ "level-errors": "^3.0.1",
+ "level-iterator-stream": "^5.0.0",
+ "level-supports": "^2.0.1",
+ "queue-microtask": "^1.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/levelup/node_modules/level-supports": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz",
+ "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/long": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+ "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz",
+ "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minizlib/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/module-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
+ "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/morgan": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
+ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
+ "dependencies": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/morgan/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/morgan/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/mysql2": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.1.2.tgz",
+ "integrity": "sha512-NXz6sUvHSEOKz1jv3koSga7eb2dHrwD/mnPmqbbZzMRvjQcSpb0Eh0ectWyYt1U60CLlEbjoA3XYjjbbReRF5Q==",
+ "dependencies": {
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.6.3",
+ "long": "^5.2.1",
+ "lru-cache": "^7.14.1",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/lru-cache": {
+ "version": "7.14.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz",
+ "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
+ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
+ "dependencies": {
+ "lru-cache": "^7.14.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/named-placeholders/node_modules/lru-cache": {
+ "version": "7.14.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz",
+ "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/napi-macros": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz",
+ "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg=="
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+ "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-gyp-build": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
+ "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
+ "node_modules/nodemailer": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz",
+ "integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
+ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
+ "dependencies": {
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
+ "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w=="
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path": {
+ "version": "0.12.7",
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
+ "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
+ "dependencies": {
+ "process": "^0.11.1",
+ "util": "^0.10.3"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "node_modules/pino": {
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-8.10.0.tgz",
+ "integrity": "sha512-ODfIe+giJtQGsvNAEj5/sHHpL3TFBg161JBH4W62Hc0l0PJjsDFD1R7meLI4PZ2aoHDJznxFNShkJcaG/qJToQ==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0",
+ "fast-redact": "^3.1.1",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "v1.0.0",
+ "pino-std-serializers": "^6.0.0",
+ "process-warning": "^2.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^3.1.0",
+ "thread-stream": "^2.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz",
+ "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==",
+ "dependencies": {
+ "readable-stream": "^4.0.0",
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz",
+ "integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g=="
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/process-warning": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.1.0.tgz",
+ "integrity": "sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg=="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz",
+ "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/run-parallel-limit": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz",
+ "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz",
+ "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/send/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "node_modules/sonic-boom": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.1.tgz",
+ "integrity": "sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+ "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",
+ "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^4.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/thread-stream": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz",
+ "integrity": "sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "dependencies": {
+ "inherits": "2.0.3"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/util/node_modules/inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/api/package.json b/api/package.json
new file mode 100644
index 0000000..e5be1a5
--- /dev/null
+++ b/api/package.json
@@ -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"
+ }
+}
diff --git a/api/routes/airlines.js b/api/routes/airlines.js
new file mode 100644
index 0000000..dae0db1
--- /dev/null
+++ b/api/routes/airlines.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/airplanes.js b/api/routes/airplanes.js
new file mode 100644
index 0000000..84568f0
--- /dev/null
+++ b/api/routes/airplanes.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/airports.js b/api/routes/airports.js
new file mode 100644
index 0000000..652ad5f
--- /dev/null
+++ b/api/routes/airports.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/flights.js b/api/routes/flights.js
new file mode 100644
index 0000000..bb57fb7
--- /dev/null
+++ b/api/routes/flights.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/pilots.js b/api/routes/pilots.js
new file mode 100644
index 0000000..e621107
--- /dev/null
+++ b/api/routes/pilots.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/seats.js b/api/routes/seats.js
new file mode 100644
index 0000000..4623ab2
--- /dev/null
+++ b/api/routes/seats.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/test.js b/api/routes/test.js
new file mode 100644
index 0000000..5316db7
--- /dev/null
+++ b/api/routes/test.js
@@ -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;
\ No newline at end of file
diff --git a/api/routes/users.js b/api/routes/users.js
new file mode 100644
index 0000000..d5a00e5
--- /dev/null
+++ b/api/routes/users.js
@@ -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;
\ No newline at end of file
diff --git a/api/tokensDB/CURRENT b/api/tokensDB/CURRENT
new file mode 100644
index 0000000..eea9b0f
--- /dev/null
+++ b/api/tokensDB/CURRENT
@@ -0,0 +1 @@
+MANIFEST-000034
diff --git a/api/tokensDB/LOCK b/api/tokensDB/LOCK
new file mode 100644
index 0000000..e69de29
diff --git a/api/tokensDB/LOG b/api/tokensDB/LOG
new file mode 100644
index 0000000..12e41b5
--- /dev/null
+++ b/api/tokensDB/LOG
@@ -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
diff --git a/api/tokensDB/LOG.old b/api/tokensDB/LOG.old
new file mode 100644
index 0000000..c113ed9
--- /dev/null
+++ b/api/tokensDB/LOG.old
@@ -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
diff --git a/api/tokensDB/MANIFEST-000034 b/api/tokensDB/MANIFEST-000034
new file mode 100644
index 0000000..c6fc8e6
Binary files /dev/null and b/api/tokensDB/MANIFEST-000034 differ
diff --git a/db.sql b/db.sql
new file mode 100644
index 0000000..d596401
--- /dev/null
+++ b/db.sql
@@ -0,0 +1,274 @@
+SET default_storage_engine = InnoDB;
+DROP DATABASE IF EXISTS `airjet`;
+CREATE DATABASE IF NOT EXISTS `airjet`
+ CHARACTER SET utf8mb4
+ COLLATE utf8mb4_unicode_ci;
+
+DROP USER IF EXISTS 'airjet';
+CREATE USER 'airjet'@'%' IDENTIFIED BY 'kS7Qmv1eceRJ7584ch0TL8jL8';
+GRANT ALL PRIVILEGES ON airjet.* TO 'airjet'@'%';
+
+USE `airjet`;
+
+CREATE TABLE pilots (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ first_name VARCHAR(255) NOT NULL,
+ last_name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) NOT NULL,
+ phone VARCHAR(20) NOT NULL,
+ license_number VARCHAR(255) NOT NULL,
+ license_expiry DATE NOT NULL,
+ salary DECIMAL(10, 2) NOT NULL DEFAULT 0.0,
+ status VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+CREATE TABLE airplanes (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL,
+ type VARCHAR(255) NOT NULL,
+ manufacturer VARCHAR(255) NOT NULL,
+ capacity INT NOT NULL,
+ status VARCHAR(255) NOT NULL,
+ location VARCHAR(255),
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+CREATE TABLE airlines (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL,
+ code VARCHAR(2) NOT NULL,
+ logo VARCHAR(255),
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+CREATE TABLE airports (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL,
+ code VARCHAR(3) NOT NULL,
+ city VARCHAR(255) NOT NULL,
+ country VARCHAR(255) NOT NULL,
+ latitude FLOAT NOT NULL,
+ longitude FLOAT NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+CREATE TABLE flights (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ airline_id INT UNSIGNED NOT NULL,
+ pilot_id INT UNSIGNED NOT NULL,
+ flight_no VARCHAR(10) NOT NULL,
+ origin_id INT UNSIGNED NOT NULL,
+ destination_id INT UNSIGNED NOT NULL,
+ departure_time DATETIME NOT NULL,
+ arrival_time DATETIME NOT NULL,
+ duration_minutes INT NOT NULL,
+ price_economy DECIMAL(10,2) NOT NULL,
+ price_business DECIMAL(10,2) NOT NULL,
+ price_first_class DECIMAL(10,2) NOT NULL,
+ status VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT fl_airline_id
+ FOREIGN KEY (airline_id)
+ REFERENCES airlines(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ CONSTRAINT fl_pilot_id
+ FOREIGN KEY (pilot_id)
+ REFERENCES pilots(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ CONSTRAINT fl_origin_id
+ FOREIGN KEY (origin_id)
+ REFERENCES airports(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ CONSTRAINT fl_destination_id
+ FOREIGN KEY (destination_id)
+ REFERENCES airports(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE user_types (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE INDEX ut_name_idx (name)
+) ENGINE=InnoDB;
+
+INSERT INTO user_types (name) VALUES ('unverified'), ('customer'), ('support'), ('pilote'), ('staff'), ('admin');
+
+CREATE TABLE permissions (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE INDEX p_name_idx (name)
+) ENGINE=InnoDB;
+
+INSERT INTO permissions (name) VALUES
+('view_users'), ('add_users'), ('edit_users'), ('delete_users'),
+('view_flights'), ('add_flights'), ('edit_flights'), ('delete_flights'),
+('view_airlines'), ('add_airlines'), ('edit_airlines'), ('delete_airlines'),
+('view_airplanes'), ('add_airplanes'), ('edit_airplanes'), ('delete_airplanes'),
+('view_airports'), ('add_airports'), ('edit_airports'), ('delete_airports'),
+('view_seats'), ('add_seats'), ('edit_seats'), ('delete_seats'),
+('view_pilots'), ('add_pilots'), ('edit_pilots'), ('delete_pilots');
+
+CREATE TABLE user_type_permissions (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ user_type_id INT UNSIGNED NOT NULL,
+ permission_id INT UNSIGNED NOT NULL,
+ PRIMARY KEY (id),
+ CONSTRAINT utp_user_type_id
+ FOREIGN KEY (user_type_id)
+ REFERENCES user_types(id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ CONSTRAINT utp_permission_id
+ FOREIGN KEY (permission_id)
+ REFERENCES permissions(id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ UNIQUE INDEX utp_user_type_permission_idx (user_type_id, permission_id)
+) ENGINE=InnoDB;
+
+CREATE TABLE users (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ first_name VARCHAR(64) NOT NULL,
+ last_name VARCHAR(64) NOT NULL,
+ username VARCHAR(64) NOT NULL,
+ password VARCHAR(255) NOT NULL,
+ email VARCHAR(128) NOT NULL,
+ phone VARCHAR(32) DEFAULT 'None',
+ user_type_id INT UNSIGNED NOT NULL,
+ is_banned BOOLEAN NOT NULL DEFAULT 0,
+ PRIMARY KEY (id),
+ CONSTRAINT u_user_type_id
+ FOREIGN KEY (user_type_id)
+ REFERENCES user_types(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ INDEX ur_user_type_idx (user_type_id)
+) ENGINE=InnoDB;
+
+CREATE TABLE user_email_verifications (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ user_id INT UNSIGNED NOT NULL,
+ verification_code VARCHAR(255),
+ type VARCHAR(32) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT mv_user_id
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ INDEX mv_user_id_idx (user_id)
+) ENGINE=InnoDB;
+
+CREATE TABLE seats (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ user_id INT UNSIGNED,
+ flight_id INT UNSIGNED NOT NULL,
+ place_no INT UNSIGNED NOT NULL,
+ class VARCHAR(32) NOT NULL,
+ bought_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT ps_user_id
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ CONSTRAINT ps_flight_id
+ FOREIGN KEY (flight_id)
+ REFERENCES flights(id)
+ ON DELETE RESTRICT
+ ON UPDATE CASCADE,
+ INDEX ps_user_id_idx (user_id),
+ INDEX ps_flight_id_idx (flight_id)
+) ENGINE=InnoDB;
+
+INSERT INTO pilots (id, first_name, last_name, email, phone, license_number, license_expiry, salary, status) VALUES
+(1, 'John', 'Doe', 'john.doe@example.com', '555-1234', '1234-5678', '2025-06-30', 75000.00, 'Active'),
+(2, 'Jane', 'Smith', 'jane.smith@example.com', '555-5678', '9876-5432', '2024-12-31', 80000.00, 'Active'),
+(3, 'Bob', 'Johnson', 'bob.johnson@example.com', '555-7890', '3456-7890', '2023-09-30', 70000.00, 'Inactive'),
+(4, 'Alice', 'Lee', 'alice.lee@example.com', '555-2345', '5678-1234', '2024-03-31', 85000.00, 'Active'),
+(5, 'David', 'Nguyen', 'david.nguyen@example.com', '555-5678', '1234-5678', '2023-12-31', 75000.00, 'Active'),
+(6, 'Maria', 'Garcia', 'maria.garcia@example.com', '555-7890', '9876-5432', '2024-06-30', 70000.00, 'Active'),
+(7, 'Mohammed', 'Ali', 'mohammed.ali@example.com', '555-2345', '3456-7890', '2025-09-30', 85000.00, 'Inactive'),
+(8, 'Sofia', 'Martinez', 'sofia.martinez@example.com', '555-3456', '5678-1234', '2022-03-31', 80000.00, 'Active');
+
+INSERT INTO airplanes (id, name, type, manufacturer, capacity, status, location) VALUES
+(1, 'Boeing 747', 'Jet', 'Boeing', 660, 'Active', 'Los Angeles'),
+(2, 'Airbus A320', 'Jet', 'Airbus', 180, 'Active', 'New York'),
+(3, 'Embraer E175', 'Jet', 'Embraer', 78, 'Inactive', 'Miami'),
+(4, 'Boeing 737', 'Jet', 'Boeing', 160, 'Active', 'Chicago'),
+(5, 'Airbus A330', 'Jet', 'Airbus', 440, 'Active', 'Paris'),
+(6, 'Bombardier CRJ900', 'Jet', 'Bombardier', 88, 'Active', 'Montreal'),
+(7, 'Boeing 777', 'Jet', 'Boeing', 396, 'Inactive', 'London'),
+(8, 'Airbus A380', 'Jet', 'Airbus', 853, 'Active', 'Dubai'),
+(9, 'Embraer E190', 'Jet', 'Embraer', 114, 'Active', 'Buenos Aires'),
+(10, 'Boeing 787', 'Jet', 'Boeing', 335, 'Active', 'Tokyo'),
+(11, 'Boeing 747-8', 'Jet', 'Boeing', 605, 'Active', 'Hong Kong'),
+(12, 'Airbus A350', 'Jet', 'Airbus', 440, 'Active', 'Dublin'),
+(13, 'Embraer E195', 'Jet', 'Embraer', 124, 'Inactive', 'Rio de Janeiro'),
+(14, 'Boeing 737 MAX', 'Jet', 'Boeing', 230, 'Active', 'Seattle'),
+(15, 'Airbus A321', 'Jet', 'Airbus', 236, 'Active', 'Shanghai');
+
+INSERT INTO airlines (id, name, code, logo) VALUES
+(1, 'Delta Air Lines', 'DL', 'https://www.delta.com/content/dam/delta-com/brand-icons/Delta_Icon_blue_72.png'),
+(2, 'American Airlines', 'AA', 'https://www.aa.com/content/dam/aa-com/logo-web/aa-logo-blue-and-red-horz.png'),
+(3, 'United Airlines', 'UA', 'https://www.united.com/web/en-US/content/images/global/header/header-united-logo.png'),
+(4, 'Southwest Airlines', 'WN', 'https://www.southwest.com/etc/designs/southwest/v2/images/swa-logo--mod.svg'),
+(5, 'Alaska Airlines', 'AS', 'https://www.alaskaair.com/content/dam/alaskaair/logo/2016/alaska-airlines-horiz-white-blue-1x.png'),
+(6, 'JetBlue Airways', 'B6', 'https://www.jetblue.com/etc/designs/jetblue/clientlibs/dist/images/svg/jetblue-logo.svg'),
+(7, 'Spirit Airlines', 'NK', 'https://www.spirit.com/images/spirit-logo.png'),
+(8, 'Frontier Airlines', 'F9', 'https://www.flyfrontier.com/etc/designs/frontier-airlines/clientlibs/dist/images/f9-logo-horz.svg'),
+(9, 'Air France', 'AF', 'https://www.airfrance.com/etc/designs/airfrance/clientlibs/dist/images/global/logo/airfrance-logo-blue.svg'),
+(10, 'Transavia France', 'TO', 'https://www.transavia.com/content/dam/airlines/tv/fra/fr/common/img/logo.svg'),
+(11, 'EasyJet France', 'U2', 'https://www.easyjet.com/etc/designs/easyjet/clientlibs/dist/images/logo.svg'),
+(12, 'Corsair International', 'SS', 'https://www.corsair.fr/etc/designs/corsair/clientlibs/dist/images/logo.svg'),
+(13, 'XL Airways France', 'SE', 'https://www.xl.com/assets/images/XL-logo.png'),
+(14, 'Aigle Azur', 'ZI', 'https://www.aigle-azur.com/site/sites/all/themes/aigle-azur/images/logo-aigle-azur-160x80.png');
+
+INSERT INTO airports (id, name, code, city, country, latitude, longitude) VALUES
+(1, 'John F. Kennedy International Airport', 'JFK', 'New York', 'United States', 40.6413, -73.7781),
+(2, 'Los Angeles International Airport', 'LAX', 'Los Angeles', 'United States', 33.9416, -118.4085),
+(3, 'London Heathrow Airport', 'LHR', 'London', 'United Kingdom', 51.4700, -0.4543),
+(4, 'Paris-Charles de Gaulle Airport', 'CDG', 'Paris', 'France', 49.0097, 2.5479),
+(5, 'Tokyo Haneda Airport', 'HND', 'Tokyo', 'Japan', 35.5532, 139.7818),
+(6, 'Dubai International Airport', 'DXB', 'Dubai', 'United Arab Emirates', 25.2528, 55.3644),
+(7, 'Sydney Kingsford Smith Airport', 'SYD', 'Sydney', 'Australia', -33.9461, 151.1772),
+(8, 'São Paulo-Guarulhos International Airport', 'GRU', 'São Paulo', 'Brazil', -23.4356, -46.4731),
+(9, 'Jomo Kenyatta International Airport', 'NBO', 'Nairobi', 'Kenya', -1.3192, 36.9258),
+(10, 'Cairo International Airport', 'CAI', 'Cairo', 'Egypt', 30.1111, 31.4139),
+(11, 'Paris Orly Airport', 'ORY', 'Paris', 'France', 48.7262, 2.3650),
+(12, 'Nice Côte d''Azur Airport', 'NCE', 'Nice', 'France', 43.6584, 7.2157),
+(13, 'Marseille Provence Airport', 'MRS', 'Marseille', 'France', 43.4393, 5.2214),
+(14, 'Lyon-Saint-Exupéry Airport', 'LYS', 'Lyon', 'France', 45.7216, 5.0790),
+(15, 'Bordeaux-Mérignac Airport', 'BOD', 'Bordeaux', 'France', 44.8283, -0.7156),
+(16, 'Toulouse-Blagnac Airport', 'TLS', 'Toulouse', 'France', 43.6356, 1.3678),
+(17, 'Nantes Atlantique Airport', 'NTE', 'Nantes', 'France', 47.1567, -1.6114),
+(18, 'Strasbourg Airport', 'SXB', 'Strasbourg', 'France', 48.5442, 7.6277),
+(19, 'Lille Airport', 'LIL', 'Lille', 'France', 50.5633, 3.0897),
+(20, 'Brest Bretagne Airport', 'BES', 'Brest', 'France', 48.4472, -4.4228),
+(21, 'Vienna International Airport', 'VIE', 'Vienna', 'Austria', 48.1197, 16.5667),
+(22, 'Zürich Airport', 'ZRH', 'Zürich', 'Switzerland', 47.4502, 8.5618),
+(23, 'Amsterdam Airport Schiphol', 'AMS', 'Amsterdam', 'Netherlands', 52.3081, 4.7642),
+(24, 'Frankfurt Airport', 'FRA', 'Frankfurt', 'Germany', 50.0379, 8.5622),
+(25, 'Barcelona-El Prat Airport', 'BCN', 'Barcelona', 'Spain', 41.2974, 2.0833),
+(26, 'Adolfo Suárez Madrid-Barajas Airport', 'MAD', 'Madrid', 'Spain', 40.4936, -3.5668),
+(27, 'Leonardo da Vinci-Fiumicino Airport', 'FCO', 'Rome', 'Italy', 41.8003, 12.2388),
+(28, 'Stockholm Arlanda Airport', 'ARN', 'Stockholm', 'Sweden', 59.6519, 17.9186);
+
+INSERT INTO flights (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) VALUES
+(1, 1, 1, 'BA123', 1, 2, '2023-03-15 08:00:00', '2023-03-15 10:00:00', 120, 100, 200, 300, 'scheduled'),
+(2, 2, 2, 'AF456', 2, 3, '2023-03-16 14:00:00', '2023-03-16 16:30:00', 150, 80, 150, 250, 'scheduled'),
+(3, 3, 3, 'LH789', 4, 5, '2023-03-17 09:30:00', '2023-03-17 12:00:00', 150, 90, 170, 280, 'scheduled'),
+(4, 1, 4, 'BA345', 2, 5, '2023-03-18 07:00:00', '2023-03-18 10:00:00', 180, 120, 240, 400, 'scheduled'),
+(5, 4, 5, 'EZY678', 3, 1, '2023-03-19 11:00:00', '2023-03-19 13:00:00', 120, 60, 120, 200, 'scheduled'),
+(6, 2, 6, 'AF901', 5, 6, '2023-03-20 16:30:00', '2023-03-20 19:00:00', 150, 100, 200, 350, 'scheduled');
+
+INSERT INTO `user_type_permissions` (`user_type_id`, `permission_id`) VALUES
+('6', '1'),('6', '2'),('6', '3'),('6', '4'),('6', '5'),('6', '6'),('6', '7'),('6', '8'),('6', '9'),('6', '10'),('6', '11'),('6', '12'),('6', '13'),('6', '14'),('6', '15'),('6', '16'),('6', '17'),('6', '18'),('6', '19'),('6', '20'),('6', '21'),('6', '22'),('6', '23'),('6', '24'),('6', '25'),('6', '26'),('6', '27'),('6', '28');
\ No newline at end of file
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..91bb714
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,61 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name aostia.me *.aostia.me;
+
+ include snippets/letsencrypt.conf;
+
+ return 301 https://$host$request_uri;
+}
+
+server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ server_name www.aostia.me;
+
+ ssl_certificate /etc/letsencrypt/live/aostia.me/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/aostia.me/privkey.pem;
+ ssl_trusted_certificate /etc/letsencrypt/live/aostia.me/chain.pem;
+ include snippets/ssl.conf;
+ include snippets/letsencrypt.conf;
+
+ return 301 https://aostia.me$request_uri;
+}
+
+server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ server_name aostia.me;
+
+ ssl_certificate /etc/letsencrypt/live/aostia.me/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/aostia.me/privkey.pem;
+ ssl_trusted_certificate /etc/letsencrypt/live/aostia.me/chain.pem;
+ include snippets/ssl.conf;
+ include snippets/letsencrypt.conf;
+
+ root /var/www/aostia.me/html;
+
+ index index.php index.html;
+
+ access_log /var/log/nginx/aostia.me.access.log;
+ error_log /var/log/nginx/aostia.me.error.log;
+
+ add_header X-XSS-Protection "1; mode=block";
+ add_header Referrer-Policy "strict-origin-when-cross-origin";
+ add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
+ add_header Content-Security-Policy "default-src 'self'; img-src 'self'; font-src 'self' ka-f.fontawesome.com fonts.gstatic.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; script-src 'self' 'unsafe-inline'; connect-src 'self' ka-f.fontawesome.com";
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+ location ~ \.php$ {
+ include snippets/fastcgi-php.conf;
+ fastcgi_pass unix:run/php/php7.4-fpm.sock;
+ }
+ location /api {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_pass http://localhost:1109;
+ }
+}
\ No newline at end of file
diff --git a/webapp/html/dash.html b/webapp/html/dash.html
new file mode 100644
index 0000000..83ddf60
--- /dev/null
+++ b/webapp/html/dash.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+