diff --git a/.idea/gimm285-api-project.iml b/.idea/gimm285-api-project.iml index 24643cc..a6b321b 100644 --- a/.idea/gimm285-api-project.iml +++ b/.idea/gimm285-api-project.iml @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..b843835 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/css/main.css b/css/main.css index ac55fc9..80ed2a6 100644 --- a/css/main.css +++ b/css/main.css @@ -114,6 +114,11 @@ body, html { margin: 0px; background-color: #171a21; transition: all 0.4s ease 0s; + color: #d3dade; +} + +p{ + margin-bottom: 0; } input, select, textarea { @@ -137,6 +142,30 @@ input, select, textarea { box-shadow: #000000 3px 3px 5px; } +#backButton { + border-radius: 5px; + border:0; + min-width: 150px; + min-height: 50px; + background-image: linear-gradient(#316282, #22445b); + color: #ffffff; +} + +#backButton:hover { + background-image: linear-gradient(#b3d4fc, #a1c1ec); + box-shadow: #000000 3px 3px 5px; +} + +#outputMessage { + background-color: #4c6b22; + color: #b3d4fc; + min-height: 50px; +} + +.tableCoverArt { + max-height: 50px; +} + table{ margin-bottom: 50px; } diff --git a/edit.html b/edit.html new file mode 100644 index 0000000..b743187 --- /dev/null +++ b/edit.html @@ -0,0 +1,10 @@ + + + + + Edit Game + + + + + \ No newline at end of file diff --git a/game.html b/game.html new file mode 100644 index 0000000..8642839 --- /dev/null +++ b/game.html @@ -0,0 +1,78 @@ + + + + + Pressurized Gas -- Game Page + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 82b59ef..f017354 100644 --- a/index.html +++ b/index.html @@ -2,260 +2,273 @@ - - Kaj Forney -- GIMM 285 CRUD API Project - - + + Kaj Forney -- GIMM 285 CRUD API Project + + - - + + - - - + + + - + - -
+
-
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
-
+
- Windows - +
- MacOS - + +
+
+
+ +
+
+
- Linux - +
-
+
-
-
-
+
+
+ +
+
+ +
+
+
-
-
- +
+
+ +
+
+ +
+
-
- -
-
-
-
-
- +
+
+ +
+
+ +
+
-
- -
-
-
-
-
- +
+
+ +
+
+
+
+ Windows + +
+
+ MacOS + +
+
+ Linux + +
+
+
+
-
-
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
-
+
-
+

Page created by Kaj Forney, as work for class (GIMM 285).

-
+
- - + + - + diff --git a/insert.html b/insert.html index 0a326b2..affac50 100644 --- a/insert.html +++ b/insert.html @@ -39,10 +39,15 @@
- +
@@ -50,10 +55,15 @@
- +
@@ -94,10 +104,22 @@
- +
- + +
+
+
+ +
+
+ +

(maximum 300 characters)

+
+
+
@@ -105,6 +127,7 @@
+

(supports Markdown)

@@ -120,13 +143,18 @@
+
+
+
+
+
-

Page created by Kaj Forney, as work for class (GIMM 285).

+

All example data used is originally from Steam.

@@ -137,58 +165,116 @@ + + + + + + + diff --git a/insertPublisher.html b/insertPublisher.html new file mode 100644 index 0000000..afe6f7a --- /dev/null +++ b/insertPublisher.html @@ -0,0 +1,148 @@ + + + + + + Kaj Forney -- GIMM 285 CRUD API Project + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +

(maximum 250 characters)

+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ + +
+ +
+

Page created by Kaj Forney, as work for class (GIMM 285).

+

All example data used is originally from Steam.

+
+ + + + + + + + + diff --git a/server/index.js b/server/index.js index 504428a..9a77cbe 100644 --- a/server/index.js +++ b/server/index.js @@ -3,13 +3,25 @@ const express = require('express'); const multer = require('multer'); const mysql = require('mysql2'); const games = require('./model/games'); +const developers = require('./model/developers'); +const publishers = require('./model/publishers'); const {request, response} = require("express"); +const {check, checkSchema, validationResult} = require("express-validator"); //Setup defaults for script const app = express(); -const upload = multer(); +const upload = multer({ + dest: 'public/images/', + //Validation for file upload + fileFilter: (request, file, callback) => { + const allowedFileMimeTypes = ["image/png", "image/jpg", "image/jpeg"]; + callback(null, allowedFileMimeTypes.includes(file.mimetype)); + } +}); const port = 8787; +app.use(express.static('public')); + //The * in app.* needs to match the method type of the request app.get('/games', upload.none(), async (request, response) => { @@ -28,20 +40,202 @@ app.get('/games', upload.none(), .json({'data': result}); }); -app.post('/games', upload.none(), +app.get('/games/:id', upload.none(), + async (request, response) => { + console.log("Getting data for game " + request.params.id); + let result = {}; + try { + result = await games.getGame(request.params.id); + } catch (error) { + return response + .status(500) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server!'}) + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({'data': result}) + }); + +app.get('/developers', upload.none(), async (request, response) => { let result = {}; try { - result = await games.addNewGame(request.query); + result = await developers.getDeveloperNames(); } catch (error) { return response .status(500) //Error code when something goes wrong with the server .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error .json({message: 'Something went wrong with the server.'}); } + //Default response object response .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error - .json({message: 'Game added successfully!'}); + .json({'data': result}); + }); + +app.get('/developers/:id', upload.none(), + async (request, response) => { + console.log("Getting data for developer " + request.params.id); + let result = {}; + try { + result = await developers.getDeveloper(request.params.id); + } catch (error) { + return response + .status(500) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server!'}) + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({'data': result}) + }); + +app.get('/publishers', upload.none(), + async (request, response) => { + let result = {}; + try { + result = await publishers.getPublisherNames(); + } catch (error) { + return response + .status(500) //Error code when something goes wrong with the server + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server.'}); + } + //Default response object + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({'data': result}); + }); + +app.get('/publishers/:id', upload.none(), + async (request, response) => { + console.log("Getting data for publisher " + request.params.id); + let result = {}; + try { + result = await publishers.getPublisher(request.params.id); + } catch (error) { + return response + .status(500) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server!'}) + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({'data': result}) + }); + +app.post('/games', upload.single('coverArt'), + check('title', 'You must enter a title.').isLength({min: 3}), + check('releaseDate', 'You must select a release date.').isDate(["YYYY-MM-DD"]), + check('win', 'Invalid Windows option.').isIn([0, 1]), + check('mac', 'Invalid MacOS option.').isIn([0, 1]), + check('linux', 'Invalid Linux option.').isIn([0, 1]), + check('shortDescription', 'You must enter a description'), + checkSchema({ + 'coverArt': { + custom: { + options: (value, {req, path}) => !!req.files[path], + errorMessage: 'Please upload an image file.', + }, + }, + }), + + async (request, response) => { + + const errors = validationResult(request); + if(!errors.isEmpty()) { + return response + .status(400) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({ + message: 'Request fields or files are invalid.', + errors: errors.array(), + }); + } else { + console.log(request.query); + let result = {}; + try { + result = await games.addNewGame(request.query, request.file.filename); + } catch (error) { + console.log(error); + return response + .status(500) //Error code when something goes wrong with the server + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server.'}); + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Game added successfully!'}); + } + }); + +app.post('/developers', upload.none(), + check('name', 'You must enter a name.').isLength({min: 3}), + check('description', 'You must enter a description').isLength({min:5}), + check('homepage', 'You must enter a homepage URL.').isURL(), + + async (request, response) => { + + const errors = validationResult(request); + if(!errors.isEmpty()) { + return response + .status(400) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({ + message: 'Request fields or files are invalid.', + errors: errors.array(), + }); + } else { + console.log(request.query); + let result = {}; + try { + result = await developers.addNewDeveloper(request.query); + } catch (error) { + console.log(error); + return response + .status(500) //Error code when something goes wrong with the server + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server.'}); + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Developer added successfully!'}); + } + }); + +app.post('/publishers', upload.none(), + check('name', 'You must enter a name.').isLength({min: 3}), + check('description', 'You must enter a description').isLength({min:5}), + check('homepage', 'You must enter a homepage URL.').isURL(), + + async (request, response) => { + + const errors = validationResult(request); + if(!errors.isEmpty()) { + return response + .status(400) + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({ + message: 'Request fields or files are invalid.', + errors: errors.array(), + }); + } else { + console.log(request.query); + let result = {}; + try { + result = await publishers.addNewPublisher(request.query); + } catch (error) { + console.log(error); + return response + .status(500) //Error code when something goes wrong with the server + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Something went wrong with the server.'}); + } + response + .setHeader('Access-Control-Allow-Origin', '*') //Prevent CORS error + .json({message: 'Publisher added successfully!'}); + } }); app.listen(port, () => { diff --git a/server/model/connection.js b/server/model/connection.js index eb22828..c10781d 100644 --- a/server/model/connection.js +++ b/server/model/connection.js @@ -4,7 +4,9 @@ const credentials = require('./creds.json'); let connection = null; async function query(sql, params) { + console.log(params); if (null === connection) { + console.log("Establishing connection to database!"); connection = await mysql.createConnection(credentials); } diff --git a/server/model/developers.js b/server/model/developers.js index 4a06e06..b1bd1b3 100644 --- a/server/model/developers.js +++ b/server/model/developers.js @@ -1 +1,41 @@ const connection = require('./connection'); + +async function getDeveloperNames() { + let selectSql = `SELECT id,name FROM developers` + + return await connection.query(selectSql); +} + +async function getDeveloper(id) { + + let selectSql = `SELECT name,homepage,description FROM developers`, + whereStatements = [], + queryParameters = []; + + if (typeof id !== 'undefined' && id.length > 0) { + whereStatements.push("id LIKE ?"); + queryParameters.push('%' + id + '%'); + } + + //Dynamically add WHERE expressions to SELECT statements if needed + if (whereStatements.length > 0) { + selectSql = selectSql + ' WHERE ' + whereStatements.join(' AND '); + } + + return await connection.query(selectSql, queryParameters); +} + +async function addNewDeveloper(formInput) { + let insertSql = `INSERT INTO developers (name, description, homepage) VALUES (?, ?, ?)`; + let queryParameters = [formInput.name, + formInput.description, + formInput.homepage]; + + return await connection.query(insertSql, queryParameters); +} + +module.exports = { + getDeveloperNames, + getDeveloper, + addNewDeveloper +} \ No newline at end of file diff --git a/server/model/games.js b/server/model/games.js index 8eb2f85..0b1e455 100644 --- a/server/model/games.js +++ b/server/model/games.js @@ -1,7 +1,8 @@ const connection = require('./connection'); +const parseText = require('./parseText'); async function getAllGames(parameters = {}) { - let selectSql = `SELECT + let selectSql = `SELECT m.id AS appID, m.game_title AS title, d.name AS developer, @@ -10,91 +11,142 @@ async function getAllGames(parameters = {}) { m.short_description AS summary, m.win_supported AS win, m.mac_supported AS mac, - m.linux_supported AS linux + m.linux_supported AS linux, + m.cover_art_filename AS coverArt FROM games_master_table m INNER JOIN developers d ON m.developer_id = d.id INNER JOIN publishers p ON m.publisher_id = p.id`, - whereStatements = [], - orderByStatements = [], - queryParameters = []; + whereStatements = [], + orderByStatements = [], + queryParameters = []; - if (typeof parameters.win !== 'undefined' && parseInt(parameters.win) === 1) { - whereStatements.push("m.win_supported = 1"); - } - - if (typeof parameters.mac !== 'undefined' && parseInt(parameters.mac) === 1) { - whereStatements.push("m.mac_supported = 1"); - } - - if (typeof parameters.linux !== 'undefined' && parseInt(parameters.linux) === 1) { - whereStatements.push("m.linux_supported = 1"); - } - - if (typeof parameters.appID !== 'undefined' && parameters.appID.length > 0) { - const appID = parameters.appID; - whereStatements.push("m.id LIKE ?"); - queryParameters.push('%' + appID + '%'); - } - - if (typeof parameters.title !== 'undefined' && parameters.title.length > 0) { - const title = parameters.title; - whereStatements.push("m.game_title LIKE ?"); - queryParameters.push('%' + title + '%'); - } - - if (typeof parameters.developer !== 'undefined' && parameters.developer.length > 0) { - const developer = parameters.developer; - whereStatements.push("d.name LIKE ?"); - queryParameters.push('%' + developer + '%'); - } - - if (typeof parameters.publisher !== 'undefined' && parameters.publisher.length > 0) { - const publisher = parameters.publisher; - whereStatements.push("p.name LIKE ?"); - queryParameters.push('%' + publisher + '%'); - } - - if (typeof parameters.releaseDate !== 'undefined' && parameters.releaseDate.length > 0) { - const releaseDate = parameters.releaseDate; - whereStatements.push("m.release_date LIKE ?"); - queryParameters.push('%' + releaseDate + '%'); - } - - if (typeof parameters.orderBy !== 'undefined') { - const sort = parameters.orderBy; - if (sort === 'ASC') { - orderByStatements.push('m.id ASC'); - } else if (sort === 'DESC') { - orderByStatements.push('m.id DESC') + if (typeof parameters.win !== 'undefined' && parseInt(parameters.win) === 1) { + whereStatements.push("m.win_supported = 1"); } - } - //Dynamically add WHERE expressions to SELECT statements if needed - if (whereStatements.length > 0) { - selectSql = selectSql + ' WHERE ' + whereStatements.join(' AND '); - } + if (typeof parameters.mac !== 'undefined' && parseInt(parameters.mac) === 1) { + whereStatements.push("m.mac_supported = 1"); + } - //Dynamically add ORDER BY expressions to SELECT statements if needed - if (orderByStatements.length > 0) { - selectSql = selectSql + ' ORDER BY ' + orderByStatements.join(', '); - } + if (typeof parameters.linux !== 'undefined' && parseInt(parameters.linux) === 1) { + whereStatements.push("m.linux_supported = 1"); + } - //Dynamically add ORDER BY expressions to SELECT statements if needed - if (typeof parameters.limitNumber !== 'undefined' && parameters.limitNumber > 0 && parameters.limitNumber < 6) { - selectSql = selectSql + ' LIMIT ' + parameters.limitNumber; - } + if (typeof parameters.appID !== 'undefined' && parameters.appID.length > 0) { + const appID = parameters.appID; + whereStatements.push("m.id LIKE ?"); + queryParameters.push('%' + appID + '%'); + } - return await connection.query(selectSql, queryParameters); + if (typeof parameters.title !== 'undefined' && parameters.title.length > 0) { + const title = parameters.title; + whereStatements.push("m.game_title LIKE ?"); + queryParameters.push('%' + title + '%'); + } + + if (typeof parameters.developer !== 'undefined' && parameters.developer.length > 0) { + const developer = parameters.developer; + whereStatements.push("d.name LIKE ?"); + queryParameters.push('%' + developer + '%'); + } + + if (typeof parameters.publisher !== 'undefined' && parameters.publisher.length > 0) { + const publisher = parameters.publisher; + whereStatements.push("p.name LIKE ?"); + queryParameters.push('%' + publisher + '%'); + } + + if (typeof parameters.releaseDate !== 'undefined' && parameters.releaseDate.length > 0) { + const releaseDate = parameters.releaseDate; + whereStatements.push("m.release_date LIKE ?"); + queryParameters.push('%' + releaseDate + '%'); + } + + if (typeof parameters.orderBy !== 'undefined') { + const sort = parameters.orderBy; + if (sort === 'ASC') { + orderByStatements.push('m.id ASC'); + } else if (sort === 'DESC') { + orderByStatements.push('m.id DESC') + } + } + + //Dynamically add WHERE expressions to SELECT statements if needed + if (whereStatements.length > 0) { + selectSql = selectSql + ' WHERE ' + whereStatements.join(' AND '); + } + + //Dynamically add ORDER BY expressions to SELECT statements if needed + if (orderByStatements.length > 0) { + selectSql = selectSql + ' ORDER BY ' + orderByStatements.join(', '); + } + + //Dynamically add ORDER BY expressions to SELECT statements if needed + if (typeof parameters.limitNumber !== 'undefined' && parameters.limitNumber > 0 && parameters.limitNumber < 6) { + selectSql = selectSql + ' LIMIT ' + parameters.limitNumber; + } + + return await connection.query(selectSql, queryParameters); } -async function addNewGame() { - let insertSql = `INSERT INTO games_master_table`; - let queryParameters = []; +async function getGame(id) { + let selectSql = `SELECT + m.id AS appID, + m.game_title AS title, + d.name AS developer, + p.name AS publisher, + m.release_date AS releaseDate, + m.short_description AS summary, + m.long_description AS longDescription, + m.win_supported AS win, + m.mac_supported AS mac, + m.linux_supported AS linux, + m.cover_art_filename AS coverArt + FROM games_master_table m + INNER JOIN developers d ON m.developer_id = d.id + INNER JOIN publishers p ON m.publisher_id = p.id`, + whereStatements = [], + queryParameters = []; - return await connection.query(insertSql, queryParameters); + if (typeof id !== 'undefined' && id.length > 0) { + whereStatements.push("m.id LIKE ?"); + queryParameters.push('%' + id + '%'); + } + + //Dynamically add WHERE expressions to SELECT statements if needed + if (whereStatements.length > 0) { + selectSql = selectSql + ' WHERE ' + whereStatements.join(' AND '); + } + + return await connection.query(selectSql, queryParameters); +} + +async function addNewGame(formInput, imagePath) { + + console.log("Image: " + imagePath); + + let parsedDescription = await parseText.parseMarkdown(formInput.longDescription); + + let insertSql = `INSERT INTO games_master_table (game_title, developer_id, publisher_id, release_date, + win_supported, mac_supported, linux_supported, + short_description, long_description, cover_art_filename) VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; + let queryParameters = [formInput.title, + formInput.developerID, + formInput.publisherID, + formInput.releaseDate, + formInput.win, + formInput.mac, + formInput.linux, + formInput.shortDescription, + parsedDescription, + imagePath]; + + return await connection.query(insertSql, queryParameters); } module.exports = { - getAllGames, - addNewGame + getAllGames, + getGame, + addNewGame } diff --git a/server/model/parseText.js b/server/model/parseText.js new file mode 100644 index 0000000..93ce3dd --- /dev/null +++ b/server/model/parseText.js @@ -0,0 +1,18 @@ +const marked = require('marked'); +const createDOMPurify = require('dompurify'); +const { JSDOM } = require('jsdom'); + +//This function parses Markdown input, converts it to HTML, and then sanitizes it for security reasons since this input text comes from the user +async function parseMarkdown(inputText) { + let parsedMD = marked.parse(inputText); + + const window = new JSDOM('').window; + const DOMPurify = createDOMPurify(window); + const cleanHTML = DOMPurify.sanitize(parsedMD); + + return await cleanHTML; +} + +module.exports= { + parseMarkdown +} \ No newline at end of file diff --git a/server/model/publishers.js b/server/model/publishers.js index 4a06e06..d287c85 100644 --- a/server/model/publishers.js +++ b/server/model/publishers.js @@ -1 +1,41 @@ const connection = require('./connection'); + +async function getPublisherNames() { + let selectSql = `SELECT id,name FROM publishers` + + return await connection.query(selectSql); +} + +async function getPublisher(id) { + + let selectSql = `SELECT name,homepage,description FROM publishers`, + whereStatements = [], + queryParameters = []; + + if (typeof id !== 'undefined' && id.length > 0) { + whereStatements.push("id LIKE ?"); + queryParameters.push('%' + id + '%'); + } + + //Dynamically add WHERE expressions to SELECT statements if needed + if (whereStatements.length > 0) { + selectSql = selectSql + ' WHERE ' + whereStatements.join(' AND '); + } + + return await connection.query(selectSql, queryParameters); +} + +async function addNewPublisher(formInput) { + let insertSql = `INSERT INTO publishers (name, description, homepage) VALUES (?, ?, ?)`; + let queryParameters = [formInput.name, + formInput.description, + formInput.homepage]; + + return await connection.query(insertSql, queryParameters); +} + +module.exports = { + getPublisherNames, + getPublisher, + addNewPublisher +} \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 2c77a8b..73ce7e8 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,11 +9,25 @@ "dompurify": "^3.0.2", "express": "^4.18.2", "express-validator": "^6.15.0", + "jsdom": "^21.1.1", "marked": "^4.3.0", "multer": "^1.4.5-lts.1", - "mysql2": "^3.2.0" + "mysql2": "^3.2.3" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -26,6 +40,66 @@ "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==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.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/agent-base/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/agent-base/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/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -36,6 +110,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -95,6 +174,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", @@ -158,6 +248,30 @@ "node": ">= 0.10" } }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -166,6 +280,24 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "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==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -191,6 +323,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dompurify": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.2.tgz", @@ -209,11 +352,71 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "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/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -275,6 +478,11 @@ "node": ">= 8.0.0" } }, + "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==" + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -292,6 +500,19 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -356,6 +577,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -371,6 +603,73 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/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/http-proxy-agent/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/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/https-proxy-agent/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/https-proxy-agent/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/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -395,6 +694,11 @@ "node": ">= 0.10" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -405,22 +709,78 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/jsdom": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", + "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.2", + "acorn-globals": "^7.0.0", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", "engines": { - "node": ">=12" + "node": ">=16.14" } }, "node_modules/marked": { @@ -527,15 +887,15 @@ } }, "node_modules/mysql2": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.0.tgz", - "integrity": "sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.4.tgz", + "integrity": "sha512-VRjBMAB/WXd35cytsbKPy5eQMdHTQu661CcFAK+pcScVCfGpzQQR9EOM9H4z9tvEW0maVOjTAX0dqXneLX4kaQ==", "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^5.2.1", - "lru-cache": "^7.14.1", + "lru-cache": "^8.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" @@ -566,6 +926,14 @@ "node": ">=12.0.0" } }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -574,6 +942,11 @@ "node": ">= 0.6" } }, + "node_modules/nwsapi": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", + "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -601,6 +974,33 @@ "node": ">= 0.8" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -614,6 +1014,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -631,6 +1039,19 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -645,6 +1066,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -686,6 +1112,16 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -710,6 +1146,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -775,6 +1222,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -812,6 +1268,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -820,6 +1281,42 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -837,6 +1334,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -845,6 +1350,15 @@ "node": ">= 0.8" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -874,6 +1388,108 @@ "node": ">= 0.8" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/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/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/server/package.json b/server/package.json index 2dd388a..b25e411 100644 --- a/server/package.json +++ b/server/package.json @@ -4,8 +4,9 @@ "dompurify": "^3.0.2", "express": "^4.18.2", "express-validator": "^6.15.0", + "jsdom": "^21.1.1", "marked": "^4.3.0", "multer": "^1.4.5-lts.1", - "mysql2": "^3.2.0" + "mysql2": "^3.2.3" } } diff --git a/server/public/images/.folder b/server/public/images/.folder new file mode 100644 index 0000000..e69de29