PA 5 (INSERT) complete.

This commit is contained in:
Kaj Forney 2023-04-29 20:48:03 -06:00
parent 3419c9b680
commit 6203d762bb
Signed by: kforney
GPG key ID: 3AB4E2E04CEF656F
18 changed files with 1819 additions and 380 deletions

View file

@ -8,5 +8,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="bootstrap" level="application" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{bootstrap}" />
</component>
</project>

View file

@ -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;
}

10
edit.html Normal file
View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Edit Game</title>
</head>
<body>
</body>
</html>

78
game.html Normal file
View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pressurized Gas -- Game Page</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<meta name="theme-color" content="#488ea7">
</head>
<body>
<div id="gameTitle"></div>
<div id="longDescription"></div>
<div id="coverArt"></div>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous" defer></script>
<script>
const isEmpty = (obj) => Object.keys(obj).length === 0;
const getParameters = {};
let gameID = new URL(document.location).searchParams.get("ID");
if (gameID == null) {
gameID = -1;
}
//Settings for FETCH API request
let fetchSettings = {
method: 'GET'
};
if (gameID !== -1) {
fetch("http://localhost:8787/games/" + gameID, fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({status, json}) => {
if (status === 200) {
document.title = "Pressurized Gas - " + json.data[0].title;
document.getElementById("gameTitle").innerHTML = json.data[0].title;
document.getElementById("longDescription").innerHTML = json.data[0].longDescription;
var coverImg = document.createElement('img');
coverImg.src = "http://localhost:8787/images/" + json.data[0].coverArt;
document.getElementById("coverArt").appendChild(coverImg);
}
})
.catch(error => {
console.error('Error:', error);
});
} else {
document.title = "Pressurized Gas - ERROR"
document.getElementById("gameTitle").innerHTML = "Invalid game ID!"
document.getElementById("gameTitle").style.color = "#FF0000";
}
</script>
</body>
</html>

View file

@ -2,260 +2,273 @@
<html>
<head>
<meta charset="utf-8">
<title>Kaj Forney -- GIMM 285 CRUD API Project</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>Kaj Forney -- GIMM 285 CRUD API Project</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<meta name="theme-color" content="#488ea7">
<meta name="theme-color" content="#488ea7">
</head>
<body>
<div id="header" class="container">
<div id="header" class="container">
<h1>View Game Data</h1>
<hr />
</div>
<hr/>
</div>
<div id="form">
<div id="form">
<div class="container">
<div class="row mb-3">
<div class="col">
<label>Application ID</label>
</div>
<div class="col">
<input id="appID" type="text" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Title</label>
</div>
<div class="col">
<input id="title" type="text" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Developer</label>
</div>
<div class="col">
<input id="developer" type="text" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Publisher</label>
</div>
<div class="col">
<input id="publisher" type="text" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Release Date</label>
</div>
<div class="col">
<input id="releaseDate" type="date" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Supported OSes</label>
</div>
<div class="col">
<div class="row">
<div class="row mb-3">
<div class="col">
<span>Windows</span>
<input id="win" type="checkbox"/>
<label>Application ID</label>
</div>
<div class="col">
<span>MacOS</span>
<input id="mac" type="checkbox"/>
<input id="appID" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Title</label>
</div>
<div class="col">
<span>Linux</span>
<input id="linux" type="checkbox"/>
<input id="title" type="text"/>
</div>
</div>
<div class="col"></div>
</div>
<div class="col"></div>
</div>
<hr />
<div class="row mb-3">
<div class="col">
<label>Developer</label>
</div>
<div class="col">
<input id="developer" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Limit Number of Results to:</label>
<div class="row mb-3">
<div class="col">
<label>Publisher</label>
</div>
<div class="col">
<input id="publisher" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="col">
<input id="limitNumber" type="text" />
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Order by:</label>
<div class="row mb-3">
<div class="col">
<label>Release Date</label>
</div>
<div class="col">
<input id="releaseDate" type="date"/>
</div>
<div class="col"></div>
</div>
<div class="col">
<select id="orderBy">
<option value="ASC">Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col align-content-center">
<input id="submitButton" type="submit" />
<div class="row mb-3">
<div class="col">
<label>Supported OSes</label>
</div>
<div class="col">
<div class="row">
<div class="col">
<span>Windows</span>
<input id="win" type="checkbox"/>
</div>
<div class="col">
<span>MacOS</span>
<input id="mac" type="checkbox"/>
</div>
<div class="col">
<span>Linux</span>
<input id="linux" type="checkbox"/>
</div>
</div>
</div>
<div class="col"></div>
</div>
</div>
<hr/>
<hr/>
<div class="row mb-3">
<div class="col">
<label>Limit Number of Results to:</label>
</div>
<div class="col">
<input id="limitNumber" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Order by:</label>
</div>
<div class="col">
<select id="orderBy">
<option value="ASC">Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col align-content-center">
<input id="submitButton" type="submit"/>
</div>
</div>
<hr/>
</div>
<div id="outputTable" class="container overflow"></div>
</div>
</div>
<footer id="footer">
<footer id="footer">
<p>Page created by <a href="https://lunarpenguin.net/">Kaj Forney</a>, as work for class (GIMM 285).</p>
</footer>
</footer>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous" defer></script>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous" defer></script>
<script>
<script>
const isEmpty = (obj) => Object.keys(obj).length === 0;
document.getElementById('submitButton').addEventListener('click', (event) => {
//const formData = new FormData();
const getParameters = {};
//const formData = new FormData();
const getParameters = {};
if (document.getElementById('appID').value.length !== 0) {
//formData.append('appID', document.getElementById('appID').value);
getParameters.appID = document.getElementById('appID').value;
}
if (document.getElementById('title').value.length !== 0) {
//formData.append('title', document.getElementById('title').value);
getParameters.title = document.getElementById('title').value;
}
if (document.getElementById('developer').value.length !== 0) {
//formData.append('developer', document.getElementById('developer').value);
getParameters.developer = document.getElementById('developer').value;
}
if (document.getElementById('publisher').value.length !== 0) {
//formData.append('publisher', document.getElementById('publisher').value);
getParameters.publisher = document.getElementById('publisher').value;
}
if (document.getElementById('releaseDate').value.length !== 0) {
//formData.append('releaseDate', document.getElementById('releaseDate').value);
getParameters.releaseDate = document.getElementById('releaseDate').value;
}
if (document.querySelector('#win:checked')) {
//formData.append('win', 1);
getParameters.win = 1;
}
if (document.querySelector('#mac:checked')) {
//formData.append('mac', 1);
getParameters.mac = 1;
}
if (document.querySelector('#linux:checked')) {
//formData.append('linux', 1);
getParameters.linux = 1;
}
if (document.getElementById('limitNumber').value.length !== 0) {
//formData.append('limitNumber', document.getElementById('limitNumber').value);
getParameters.limitNumber = document.getElementById('limitNumber').value;
}
if (document.getElementById('orderBy').value.length !== 0) {
//formData.append('orderBy', document.getElementById('orderBy').value);
getParameters.orderBy = document.getElementById('orderBy').value;
}
if (document.getElementById('appID').value.length !== 0) {
//formData.append('appID', document.getElementById('appID').value);
getParameters.appID = document.getElementById('appID').value;
}
if (document.getElementById('title').value.length !== 0) {
//formData.append('title', document.getElementById('title').value);
getParameters.title = document.getElementById('title').value;
}
if (document.getElementById('developer').value.length !== 0) {
//formData.append('developer', document.getElementById('developer').value);
getParameters.developer = document.getElementById('developer').value;
}
if (document.getElementById('publisher').value.length !== 0) {
//formData.append('publisher', document.getElementById('publisher').value);
getParameters.publisher = document.getElementById('publisher').value;
}
if (document.getElementById('releaseDate').value.length !== 0) {
//formData.append('releaseDate', document.getElementById('releaseDate').value);
getParameters.releaseDate = document.getElementById('releaseDate').value;
}
if (document.querySelector('#win:checked')) {
//formData.append('win', 1);
getParameters.win = 1;
}
if (document.querySelector('#mac:checked')) {
//formData.append('mac', 1);
getParameters.mac = 1;
}
if (document.querySelector('#linux:checked')) {
//formData.append('linux', 1);
getParameters.linux = 1;
}
if (document.getElementById('limitNumber').value.length !== 0) {
//formData.append('limitNumber', document.getElementById('limitNumber').value);
getParameters.limitNumber = document.getElementById('limitNumber').value;
}
if (document.getElementById('orderBy').value.length !== 0) {
//formData.append('orderBy', document.getElementById('orderBy').value);
getParameters.orderBy = document.getElementById('orderBy').value;
}
//Settings for FETCH API request
let fetchSettings = {
method: 'GET'
};
//Settings for FETCH API request
let fetchSettings = {
method: 'GET'
};
//Send FETCH API request
fetch("http://localhost:8787/games" + (!isEmpty(getParameters) ? '?' + new URLSearchParams(getParameters) : ''), fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({status, json}) => {
if (status === 200) {
let displayTable = '<table>' +
'<thead>' +
'<tr>' +
'<th>AppID</th>' +
'<th>Title</th>' +
'<th>Developer</th>' +
'<th>Publisher</th>' +
'<th>Release Date</th> ' +
'<th>Supported OSes</th>' +
'<th>Summary</th>' +
'</tr>' +
'</thead>' +
'<tbody>';
//Send FETCH API request
fetch("http://localhost:8787/games" + (!isEmpty(getParameters) ? '?' + new URLSearchParams(getParameters) : ''), fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({status, json}) => {
if (status === 200) {
let displayTable = '<table>' +
'<thead>' +
'<tr>' +
'<th>Edit Game</th>' +
'<th>AppID</th>' +
'<th>Title</th>' +
'<th>Developer</th>' +
'<th>Publisher</th>' +
'<th>Release Date</th> ' +
'<th>Supported OSes</th>' +
'<th>Summary</th>' +
'<th>Cover Art</th>' +
'</tr>' +
'</thead>' +
'<tbody>';
function osSupport(win, mac, lnx) {
let osString = "";
if (win == 1) {osString += "Windows, "};
if (mac == 1) {osString += "Mac, "};
if (lnx == 1) {osString += "Linux"};
return osString;
}
function osSupport(win, mac, lnx) {
let osString = "";
if (win == 1) {
osString += "Windows, "
}
;
if (mac == 1) {
osString += "Mac, "
}
;
if (lnx == 1) {
osString += "Linux"
}
;
return osString;
}
if (typeof json.data !== 'undefined') {
for (row of json.data) {
displayTable += '<tr>' +
'<td>' + row.appID + '</td>' +
'<td>' + row.title + '</td>' +
'<td>' + row.developer + '</td>' +
'<td>' + row.publisher + '</td>' +
'<td>' + new Date(row.releaseDate).toDateString() + '</td>' +
'<td>' + osSupport(row.win.toString(), row.mac.toString(), row.linux.toString()) + '</td>' +
'<td>' + row.summary + '</td>' +
'</tr>';
}
}
displayTable += '</tbody></table>';
document.getElementById('outputTable').innerHTML = displayTable;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
if (typeof json.data !== 'undefined') {
for (row of json.data) {
displayTable += '<tr>' +
'<td> <a href="edit.html?id=' + row.appID.toString() + '">Edit</a></td>' +
'<td>' + row.appID + '</td>' +
'<td>' + row.title + '</td>' +
'<td>' + row.developer + '</td>' +
'<td>' + row.publisher + '</td>' +
'<td>' + new Date(row.releaseDate).toDateString() + '</td>' +
'<td>' + osSupport(row.win.toString(), row.mac.toString(), row.linux.toString()) + '</td>' +
'<td>' + row.summary + '</td>' +
'<td> <img class="tableCoverArt" src="http://localhost:8787/images/' + row.coverArt + '"> </td>' +
'</tr>';
}
}
displayTable += '</tbody></table>';
document.getElementById('outputTable').innerHTML = displayTable;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</script>
</body>

View file

@ -39,10 +39,15 @@
<div class="row mb-3">
<div class="col">
<label>Developer</label>
<label>
Developer
<br/>
(<a href="insertDeveloper.html">Add New Developer</a>)
</label>
</div>
<div class="col">
<select id="developer">
</select>
</div>
<div class="col"></div>
@ -50,10 +55,15 @@
<div class="row mb-3">
<div class="col">
<label>Publisher</label>
<label>
Publisher
<br/>
(<a href="insertPublisher.html">Add New Publisher</a>)
</label>
</div>
<div class="col">
<select id="publisher">
</select>
</div>
<div class="col"></div>
@ -94,10 +104,22 @@
<div class="row mb-3">
<div class="col">
<label>Short Description</label>
<label>Cover Art</label>
</div>
<div class="col">
<textarea id="shortDescription" rows="5" cols="40"></textarea>
<input id="coverArt" type="file"
accept="image/jpeg, image/png"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Short Description</label>
<p>(maximum 300 characters)</p>
</div>
<div class="col">
<textarea id="shortDescription" rows="5" cols="40" maxlength="300"></textarea>
</div>
<div class="col"></div>
</div>
@ -105,6 +127,7 @@
<div class="row mb-3">
<div class="col">
<label>Long Description</label>
<p>(supports <a href="https://www.markdownguide.org/">Markdown</a>) </p>
</div>
<div class="col">
<textarea id="longDescription" rows="15" cols="40"></textarea>
@ -120,13 +143,18 @@
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="outputMessage"></div>
<div class="col"></div>
</div>
<div id="outputTable" class="container overflow"></div>
</div>
<footer id="footer">
<p>Page created by <a href="https://lunarpenguin.net/">Kaj Forney</a>, as work for class (GIMM 285).</p>
<p>All example data used is originally from <a href="https://store.steampowered.com/">Steam</a>.</p>
</footer>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
@ -137,58 +165,116 @@
<script>
const isEmpty = (obj) => Object.keys(obj).length === 0;
document.getElementById("outputMessage").hidden = true;
fetch("http://localhost:8787/developers", { method: 'GET' })
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({ status, json }) => {
if (200 === status) {
const developerSelect = document.getElementById('developer');
for (dev of json.data) {
let option = document.createElement("option");
option.value = dev.id;
option.text = dev.name;
developerSelect.add(option);
}
}
})
.catch(error => {
console.error('Error:', error);
});
fetch("http://localhost:8787/publishers", { method: 'GET' })
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({ status, json }) => {
if (200 === status) {
const publisherSelect = document.getElementById('publisher');
for (publisher of json.data) {
let option = document.createElement("option");
option.value = publisher.id;
option.text = publisher.name;
publisherSelect.add(option);
}
}
})
.catch(error => {
console.error('Error:', error);
});
document.getElementById('submitButton').addEventListener('click', (event) => {
//const formData = new FormData();
const getParameters = {};
const parameters = {};
if (document.getElementById('appID').value.length !== 0) {
//formData.append('appID', document.getElementById('appID').value);
getParameters.appID = document.getElementById('appID').value;
}
if (document.getElementById('title').value.length !== 0) {
//formData.append('title', document.getElementById('title').value);
getParameters.title = document.getElementById('title').value;
parameters.title = document.getElementById('title').value;
}
if (document.getElementById('developer').value.length !== 0) {
//formData.append('developer', document.getElementById('developer').value);
getParameters.developer = document.getElementById('developer').value;
parameters.developerID = document.getElementById('developer').value;
}
if (document.getElementById('publisher').value.length !== 0) {
//formData.append('publisher', document.getElementById('publisher').value);
getParameters.publisher = document.getElementById('publisher').value;
parameters.publisherID = document.getElementById('publisher').value;
}
if (document.getElementById('releaseDate').value.length !== 0) {
//formData.append('releaseDate', document.getElementById('releaseDate').value);
getParameters.releaseDate = document.getElementById('releaseDate').value;
parameters.releaseDate = document.getElementById('releaseDate').value;
}
if (document.querySelector('#win:checked')) {
//formData.append('win', 1);
getParameters.win = 1;
if (document.getElementById('win').checked) {
parameters.win = 1;
}
if (document.querySelector('#mac:checked')) {
//formData.append('mac', 1);
getParameters.mac = 1;
else {
parameters.win = 0;
}
if (document.querySelector('#linux:checked')) {
//formData.append('linux', 1);
getParameters.linux = 1;
if (document.getElementById('mac').checked) {
parameters.mac = 1;
}
if (document.getElementById('limitNumber').value.length !== 0) {
//formData.append('limitNumber', document.getElementById('limitNumber').value);
getParameters.limitNumber = document.getElementById('limitNumber').value;
else {
parameters.mac = 0;
}
if (document.getElementById('orderBy').value.length !== 0) {
//formData.append('orderBy', document.getElementById('orderBy').value);
getParameters.orderBy = document.getElementById('orderBy').value;
if (document.getElementById('linux').checked) {
parameters.linux = 1;
}
else {
parameters.linux = 0;
}
if (document.getElementById('shortDescription').value.length !== 0) {
parameters.shortDescription = document.getElementById('shortDescription').value;
}
if (document.getElementById('longDescription').value.length !== 0) {
parameters.longDescription = document.getElementById('longDescription').value;
}
const formData = new FormData();
const coverField = document.querySelector('input[type="file"]');
formData.append("coverArt", coverField.files[0]);
//Settings for FETCH API request
let fetchSettings = {
method: 'GET'
method: 'POST',
body: formData
};
//Send FETCH API request
fetch("http://localhost:8787/games" + (!isEmpty(getParameters) ? '?' + new URLSearchParams(getParameters) : ''), fetchSettings)
fetch("http://localhost:8787/games" + (!isEmpty(parameters) ? '?' + new URLSearchParams(parameters) : ''), fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
@ -199,52 +285,9 @@
})
.then(({status, json}) => {
if (status === 200) {
let displayTable = '<table>' +
'<thead>' +
'<tr>' +
'<th>AppID</th>' +
'<th>Title</th>' +
'<th>Developer</th>' +
'<th>Publisher</th>' +
'<th>Release Date</th> ' +
'<th>Supported OSes</th>' +
'<th>Summary</th>' +
'</tr>' +
'</thead>' +
'<tbody>';
function osSupport(win, mac, lnx) {
let osString = "";
if (win == 1) {
osString += "Windows, "
}
;
if (mac == 1) {
osString += "Mac, "
}
;
if (lnx == 1) {
osString += "Linux"
}
;
return osString;
}
if (typeof json.data !== 'undefined') {
for (row of json.data) {
displayTable += '<tr>' +
'<td>' + row.appID + '</td>' +
'<td>' + row.title + '</td>' +
'<td>' + row.developer + '</td>' +
'<td>' + row.publisher + '</td>' +
'<td>' + new Date(row.releaseDate).toDateString() + '</td>' +
'<td>' + osSupport(row.win.toString(), row.mac.toString(), row.linux.toString()) + '</td>' +
'<td>' + row.summary + '</td>' +
'</tr>';
}
}
displayTable += '</tbody></table>';
document.getElementById('outputTable').innerHTML = displayTable;
let message = "Game submission successful!"
document.getElementById('outputMessage').hidden = false;
document.getElementById('outputMessage').innerHTML = message;
}
})
.catch(error => {

148
insertDeveloper.html Normal file
View file

@ -0,0 +1,148 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>Kaj Forney -- GIMM 285 CRUD API Project</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<meta name="theme-color" content="#488ea7">
</head>
<body>
<div id="header" class="container">
<h1>Add New Developer</h1>
<hr/>
</div>
<div id="form">
<div class="container">
<div class="row mb-3">
<div class="col">
<label>Name</label>
</div>
<div class="col">
<input id="name" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Description</label>
<p>(maximum 250 characters)</p>
</div>
<div class="col">
<textarea id="description" rows="5" cols="40" maxlength="250"></textarea>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Homepage</label>
</div>
<div class="col">
<input id="homepage" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col-2 align-content-center">
<button id="submitButton" type="submit">Submit</button>
</div>
<div class="col-2 align-content-center">
<button id="backButton">Back</button>
</div>
<div class="col-8"></div>
</div>
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="outputMessage"></div>
<div class="col"></div>
</div>
</div>
<footer id="footer">
<p>Page created by <a href="https://lunarpenguin.net/">Kaj Forney</a>, as work for class (GIMM 285).</p>
<p>All example data used is originally from <a href="https://store.steampowered.com/">Steam</a>.</p>
</footer>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous" defer></script>
<script>
const isEmpty = (obj) => Object.keys(obj).length === 0;
document.getElementById("outputMessage").hidden = true;
document.getElementById('backButton').addEventListener('click', (event) => {
history.back();
});
document.getElementById('submitButton').addEventListener('click', (event) => {
//const formData = new FormData();
const parameters = {};
if (document.getElementById('name').value.length !== 0) {
parameters.name = document.getElementById('name').value;
}
if (document.getElementById('description').value.length !== 0) {
parameters.description = document.getElementById('description').value;
}
if (document.getElementById('homepage').value.length !== 0) {
parameters.homepage = document.getElementById('homepage').value;
}
//Settings for FETCH API request
let fetchSettings = {
method: 'POST',
};
//Send FETCH API request
fetch("http://localhost:8787/developers" + (!isEmpty(parameters) ? '?' + new URLSearchParams(parameters) : ''), fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({status, json}) => {
if (status === 200) {
let message = "Submission successful!"
document.getElementById('outputMessage').hidden = false;
document.getElementById('outputMessage').innerHTML = message;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</body>
</html>

148
insertPublisher.html Normal file
View file

@ -0,0 +1,148 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>Kaj Forney -- GIMM 285 CRUD API Project</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="site.webmanifest">
<link rel="apple-touch-icon" href="icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<meta name="theme-color" content="#488ea7">
</head>
<body>
<div id="header" class="container">
<h1>Add New Publisher</h1>
<hr/>
</div>
<div id="form">
<div class="container">
<div class="row mb-3">
<div class="col">
<label>Name</label>
</div>
<div class="col">
<input id="name" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Description</label>
<p>(maximum 250 characters)</p>
</div>
<div class="col">
<textarea id="description" rows="5" cols="40" maxlength="250"></textarea>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Homepage</label>
</div>
<div class="col">
<input id="homepage" type="text"/>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col-2 align-content-center">
<button id="submitButton" type="submit">Submit</button>
</div>
<div class="col-2 align-content-center">
<button id="backButton">Back</button>
</div>
<div class="col-8"></div>
</div>
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="outputMessage"></div>
<div class="col"></div>
</div>
</div>
<footer id="footer">
<p>Page created by <a href="https://lunarpenguin.net/">Kaj Forney</a>, as work for class (GIMM 285).</p>
<p>All example data used is originally from <a href="https://store.steampowered.com/">Steam</a>.</p>
</footer>
<script src="js/vendor/modernizr-3.11.2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous" defer></script>
<script>
const isEmpty = (obj) => Object.keys(obj).length === 0;
document.getElementById("outputMessage").hidden = true;
document.getElementById('backButton').addEventListener('click', (event) => {
history.back();
});
document.getElementById('submitButton').addEventListener('click', (event) => {
//const formData = new FormData();
const parameters = {};
if (document.getElementById('name').value.length !== 0) {
parameters.name = document.getElementById('name').value;
}
if (document.getElementById('description').value.length !== 0) {
parameters.description = document.getElementById('description').value;
}
if (document.getElementById('homepage').value.length !== 0) {
parameters.homepage = document.getElementById('homepage').value;
}
//Settings for FETCH API request
let fetchSettings = {
method: 'POST',
};
//Send FETCH API request
fetch("http://localhost:8787/publishers" + (!isEmpty(parameters) ? '?' + new URLSearchParams(parameters) : ''), fetchSettings)
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({status, json}) => {
if (status === 200) {
let message = "Submission successful!"
document.getElementById('outputMessage').hidden = false;
document.getElementById('outputMessage').innerHTML = message;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</body>
</html>

View file

@ -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, () => {

View file

@ -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);
}

View file

@ -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
}

View file

@ -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
}

18
server/model/parseText.js Normal file
View file

@ -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
}

View file

@ -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
}

640
server/package-lock.json generated
View file

@ -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",

View file

@ -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"
}
}

View file