PA 10 complete.

This commit is contained in:
Kaj Forney 2023-05-02 06:01:30 -06:00
parent b6135191b5
commit a2047bdffe
Signed by: kforney
GPG key ID: 3AB4E2E04CEF656F
32 changed files with 2525 additions and 4 deletions

View file

@ -8,9 +8,12 @@ const publishers = require('./model/publishers');
const {request, response} = require("express"); const {request, response} = require("express");
const {check, checkSchema, validationResult} = require("express-validator"); const {check, checkSchema, validationResult} = require("express-validator");
const path = require('path'); const path = require('path');
const cors = require('cors');
//Setup defaults for script //Setup defaults for script
const app = express(); const app = express();
app.use(cors());
app.use(express.static('public'));
const storage = multer.diskStorage({ const storage = multer.diskStorage({
//Logic where to upload files //Logic where to upload files
destination: function (request, file, callback) { destination: function (request, file, callback) {
@ -31,8 +34,6 @@ const upload = multer({
}); });
const port = 8787; const port = 8787;
app.use(express.static('public'));
//The * in app.* needs to match the method type of the request //The * in app.* needs to match the method type of the request
app.get('/games', upload.none(), app.get('/games', upload.none(),
async (request, response) => { async (request, response) => {

View file

@ -112,8 +112,8 @@ async function getGame(id) {
queryParameters = []; queryParameters = [];
if (typeof id !== 'undefined' && id.length > 0) { if (typeof id !== 'undefined' && id.length > 0) {
whereStatements.push("m.id LIKE ?"); whereStatements.push("m.id = ?");
queryParameters.push('%' + id + '%'); queryParameters.push(id);
} }
//Dynamically add WHERE expressions to SELECT statements if needed //Dynamically add WHERE expressions to SELECT statements if needed

62
server/public/404.html Normal file
View file

@ -0,0 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Not Found</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
line-height: 1.2;
margin: 0;
}
html {
color: #888;
display: table;
font-family: sans-serif;
height: 100%;
text-align: center;
width: 100%;
}
body {
display: table-cell;
vertical-align: middle;
margin: 2em auto;
}
h1 {
color: #555;
font-size: 2em;
font-weight: 400;
}
p {
margin: 0 auto;
width: 280px;
}
@media only screen and (max-width: 280px) {
body,
p {
width: 95%;
}
h1 {
font-size: 1.5em;
margin: 0 0 0.3em;
}
}
</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>Sorry, but the page you were trying to view does not exist.</p>
</body>
</html>
<!-- IE needs 512+ bytes: https://docs.microsoft.com/archive/blogs/ieinternals/friendly-http-error-pages -->

19
server/public/LICENSE.txt Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) HTML5 Boilerplate
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Please read: https://msdn.microsoft.com/en-us/library/ie/dn455106.aspx -->
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="icons/tile.png"/>
<square150x150logo src="icons/tile.png"/>
<wide310x150logo src="icons/tile-wide.png"/>
<square310x310logo src="icons/tile.png"/>
</tile>
</msapplication>
</browserconfig>

433
server/public/css/main.css Normal file
View file

@ -0,0 +1,433 @@
/*! HTML5 Boilerplate v8.0.0 | MIT License | https://html5boilerplate.com/ */
/* main.css 2.1.0 | MIT License | https://github.com/h5bp/main.css#readme */
/*
* What follows is the result of much research on cross-browser styling.
* Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
* Kroc Camen, and the H5BP dev community and team.
*/
/* ==========================================================================
Base styles: opinionated defaults
========================================================================== */
html {
color: #222;
font-size: 1em;
line-height: 1.4;
}
/*
* Remove text-shadow in selection highlight:
* https://twitter.com/miketaylr/status/12228805301
*
* Vendor-prefixed and regular ::selection selectors cannot be combined:
* https://stackoverflow.com/a/16982510/7133471
*
* Customize the background color to match your design.
*/
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
/*
* A better looking default horizontal rule
*/
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
/*
* Remove the gap between audio, canvas, iframes,
* images, videos and the bottom of their containers:
* https://github.com/h5bp/html5-boilerplate/issues/440
*/
audio,
canvas,
iframe,
img,
svg,
video {
vertical-align: middle;
}
/*
* Remove default fieldset styles.
*/
fieldset {
border: 0;
margin: 0;
padding: 0;
}
/*
* Allow only vertical resizing of textareas.
*/
textarea {
resize: vertical;
}
/* ==========================================================================
Author's custom styles
========================================================================== */
#header {
background-color: #171a21;
color: #c6d4df;
}
#form {
background-image: linear-gradient(180deg, #171a21, #1b2838);
min-height: 750px;
color: #c6d4df;
}
#footer {
width: 100%;
height: 60px;
line-height: 60px;
text-align: center;
background-color: #171a21;
color: lightsteelblue;
}
hr{
border-top: 3px solid #cccccc;
}
body, html {
margin: 0px;
background-color: #171a21;
transition: all 0.4s ease 0s;
color: #d3dade;
}
p{
margin-bottom: 0;
}
input, select, textarea {
border-radius: 3px;
background-color: #316282;
color: #d3dade;
border: 1px solid #22445b;
}
#submitButton {
border-radius: 5px;
border:0;
min-width: 150px;
min-height: 50px;
background-image: linear-gradient(#5c7e10, #4c6b22);
color: #ffffff;
}
#submitButton:hover {
background-image: linear-gradient(#92c91a, #84b83b);
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;
}
#deleteButton {
border-radius: 5px;
border:0;
min-width: 150px;
min-height: 50px;
background-image: linear-gradient(#c51210, #5c1210);
color: #ffffff;
}
#deleteButton:hover {
background-image: linear-gradient(#e53333, #d51210);
box-shadow: #000000 3px 3px 5px;
}
#successMessage {
background-color: #4c6b22;
color: #b3d4fc;
min-height: 30px;
}
#errorMessage {
background-color: darkred;
color: #b3d4fc;
min-height: 30px;
}
.warning {
background-color: darkred;
color: #b3d4fc;
min-height: 30px;
font-weight: bolder;
font-size: 22px;
text-align: center;
}
.tableCoverArt {
max-height: 50px;
}
table{
margin-bottom: 50px;
}
table, th, td {
border-collapse: collapse;
}
th{
background-color: #5c7e10;
border: 1px solid #4c6b22;
color: #ffffff;
padding-left: 15px;
padding-right: 15px;
}
td {
padding-left: 15px;
padding-right: 15px;
border-left: 1px solid #5c7e10;
border-right: 1px solid #5c7e10;
}
tbody tr:nth-child(even) {
background-color: #1b2838;
color: #d3dade;
}
tbody tr:nth-child(odd) {
background-color: #171a21;
color: #d3dade;
}
.overflow{
overflow-x:auto;
}
@font-face {
font-family: VT323;
font-display: swap;
src: url("../fonts/VT323-Regular.ttf");
}
@font-face {
font-family: Oswald, "DejaVu Sans";
font-display: swap;
src: url("../fonts/Oswald-Regular.ttf");
}
@font-face {
font-family: 'IBM Plex Mono';
font-display: swap;
src: url("../fonts/IBMPlexMono-Regular.ttf");
}
@font-face {
font-family: 'IBM Plex Sans';
font-display: swap;
src: url("../fonts/IBMPlexSans-Regular.ttf");
}
/* ==========================================================================
Helper classes
========================================================================== */
/*
* Hide visually and from screen readers
*/
.hidden,
[hidden] {
display: none !important;
}
/*
* Hide only visually, but have it available for screen readers:
* https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
*
* 1. For long content, line feeds are not interpreted as spaces and small width
* causes content to wrap 1 word per line:
* https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
*/
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
/* 1 */
}
/*
* Extends the .sr-only class to allow the element
* to be focusable when navigated to via the keyboard:
* https://www.drupal.org/node/897638
*/
.sr-only.focusable:active,
.sr-only.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
white-space: inherit;
width: auto;
}
/*
* Hide visually and from screen readers, but maintain layout
*/
.invisible {
visibility: hidden;
}
/*
* Clearfix: contain floats
*
* For modern browsers
* 1. The space content is one way to avoid an Opera bug when the
* `contenteditable` attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that receive the `clearfix` class.
* 2. The use of `table` rather than `block` is only necessary if using
* `:before` to contain the top-margins of child elements.
*/
.clearfix::before,
.clearfix::after {
content: " ";
display: table;
}
.clearfix::after {
clear: both;
}
/* ==========================================================================
EXAMPLE Media Queries for Responsive Design.
These examples override the primary ('mobile first') styles.
Modify as content requires.
========================================================================== */
@media only screen and (min-width: 35em) {
/* Style adjustments for viewports that meet the condition */
}
@media print,
(-webkit-min-device-pixel-ratio: 1.25),
(min-resolution: 1.25dppx),
(min-resolution: 120dpi) {
/* Style adjustments for high resolution devices */
}
/* ==========================================================================
Print styles.
Inlined to avoid the additional HTTP request:
https://www.phpied.com/delay-loading-your-print-css/
========================================================================== */
@media print {
*,
*::before,
*::after {
background: #fff !important;
color: #000 !important;
/* Black prints faster */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]::after {
content: " (" attr(href) ")";
}
abbr[title]::after {
content: " (" attr(title) ")";
}
/*
* Don't show links that are fragment identifiers,
* or use the `javascript:` pseudo protocol
*/
a[href^="#"]::after,
a[href^="javascript:"]::after {
content: "";
}
pre {
white-space: pre-wrap !important;
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
/*
* Printing Tables:
* https://web.archive.org/web/20180815150934/http://css-discuss.incutio.com/wiki/Printing_Tables
*/
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}

349
server/public/css/normalize.css vendored Normal file
View file

@ -0,0 +1,349 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

314
server/public/delete.html Normal file
View file

@ -0,0 +1,314 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Delete Game</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>Edit Game</h1>
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="successMessage"></div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="errorMessage"></div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card warning">
<p>Are you SURE you want to delete this game?</p>
</div>
<div class="col"></div>
</div>
<div id="form">
<div class="container">
<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">
<select id="developer">
</select>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>
Publisher
</label>
</div>
<div class="col">
<select id="publisher">
</select>
</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="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 class="row mb-3">
<div class="col">
<label>Cover Art</label>
</div>
<div class="col">
<div id="currentArt"></div>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Short Description</label>
</div>
<div class="col">
<textarea id="shortDescription" rows="5" cols="40" maxlength="300"></textarea>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<label>Long Description</label>
</div>
<div class="col">
<textarea id="longDescription" rows="15" cols="40"></textarea>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col-2 align-content-center">
<button id="deleteButton" type="submit">Delete</button>
</div>
<div class="col-2 align-content-center">
<button id="backButton">Back</button>
</div>
<div class="col-8"></div>
</div>
<hr/>
</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("successMessage").hidden = true;
document.getElementById("errorMessage").hidden = true;
document.getElementById('backButton').addEventListener('click', (event) => {
history.back();
});
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);
});
let urlParams = new URLSearchParams(window.location.search)
fetch("http://localhost:8787/games/" + urlParams.get("id"), {method: 'GET'})
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({ status, json }) => {
if (200 === status) {
let gameData = json.data[0];
if(gameData !== undefined) {
document.getElementById("title").value = gameData.title;
let releaseDate = new Date(gameData.releaseDate);
document.getElementById("releaseDate").value = releaseDate.toISOString().split("T", 1);
document.getElementById("win").checked = Boolean(gameData.win);
document.getElementById("mac").checked = Boolean(gameData.mac);
document.getElementById("linux").checked = Boolean(gameData.linux);
let coverImg = document.createElement('img');
coverImg.src = "http://localhost:8787/images/" + gameData.coverArt;
document.getElementById("currentArt").appendChild(coverImg);
document.getElementById("shortDescription").value = gameData.shortDescription;
document.getElementById("longDescription").value = gameData.longDescription;
} else {
document.getElementById("errorMessage").innerHTML = "Invalid App ID!";
document.getElementById("errorMessage").hidden = false;
}
}
})
.catch(error => {
console.error('Error:', error);
});
document.getElementById('deleteButton').addEventListener('click', (event) => {
const parameters = {};
if(urlParams.get("id") !== undefined) {
parameters.id = urlParams.get("id");
}
//Settings for FETCH API request
let fetchSettings = {
method: 'DELETE'
};
//Send FETCH API request
fetch("http://localhost:8787/games/" + urlParams.get("id") + (!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 = "Game deletion successful!"
document.getElementById('successMessage').hidden = false;
document.getElementById('successMessage').innerHTML = message;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</body>
</html>

362
server/public/edit.html Normal file
View file

@ -0,0 +1,362 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Edit Game</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>Edit Game</h1>
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="successMessage"></div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="errorMessage"></div>
<div class="col"></div>
</div>
<div id="form">
<div class="container">
<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
<br/>
(<a href="insertDeveloper.html">Add New Developer</a>)
</label>
</div>
<div class="col">
<select id="developer">
</select>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<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>
</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="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 class="row mb-3">
<div class="col">
<label>Cover Art</label>
</div>
<div class="col">
<div id="currentArt"></div>
<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>
<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>
</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>
<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("successMessage").hidden = true;
document.getElementById("errorMessage").hidden = true;
document.getElementById('backButton').addEventListener('click', (event) => {
history.back();
});
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);
});
let urlParams = new URLSearchParams(window.location.search)
fetch("http://localhost:8787/games/" + urlParams.get("id"), {method: 'GET'})
.then((response) => {
return new Promise((resolve) => response.json()
.then((json) => resolve({
status: response.status,
json,
})
));
})
.then(({ status, json }) => {
if (200 === status) {
let gameData = json.data[0];
if(gameData !== undefined) {
document.getElementById("title").value = gameData.title;
let releaseDate = new Date(gameData.releaseDate);
document.getElementById("releaseDate").value = releaseDate.toISOString().split("T", 1);
document.getElementById("win").checked = Boolean(gameData.win);
document.getElementById("mac").checked = Boolean(gameData.mac);
document.getElementById("linux").checked = Boolean(gameData.linux);
let coverImg = document.createElement('img');
coverImg.src = "http://localhost:8787/images/" + gameData.coverArt;
document.getElementById("currentArt").appendChild(coverImg);
document.getElementById("shortDescription").value = gameData.shortDescription;
document.getElementById("longDescription").value = gameData.longDescription;
} else {
document.getElementById("errorMessage").innerHTML = "Invalid App ID!";
document.getElementById("errorMessage").hidden = false;
}
}
})
.catch(error => {
console.error('Error:', error);
});
document.getElementById('submitButton').addEventListener('click', (event) => {
const parameters = {};
if(urlParams.get("id") !== undefined) {
parameters.id = urlParams.get("id");
}
if (document.getElementById('title').value.length !== 0) {
//formData.append('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);
parameters.developerID = document.getElementById('developer').value;
}
if (document.getElementById('publisher').value.length !== 0) {
//formData.append('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);
parameters.releaseDate = document.getElementById('releaseDate').value;
}
if (document.getElementById('win').checked) {
parameters.win = 1;
}
else {
parameters.win = 0;
}
if (document.getElementById('mac').checked) {
parameters.mac = 1;
}
else {
parameters.mac = 0;
}
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: 'PUT',
body: formData
};
//Send FETCH API request
fetch("http://localhost:8787/games/" + urlParams.get("id") + (!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 = "Game submission successful!"
document.getElementById('outputMessage').hidden = false;
document.getElementById('outputMessage').innerHTML = message;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
server/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

78
server/public/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;
let 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>

BIN
server/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

0
server/public/img/.gitignore vendored Normal file
View file

277
server/public/index.html Normal file
View file

@ -0,0 +1,277 @@
<!doctype html>
<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">
<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>View Game Data</h1>
<hr/>
</div>
<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="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>
<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>
<footer id="footer">
<p>Page created by <a href="https://lunarpenguin.net/">Kaj Forney</a>, as work for class (GIMM 285).</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('submitButton').addEventListener('click', (event) => {
//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;
}
//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>Edit Game</th>' +
'<th>Delete 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;
}
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> <a href="delete.html?id=' + row.appID.toString() + '">Delete</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>
</body>
</html>

309
server/public/insert.html Normal file
View file

@ -0,0 +1,309 @@
<!doctype html>
<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">
<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 Game</h1>
<hr/>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="successMessage"></div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col"></div>
<div class="col card" id="errorMessage"></div>
<div class="col"></div>
</div>
<div id="form">
<div class="container">
<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
<br/>
(<a href="insertDeveloper.html">Add New Developer</a>)
</label>
</div>
<div class="col">
<select id="developer">
</select>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col">
<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>
</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="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 class="row mb-3">
<div class="col">
<label>Cover Art</label>
</div>
<div class="col">
<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>
<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>
</div>
<div class="col"></div>
</div>
<div class="row mb-3">
<div class="col align-content-center">
<button id="submitButton" type="submit">Submit</button>
</div>
</div>
<hr/>
</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("successMessage").hidden = true;
document.getElementById("errorMessage").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 parameters = {};
if (document.getElementById('title').value.length !== 0) {
//formData.append('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);
parameters.developerID = document.getElementById('developer').value;
}
if (document.getElementById('publisher').value.length !== 0) {
//formData.append('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);
parameters.releaseDate = document.getElementById('releaseDate').value;
}
if (document.getElementById('win').checked) {
parameters.win = 1;
}
else {
parameters.win = 0;
}
if (document.getElementById('mac').checked) {
parameters.mac = 1;
}
else {
parameters.mac = 0;
}
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: 'POST',
body: formData
};
//Send FETCH API request
fetch("http://localhost:8787/games" + (!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 = "Game submission successful!"
document.getElementById('successMessage').hidden = false;
document.getElementById('successMessage').innerHTML = message;
}
})
.catch(error => {
console.error('Error:', error);
});
return;
});
</script>
</body>
</html>

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>

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>

0
server/public/js/main.js Normal file
View file

File diff suppressed because one or more lines are too long

5
server/public/robots.txt Normal file
View file

@ -0,0 +1,5 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:

View file

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}