First working prototype
This commit is contained in:
BIN
project.db
BIN
project.db
Binary file not shown.
BIN
project.db-shm
BIN
project.db-shm
Binary file not shown.
BIN
project.db-wal
BIN
project.db-wal
Binary file not shown.
@@ -1,74 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Home</title>
|
<title>"Password Manager"</title>
|
||||||
<link rel="stylesheet" href="../assets/css/style.css">
|
<link rel="stylesheet" href="../assets/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="grid-container">
|
<div class="center">
|
||||||
<div class="header">
|
<form id="infoForm">
|
||||||
<div class="header-left">
|
<h2>Personal Information</h2>
|
||||||
<div class="logo-container">
|
<hr>
|
||||||
<h2 class="logo">PayDirt</h2>
|
<div class="inputGroup">
|
||||||
<p class="subtitle">NFL Stats</p>
|
<p>Password Name</p>
|
||||||
|
<input type="text" id="name" placeholder="">
|
||||||
</div>
|
</div>
|
||||||
<a class="button" href="/search">Search Players</a>
|
<div class="inputGroup">
|
||||||
<br>
|
<p>Password:</p>
|
||||||
|
<input type="text" id="password" placeholder="">
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="inputGroup">
|
||||||
<a class="user-FirstName button" href="/info">Welcome, %</a>
|
<p>Set password?</p>
|
||||||
<button id="logoutButton">Log Out</button>
|
<input type="checkbox" id="set" name="Set?">
|
||||||
<button id="deleteButton" style="display: none;">Delete Account</button>
|
</div>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
<a href="/login">Log Out</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="error">
|
||||||
|
<p>Error Message</p>
|
||||||
|
</div>
|
||||||
|
<div class="success">
|
||||||
|
<p>Password successfully retrieved</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="box nw">
|
|
||||||
<p><b>Watched Players</b></p>
|
|
||||||
<table class="body" id="favorites">
|
|
||||||
<thead>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="box ne">
|
|
||||||
<p><b>Highest Contracts</b></p>
|
|
||||||
<table class="body" id="highest">
|
|
||||||
<thead>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="box sw">
|
|
||||||
<div class="cursor-question">
|
|
||||||
<p><b>Highest PayDirt Score</b></p>
|
|
||||||
<span class="tooltip-text">A player's <b>PayDirt score</b> is calculated by weighting their offense score by their annual salary. It is used to evaluate the worth of their contract.</span>
|
|
||||||
</div>
|
|
||||||
<table class="body" id="paydirt">
|
|
||||||
<thead>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="box se">
|
|
||||||
<div class="cursor-question">
|
|
||||||
<p><b>Highest Offense Score</b></p>
|
|
||||||
<span class="tooltip-text">A player's <b>offense score</b> is calculated by combining several of their offense stats into a weighted score. It is indicative of their overall performance.</span>
|
|
||||||
</div>
|
|
||||||
<table class="body" id="offense">
|
|
||||||
<thead>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module" src="variables.js"></script>
|
|
||||||
<script type="module" src="home.js"></script>
|
<script type="module" src="home.js"></script>
|
||||||
<script type="module" src="logout.js"></script>
|
|
||||||
<script type="module" src="delete.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
327
public/home.js
327
public/home.js
@@ -1,212 +1,185 @@
|
|||||||
import { postData, verifyLogin } from "./client.js";
|
import { postData, verifyLogin } from "./client.js";
|
||||||
import { formatSalary } from "./utils.js";
|
|
||||||
|
|
||||||
|
verifyLogin();
|
||||||
const token = window.localStorage.getItem("token");
|
const token = window.localStorage.getItem("token");
|
||||||
|
|
||||||
if (!token) {
|
const infoForm = document.getElementById("infoForm");
|
||||||
window.location.href = "/login";
|
|
||||||
|
const error = document.querySelector(".error");
|
||||||
|
const errorMessage = document.querySelector(".error p");
|
||||||
|
const success = document.querySelector(".success");
|
||||||
|
const successMessage = document.querySelector(".success p");
|
||||||
|
|
||||||
|
openKeyDB();
|
||||||
|
const encryptionKey = await getEncryptionKey()
|
||||||
|
|
||||||
|
//console.log(`Encryption Key:`);
|
||||||
|
//console.log(encryptionKey);
|
||||||
|
|
||||||
|
async function handlePasswordSubmission(e) {
|
||||||
|
console.log(await verifyLogin());
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = {
|
||||||
|
name: document.getElementById("name").value,
|
||||||
|
password: document.getElementById("password").value,
|
||||||
|
}
|
||||||
|
const set = document.getElementById("set").checked;
|
||||||
|
|
||||||
|
console.log(set);
|
||||||
|
|
||||||
|
if (set) {
|
||||||
|
//console.log("formdata SET so SETTING");
|
||||||
|
setPassword(formData.name, formData.password);
|
||||||
|
} else {
|
||||||
|
//console.log("formdata GET so GETTING");
|
||||||
|
getPassword(formData.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const highest = document.querySelector("#highest");
|
infoForm.onsubmit = handlePasswordSubmission;
|
||||||
|
|
||||||
updateHighest();
|
async function setPassword(name, password) {
|
||||||
updateFavorites();
|
const encoder = new TextEncoder();
|
||||||
updatePaydirt();
|
const encodedData = encoder.encode(password);
|
||||||
updateOffense();
|
|
||||||
|
|
||||||
window.addEventListener("pageshow", e => {
|
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||||
if (e.persisted) {
|
|
||||||
updateFavorites();
|
const cyphertextBuffer = await window.crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv: iv
|
||||||
|
},
|
||||||
|
encryptionKey,
|
||||||
|
encodedData
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: name,
|
||||||
|
iv: bufferToBase64(iv),
|
||||||
|
password: bufferToBase64(cyphertextBuffer)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
async function updateOffense() {
|
var resultObject = await postData("/setPassword", data, token);
|
||||||
if (!verifyLogin()) return;
|
|
||||||
|
|
||||||
|
if (resultObject.message.includes("UNIQUE")) {
|
||||||
|
resultObject = await postData("/updatePassword", data, token);
|
||||||
|
}
|
||||||
|
|
||||||
const offenseHeader = document.querySelector("#offense thead");
|
if (resultObject.success) {
|
||||||
const offenseBody = document.querySelector("#offense tbody");
|
error.style.display = "none";
|
||||||
|
success.style.display = "flex";
|
||||||
|
successMessage.innerHTML = `Password for "${name}" successfully set to "${password}"`
|
||||||
|
} else {
|
||||||
|
errorMessage.innerHTML = resultObject.message;
|
||||||
|
success.style.display = "none";
|
||||||
|
error.style.display = "flex";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPassword(name) {
|
||||||
|
const data = {
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
const resultObject = await postData("/getPassword", data, token);
|
||||||
|
|
||||||
let resultObject = await postData("/getHighestOffense", { amount: 100 }, token);
|
if (resultObject.success) {
|
||||||
console.log(resultObject);
|
error.style.display = "none";
|
||||||
|
success.style.display = "flex";
|
||||||
if (resultObject.matches.length === 0) {
|
} else {
|
||||||
alert("Error loading scores");
|
errorMessage.innerHTML = resultObject.message;
|
||||||
|
success.style.display = "none";
|
||||||
|
error.style.display = "flex";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
offenseHeader.innerHTML = "";
|
const ivBuffer = base64ToBuffer(resultObject.iv);
|
||||||
const headerRow = document.createElement("tr");
|
const cyphertextBuffer = base64ToBuffer(resultObject.password);
|
||||||
//Object.keys(resultObject.matches[0]).forEach(attribute => {
|
|
||||||
// headerRow.innerHTML += `<td>${attribute}</td>`;
|
|
||||||
//});
|
|
||||||
headerRow.innerHTML = `
|
|
||||||
<td>Rank</td>
|
|
||||||
<td>Position</td>
|
|
||||||
<td>Name</td>
|
|
||||||
<td>Team</td>
|
|
||||||
<td>Offense Score</td>
|
|
||||||
`;
|
|
||||||
offenseHeader.appendChild(headerRow);
|
|
||||||
|
|
||||||
offenseBody.innerHTML = "";
|
const decryptedBuffer = await window.crypto.subtle.decrypt(
|
||||||
let i = 0;
|
{
|
||||||
resultObject.matches.forEach(player => {
|
name: "AES-GCM",
|
||||||
i++;
|
iv: ivBuffer
|
||||||
const row = document.createElement("tr");
|
},
|
||||||
|
encryptionKey,
|
||||||
|
cyphertextBuffer
|
||||||
|
);
|
||||||
|
|
||||||
row.innerHTML = `
|
const decoder = new TextDecoder();
|
||||||
<td>${i}</td>
|
const pw = decoder.decode(decryptedBuffer);
|
||||||
<td>${player.Position}</td>
|
document.getElementById("password").value = pw;
|
||||||
<td><a href="/player/${player.PlayerID}">${player.PlayerName}</a></td>
|
//console.log("Password: " + pw);
|
||||||
<td>${player.Team}</td>
|
|
||||||
<td>${Math.floor(player.OffenseScore)}</td>
|
|
||||||
`;
|
|
||||||
offenseBody.appendChild(row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
async function updatePaydirt() {
|
//console.log(resultObject);
|
||||||
if (!verifyLogin()) return;
|
|
||||||
|
|
||||||
|
successMessage.innerHTML = `Password for "${name}" is "${pw}"`
|
||||||
const paydirtHeader = document.querySelector("#paydirt thead");
|
|
||||||
const paydirtBody = document.querySelector("#paydirt tbody");
|
|
||||||
|
|
||||||
|
|
||||||
let resultObject = await postData("/getHighestPaydirt", { amount: 100 }, token);
|
|
||||||
console.log(resultObject);
|
|
||||||
|
|
||||||
if (resultObject.matches.length === 0) {
|
|
||||||
alert("Error loading scores");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paydirtHeader.innerHTML = "";
|
|
||||||
const headerRow = document.createElement("tr");
|
|
||||||
//Object.keys(resultObject.matches[0]).forEach(attribute => {
|
|
||||||
// headerRow.innerHTML += `<td>${attribute}</td>`;
|
|
||||||
//});
|
|
||||||
headerRow.innerHTML = `
|
|
||||||
<td>Rank</td>
|
|
||||||
<td>Position</td>
|
|
||||||
<td>Name</td>
|
|
||||||
<td>Team</td>
|
|
||||||
<td>Paydirt Score</td>
|
|
||||||
`;
|
|
||||||
paydirtHeader.appendChild(headerRow);
|
|
||||||
|
|
||||||
paydirtBody.innerHTML = "";
|
|
||||||
let i = 0;
|
|
||||||
resultObject.matches.forEach(player => {
|
|
||||||
i++;
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
|
|
||||||
row.innerHTML = `
|
|
||||||
<td>${i}</td>
|
function openKeyDB() {
|
||||||
<td>${player.Position}</td>
|
return new Promise((resolve, reject) => {
|
||||||
<td><a href="/player/${player.PlayerID}">${player.PlayerName}</a></td>
|
const request = indexedDB.open("PasswordManagerVault", 1);
|
||||||
<td>${player.Team}</td>
|
|
||||||
<td>${Math.floor(player.PaydirtScore)}</td>
|
request.onupgradeneeded = (event) => {
|
||||||
`;
|
const db = event.target.result;
|
||||||
paydirtBody.appendChild(row);
|
if (!db.objectStoreNames.contains("cryptoKeys")) {
|
||||||
});
|
db.createObjectStore("cryptoKeys");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function updateHighest() {
|
request.onsuccess = () => resolve(request.result);
|
||||||
if (!verifyLogin()) return;
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
const tableHeader = document.querySelector("#highest thead");
|
|
||||||
const tableBody = document.querySelector("#highest tbody");
|
|
||||||
|
|
||||||
let resultObject = await postData("/getHighest", { amount: 100 }, token);
|
|
||||||
console.log(resultObject);
|
|
||||||
|
|
||||||
if (resultObject.matches.length === 0) {
|
|
||||||
alert("Error loading highest");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tableHeader.innerHTML = "";
|
async function saveEncryptionKey(cryptoKeyObject) {
|
||||||
const headerRow = document.createElement("tr");
|
const db = await openKeyDB();
|
||||||
//Object.keys(resultObject.matches[0]).forEach(attribute => {
|
return new Promise((resolve, reject) => {
|
||||||
// headerRow.innerHTML += `<td>${attribute}</td>`;
|
const transaction = db.transaction(["cryptoKeys"], "readwrite");
|
||||||
//});
|
const store = transaction.objectStore("cryptoKeys");
|
||||||
headerRow.innerHTML = `
|
|
||||||
<td>Rank</td>
|
|
||||||
<td>Position</td>
|
|
||||||
<td>Name</td>
|
|
||||||
<td>Team</td>
|
|
||||||
<td>Contract</td>
|
|
||||||
<td>Annual Salary</td>
|
|
||||||
`;
|
|
||||||
tableHeader.appendChild(headerRow);
|
|
||||||
|
|
||||||
tableBody.innerHTML = "";
|
// We store the object under the label "masterEncryptionKey"
|
||||||
let i = 0;
|
const request = store.put(cryptoKeyObject, "masterEncryptionKey");
|
||||||
resultObject.matches.forEach(player => {
|
|
||||||
i++;
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
|
|
||||||
//for (attribute in player) {
|
request.onsuccess = () => resolve(true);
|
||||||
// row.innerHTML += `<td>${player}</td>l`;
|
request.onerror = () => reject(request.error);
|
||||||
//}
|
|
||||||
row.innerHTML = `
|
|
||||||
<td>${i}</td>
|
|
||||||
<td>${player.Position}</td>
|
|
||||||
<td><a href="/player/${player.PlayerID}">${player.PlayerName}</a></td>
|
|
||||||
<td>${player.Team}</td>
|
|
||||||
<td>${formatSalary(player.TrueAvgPerYear * player.Years)}</td>
|
|
||||||
<td>${formatSalary(player.TrueAvgPerYear)}</td>
|
|
||||||
`;
|
|
||||||
tableBody.appendChild(row);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEncryptionKey() {
|
||||||
|
const db = await openKeyDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = db.transaction(["cryptoKeys"], "readonly");
|
||||||
|
const store = transaction.objectStore("cryptoKeys");
|
||||||
|
|
||||||
|
const request = store.get("masterEncryptionKey");
|
||||||
|
|
||||||
|
request.onsuccess = () => {
|
||||||
|
if (request.result) {
|
||||||
|
resolve(request.result);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
async function updateFavorites() {
|
|
||||||
if (!verifyLogin()) return;
|
|
||||||
|
|
||||||
const tableHeader = document.querySelector("#favorites thead");
|
|
||||||
const tableBody = document.querySelector("#favorites tbody");
|
|
||||||
|
|
||||||
let resultObject = await postData("/getWatchlist", {}, token);
|
|
||||||
|
|
||||||
tableHeader.innerHTML = "";
|
|
||||||
const headerRow = document.createElement("tr");
|
|
||||||
//Object.keys(resultObject.matches[0]).forEach(attribute => {
|
|
||||||
// headerRow.innerHTML += `<td>${attribute}</td>`;
|
|
||||||
//});
|
|
||||||
headerRow.innerHTML = `
|
|
||||||
<td>Rank</td>
|
|
||||||
<td>Position</td>
|
|
||||||
<td>Name</td>
|
|
||||||
<td>Team</td>
|
|
||||||
<td>Remove</td>
|
|
||||||
`;
|
|
||||||
tableHeader.appendChild(headerRow);
|
|
||||||
|
|
||||||
tableBody.innerHTML = "";
|
|
||||||
console.log(resultObject.watchlist);
|
|
||||||
let i = 0;
|
|
||||||
resultObject.watchlist.forEach(player => {
|
|
||||||
i++;
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
|
|
||||||
row.innerHTML = `
|
|
||||||
<td>${i}</td>
|
|
||||||
<td>${player.Position}</td>
|
|
||||||
<td><a href="/player/${player.PlayerID}">${player.PlayerName}</a></td>
|
|
||||||
<td>${player.Team}</td>
|
|
||||||
<td><a href="#" class="remove" data-id="${player.PlayerID}">Stop Watching</a></td>
|
|
||||||
`;
|
|
||||||
tableBody.appendChild(row);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll(".remove").forEach(element => {
|
function bufferToBase64(buffer) {
|
||||||
element.addEventListener("click", async e => {
|
const bytes = new Uint8Array(buffer);
|
||||||
postData("/toggleWatched", { id: element.dataset.id }, token);
|
let binary = '';
|
||||||
element.closest("tr").remove();
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
})
|
binary += String.fromCharCode(bytes[i]);
|
||||||
});
|
}
|
||||||
};
|
return window.btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToBuffer(base64) {
|
||||||
|
const binaryString = window.atob(base64);
|
||||||
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|||||||
118
public/login.js
118
public/login.js
@@ -6,11 +6,28 @@ const error = document.querySelector(".error");
|
|||||||
const errorMessage = document.querySelector(".error p");
|
const errorMessage = document.querySelector(".error p");
|
||||||
const success = document.querySelector(".success");
|
const success = document.querySelector(".success");
|
||||||
|
|
||||||
|
openKeyDB();
|
||||||
|
|
||||||
if (loginForm) loginForm.onsubmit = async e => {
|
if (loginForm) loginForm.onsubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
let saltObject = await postData("/getSalt", {
|
||||||
|
username: document.getElementById("logUser").value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (saltObject.message == "User not found") {
|
||||||
|
errorMessage.innerHTML = saltObject.message;
|
||||||
|
error.style.display = "flex";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { authenticationKeyString, encryptionKeyObject } = await generateKeys(document.getElementById("logPass").value, hexToUint8Array(saltObject.salt));
|
||||||
|
|
||||||
|
saveEncryptionKey(encryptionKeyObject);
|
||||||
|
|
||||||
let resultObject = await postData("/login", {
|
let resultObject = await postData("/login", {
|
||||||
username: document.getElementById("logUser").value,
|
username: document.getElementById("logUser").value,
|
||||||
password: document.getElementById("logPass").value
|
password: authenticationKeyString
|
||||||
});
|
});
|
||||||
if (resultObject.message.includes("success")) {
|
if (resultObject.message.includes("success")) {
|
||||||
error.style.display = "none";
|
error.style.display = "none";
|
||||||
@@ -24,3 +41,102 @@ if (loginForm) loginForm.onsubmit = async e => {
|
|||||||
localStorage.setItem("token", resultObject.token);
|
localStorage.setItem("token", resultObject.token);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function generateKeys(password, salt) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
const base = await window.crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
encoder.encode(password),
|
||||||
|
"PBKDF2",
|
||||||
|
false,
|
||||||
|
["deriveBits", "deriveKey"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const authSalt = new Uint8Array([...salt, ...encoder.encode("auth")]);
|
||||||
|
const authKeyBits = await window.crypto.subtle.deriveBits(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: authSalt,
|
||||||
|
iterations: 100000,
|
||||||
|
hash: "SHA-256"
|
||||||
|
},
|
||||||
|
base,
|
||||||
|
256
|
||||||
|
);
|
||||||
|
|
||||||
|
const encryptionSalt = new Uint8Array([...salt, ...encoder.encode("enc")]);
|
||||||
|
const encryptionKey = await window.crypto.subtle.deriveKey(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: encryptionSalt,
|
||||||
|
iterations: 100000,
|
||||||
|
hash: "SHA-256"
|
||||||
|
},
|
||||||
|
base,
|
||||||
|
{ name: "AES-GCM", length: 256 },
|
||||||
|
false,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(convertBufferToHex(authKeyBits));
|
||||||
|
console.log(encryptionKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
authenticationKeyString: convertBufferToHex(authKeyBits),
|
||||||
|
encryptionKeyObject: encryptionKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openKeyDB() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open("PasswordManagerVault", 1);
|
||||||
|
|
||||||
|
request.onupgradeneeded = (event) => {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains("cryptoKeys")) {
|
||||||
|
db.createObjectStore("cryptoKeys");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEncryptionKey(cryptoKeyObject) {
|
||||||
|
const db = await openKeyDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = db.transaction(["cryptoKeys"], "readwrite");
|
||||||
|
const store = transaction.objectStore("cryptoKeys");
|
||||||
|
|
||||||
|
// We store the object under the label "masterEncryptionKey"
|
||||||
|
const request = store.put(cryptoKeyObject, "masterEncryptionKey");
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve(true);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBufferToHex(buffer) {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
return Array.from(bytes)
|
||||||
|
.map(byte => byte.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToUint8Array(hexString) {
|
||||||
|
if (hexString.length % 2 !== 0) {
|
||||||
|
throw new Error("Invalid hex string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const array = new Uint8Array(hexString.length / 2);
|
||||||
|
|
||||||
|
for (let i = 0; i < hexString.length; i += 2) {
|
||||||
|
array[i / 2] = parseInt(hexString.substring(i, i + 2), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
74
public/paydirt.html
Normal file
74
public/paydirt.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Home</title>
|
||||||
|
<link rel="stylesheet" href="../assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="logo-container">
|
||||||
|
<h2 class="logo">PayDirt</h2>
|
||||||
|
<p class="subtitle">NFL Stats</p>
|
||||||
|
</div>
|
||||||
|
<a class="button" href="/search">Search Players</a>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<a class="user-FirstName button" href="/info">Welcome, %</a>
|
||||||
|
<button id="logoutButton">Log Out</button>
|
||||||
|
<button id="deleteButton" style="display: none;">Delete Account</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="box nw">
|
||||||
|
<p><b>Watched Players</b></p>
|
||||||
|
<table class="body" id="favorites">
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="box ne">
|
||||||
|
<p><b>Highest Contracts</b></p>
|
||||||
|
<table class="body" id="highest">
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="box sw">
|
||||||
|
<div class="cursor-question">
|
||||||
|
<p><b>Highest PayDirt Score</b></p>
|
||||||
|
<span class="tooltip-text">A player's <b>PayDirt score</b> is calculated by weighting their offense score by their annual salary. It is used to evaluate the worth of their contract.</span>
|
||||||
|
</div>
|
||||||
|
<table class="body" id="paydirt">
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="box se">
|
||||||
|
<div class="cursor-question">
|
||||||
|
<p><b>Highest Offense Score</b></p>
|
||||||
|
<span class="tooltip-text">A player's <b>offense score</b> is calculated by combining several of their offense stats into a weighted score. It is indicative of their overall performance.</span>
|
||||||
|
</div>
|
||||||
|
<table class="body" id="offense">
|
||||||
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="variables.js"></script>
|
||||||
|
<script type="module" src="home.js"></script>
|
||||||
|
<script type="module" src="logout.js"></script>
|
||||||
|
<script type="module" src="delete.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -8,16 +8,27 @@ const success = document.querySelector(".success");
|
|||||||
|
|
||||||
if (registerForm) registerForm.onsubmit = async e => {
|
if (registerForm) registerForm.onsubmit = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (registerForm.regPass.value !== registerForm.regVerify.value) {
|
if (registerForm.regPass.value !== registerForm.regVerify.value) {
|
||||||
error.style.display = "flex";
|
error.style.display = "flex";
|
||||||
errorMessage.innerHTML = "Passwords must match";
|
errorMessage.innerHTML = "Passwords must match";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const salt = window.crypto.getRandomValues(new Uint8Array(16));
|
||||||
|
|
||||||
|
|
||||||
|
const { authenticationKeyString, encryptionKeyObject } = await generateKeys(registerForm.regPass.value, salt);
|
||||||
|
|
||||||
|
console.log(authenticationKeyString);
|
||||||
|
console.log(encryptionKeyObject);
|
||||||
|
|
||||||
|
|
||||||
let resultObject = await postData("/register", {
|
let resultObject = await postData("/register", {
|
||||||
username: registerForm.regUser.value,
|
username: registerForm.regUser.value,
|
||||||
password: registerForm.regPass.value,
|
password: authenticationKeyString,
|
||||||
role: registerForm.regRole.value
|
role: registerForm.regRole.value,
|
||||||
|
salt: convertBufferToHex(salt)
|
||||||
});
|
});
|
||||||
if (resultObject.message.includes("User registered")) {
|
if (resultObject.message.includes("User registered")) {
|
||||||
error.style.display = "none";
|
error.style.display = "none";
|
||||||
@@ -30,3 +41,57 @@ if (registerForm) registerForm.onsubmit = async e => {
|
|||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function generateKeys(password, salt) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
const base = await window.crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
encoder.encode(password),
|
||||||
|
"PBKDF2",
|
||||||
|
false,
|
||||||
|
["deriveBits", "deriveKey"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const authSalt = new Uint8Array([...salt, ...encoder.encode("auth")]);
|
||||||
|
const authKeyBits = await window.crypto.subtle.deriveBits(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: authSalt,
|
||||||
|
iterations: 100000,
|
||||||
|
hash: "SHA-256"
|
||||||
|
},
|
||||||
|
base,
|
||||||
|
256
|
||||||
|
);
|
||||||
|
|
||||||
|
const encryptionSalt = new Uint8Array([...salt, ...encoder.encode("enc")]);
|
||||||
|
const encryptionKey = await window.crypto.subtle.deriveKey(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: encryptionSalt,
|
||||||
|
iterations: 100000,
|
||||||
|
hash: "SHA-256"
|
||||||
|
},
|
||||||
|
base,
|
||||||
|
{ name: "AES-GCM", length: 256 },
|
||||||
|
false,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(convertBufferToHex(authKeyBits));
|
||||||
|
console.log(encryptionKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
authenticationKeyString: convertBufferToHex(authKeyBits),
|
||||||
|
encryptionKeyObject: encryptionKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBufferToHex(buffer) {
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
return Array.from(bytes)
|
||||||
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
408
server.js
408
server.js
@@ -38,23 +38,25 @@ if (!JWT_SECRET) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.post("/register", async (req, res) => {
|
app.post("/register", async (req, res) => {
|
||||||
const { username, password, role } = req.body;
|
const { username, password, role, salt } = req.body;
|
||||||
try {
|
try {
|
||||||
const hash = await bcrypt.hash(password, 10);
|
const hash = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
username: username,
|
username: username,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
role: role
|
role: role,
|
||||||
|
salt: salt
|
||||||
}
|
}
|
||||||
const query = db.prepare("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, datetime('now'))");
|
const query = db.prepare("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt, Salt) VALUES (@username, @hash, @role, datetime('now'), @salt)");
|
||||||
query.run(inputs);
|
query.run(inputs);
|
||||||
|
|
||||||
res.send({ success: true, message: "User registered" })
|
res.send({ success: true, message: "User registered" })
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message.includes("Violation of UNIQUE KEY constraint")) {
|
if (err.message.includes("UNIQUE")) {
|
||||||
res.status(500).send({ success: false, message: `Username "${username}" is already taken.` });
|
res.status(500).send({ success: false, message: `Username "${username}" is already taken.` });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
res.status(500).send({ success: false, message: err.message });
|
res.status(500).send({ success: false, message: err.message });
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,7 @@ app.post("/register", async (req, res) => {
|
|||||||
|
|
||||||
app.post("/login", async (req, res) => {
|
app.post("/login", async (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
try {
|
//try {
|
||||||
const query = db.prepare("SELECT * FROM Users WHERE Username = @username");
|
const query = db.prepare("SELECT * FROM Users WHERE Username = @username");
|
||||||
const results = query.all({ username: username });
|
const results = query.all({ username: username });
|
||||||
|
|
||||||
@@ -82,7 +84,8 @@ app.post("/login", async (req, res) => {
|
|||||||
res.send({
|
res.send({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Login successful",
|
message: "Login successful",
|
||||||
token
|
token,
|
||||||
|
salt: results[0].Salt
|
||||||
});
|
});
|
||||||
const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username");
|
const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username");
|
||||||
update.run({ username: username });
|
update.run({ username: username });
|
||||||
@@ -90,11 +93,97 @@ app.post("/login", async (req, res) => {
|
|||||||
console.log("Issued token: " + JSON.stringify(token))
|
console.log("Issued token: " + JSON.stringify(token))
|
||||||
}
|
}
|
||||||
else res.status(401).send({ success: false, message: "Invalid credentials" });
|
else res.status(401).send({ success: false, message: "Invalid credentials" });
|
||||||
|
//} catch (err) {
|
||||||
|
// res.status(500).send({ success: false, message: err.message });
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/getSalt", async (req, res) => {
|
||||||
|
const { username } = req.body;
|
||||||
|
try {
|
||||||
|
const query = db.prepare("SELECT * FROM Users WHERE Username = @username");
|
||||||
|
const results = query.all({ username: username });
|
||||||
|
|
||||||
|
if (results.length == 0) return res.status(400).send({ message: "User not found" });
|
||||||
|
|
||||||
|
const isDeleted = results[0].IsDeleted;
|
||||||
|
|
||||||
|
if (isDeleted === true) {
|
||||||
|
return res.status(400).json({ message: "User not found (deleted)" })
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
salt: results[0].Salt
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).send({ success: false, message: err.message });
|
res.status(500).send({ success: false, message: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post("/setPassword", authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, iv, password } = req.body;
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
name: name,
|
||||||
|
userID: req.user.Id,
|
||||||
|
iv: iv,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = db.prepare("INSERT INTO Passwords (Name, UserID, Password, IV) VALUES (@name, @userID, @password, @iv)");
|
||||||
|
query.run(inputs);
|
||||||
|
|
||||||
|
res.status(200).json({ success: true, message: "Success" });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ success: false, message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/updatePassword", authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, iv, password } = req.body;
|
||||||
|
|
||||||
|
const inputs = {
|
||||||
|
name: name,
|
||||||
|
userID: req.user.Id,
|
||||||
|
iv: iv,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = db.prepare("UPDATE Passwords SET Password = @password, IV = @iv WHERE UserID = @userID AND Name = @name");
|
||||||
|
query.run(inputs);
|
||||||
|
|
||||||
|
res.status(200).json({ success: true, message: "Success" });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ success: false, message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/getPassword", authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
const query = db.prepare("SELECT * FROM Passwords WHERE UserID = @userID AND Name = @name");
|
||||||
|
const results = query.all({ userID: req.user.Id, name: name});
|
||||||
|
|
||||||
|
if (results.length == 0) {
|
||||||
|
res.status(500).json({ success: false, message: `No password with name ${name}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length > 1) {
|
||||||
|
res.status(500).json({ success: false, message: `More than one password with name ${name}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ password: results[0].Password, iv: results[0].IV, success: true, message: "Success" });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ success: false, message: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
app.post("/getWatchlist", authenticate, async (req, res) => {
|
app.post("/getWatchlist", authenticate, async (req, res) => {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
|
|
||||||
@@ -108,319 +197,12 @@ app.post("/getWatchlist", authenticate, async (req, res) => {
|
|||||||
res.status(200).json({ watchlist: watchlist });
|
res.status(200).json({ watchlist: watchlist });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/isWatched", authenticate, async (req, res) => {
|
|
||||||
const { id } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`);
|
|
||||||
const watchlist = query.all({ userID: req.user.Id, id: id});
|
|
||||||
|
|
||||||
const isWatched = watchlist.some(row => row.PlayerID === parseInt(id));
|
|
||||||
|
|
||||||
res.status(200).json({ isWatched: isWatched });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/toggleWatched", authenticate, async (req, res) => {
|
|
||||||
const { id } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`);
|
|
||||||
const watchlist = query.all({ userID: req.user.Id, id: id });
|
|
||||||
|
|
||||||
const isWatched = watchlist.some(row => row.PlayerID === parseInt(id));
|
|
||||||
|
|
||||||
if (isWatched) {
|
|
||||||
const query = db.prepare(`DELETE FROM Watch WHERE UserID = @userID AND PlayerID = @id`);
|
|
||||||
query.run({ userID: req.user.Id, id: id });
|
|
||||||
|
|
||||||
res.status(200).json({ message: "No longer watching player" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, watch the player
|
|
||||||
const watchQuery = db.prepare(`INSERT INTO Watch (UserID, PlayerID) VALUES (@userID, @id)`);
|
|
||||||
watchQuery.run({ userID: req.user.Id, id: id });
|
|
||||||
|
|
||||||
res.status(200).json({ message: "Watching Player" });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getPlayers", authenticate, async (req, res) => {
|
|
||||||
const { player, positions } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`SELECT p.PlayerID, p.PlayerName, c.TotalValue, p.Team, p.Position FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID WHERE p.PlayerName LIKE '%' || @query || '%' AND p.Position IN (@one, @two, @three, @four) ORDER BY p.PlayerName;`);
|
|
||||||
const matches = query.all({
|
|
||||||
query: player,
|
|
||||||
one: positions[0],
|
|
||||||
two: positions[1],
|
|
||||||
three: positions[2],
|
|
||||||
four: positions[3]
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).json({ query: player, matches: matches });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getHighest", authenticate, async (req, res) => {
|
|
||||||
const { amount } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`
|
|
||||||
SELECT p.PlayerID, p.PlayerName, p.[Position], p.Team, TotalValue, TrueAvgPerYear, Years
|
|
||||||
FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID
|
|
||||||
ORDER BY TotalValue DESC
|
|
||||||
LIMIT @amount;
|
|
||||||
`);
|
|
||||||
const matches = query.all({ amount: amount });
|
|
||||||
|
|
||||||
res.status(200).json({ matches: matches });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getHighestOffense", authenticate, async (req, res) => {
|
|
||||||
const { amount } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`
|
|
||||||
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
|
|
||||||
SUM(total_yards) AS TotalYards,
|
|
||||||
SUM(passing_yards) AS PassingYards,
|
|
||||||
SUM(rushing_yards) AS RushingYards,
|
|
||||||
SUM(receiving_yards) AS RecievingYards,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
|
|
||||||
SUM(receiving_touchdown) AS ReceivingTDs,
|
|
||||||
SUM(rush_touchdown) AS RushTDs,
|
|
||||||
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
|
|
||||||
SUM(interception) + sum(fumble_lost) AS Turnovers,
|
|
||||||
SUM(tackled_for_loss) AS TackledForLoss,
|
|
||||||
CASE
|
|
||||||
WHEN Player.Position = 'QB'
|
|
||||||
THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
|
|
||||||
ELSE 0
|
|
||||||
END AS Sacks,
|
|
||||||
SUM(safety) AS Safties,
|
|
||||||
|
|
||||||
SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss))
|
|
||||||
|
|
||||||
- (SUM(safety) * 100.0)
|
|
||||||
AS OffenseScore,
|
|
||||||
|
|
||||||
AvgPerYear,
|
|
||||||
(SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss)
|
|
||||||
)
|
|
||||||
- (SUM(safety) * 100.0)) / AvgPerYear AS PaydirtScore
|
|
||||||
|
|
||||||
FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract ON Player.PlayerID = Contract.PlayerID
|
|
||||||
WHERE season = 2024 AND SeasonType = 'REG'
|
|
||||||
GROUP BY Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], Contract.AvgPerYear
|
|
||||||
ORDER BY OffenseScore DESC
|
|
||||||
LIMIT @amount;
|
|
||||||
`);
|
|
||||||
const matches = query.all({ amount: amount });
|
|
||||||
|
|
||||||
res.status(200).json({ matches: matches });
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.post("/getPlayerStats", authenticate, async (req, res) => {
|
|
||||||
const { playerID } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`
|
|
||||||
SELECT player.playerid, player.playername, season, seasontype, week, pass_attempts, complete_pass, total_yards, total_tds, interception, receptions, receiving_yards, receiving_touchdown, rush_attempts, rushing_yards, rush_touchdown, fumble
|
|
||||||
fROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID
|
|
||||||
WHERE Player.PlayerID = @playerID
|
|
||||||
ORDER BY Season, week;
|
|
||||||
`);
|
|
||||||
const matches = query.all({ playerID: playerID })
|
|
||||||
|
|
||||||
res.status(200).json({ matches: matches });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getPlayerScores", authenticate, async (req, res) => {
|
|
||||||
const { playerID } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`
|
|
||||||
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
|
|
||||||
c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, c.EndYear, Player.Height, Player.Weight,
|
|
||||||
SUM(total_yards) AS TotalYards,
|
|
||||||
SUM(passing_yards) AS PassingYards,
|
|
||||||
SUM(rushing_yards) AS RushingYards,
|
|
||||||
SUM(receiving_yards) AS RecievingYards,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
|
|
||||||
SUM(receiving_touchdown) AS ReceivingTDs,
|
|
||||||
SUM(rush_touchdown) AS RushTDs,
|
|
||||||
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
|
|
||||||
SUM(interception) + sum(fumble_lost) AS Turnovers,
|
|
||||||
SUM(tackled_for_loss) AS TackledForLoss,
|
|
||||||
CASE
|
|
||||||
WHEN Player.Position = 'QB'
|
|
||||||
THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
|
|
||||||
ELSE 0
|
|
||||||
END AS Sacks,
|
|
||||||
SUM(safety) AS Safties,
|
|
||||||
|
|
||||||
SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss)
|
|
||||||
)
|
|
||||||
|
|
||||||
- (SUM(safety) * 100.0)
|
|
||||||
AS OffenseScore,
|
|
||||||
|
|
||||||
AvgPerYear,
|
|
||||||
(SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss)
|
|
||||||
)
|
|
||||||
- (SUM(safety) * 100.0)) / AvgPerYear * 1000000 AS PaydirtScore
|
|
||||||
|
|
||||||
|
|
||||||
FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract AS c ON Player.PlayerID = c.PlayerID
|
|
||||||
WHERE season = 2024 AND SeasonType = 'REG' AND Player.PlayerID = @playerID
|
|
||||||
GROUP BY c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, Player.Height, Player.Weight, c.EndYear, Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], c.AvgPerYear
|
|
||||||
ORDER BY PaydirtScore DESC;
|
|
||||||
`);
|
|
||||||
const matches = query.all({ playerID: playerID })
|
|
||||||
|
|
||||||
res.status(200).json({ match: matches[0] });
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.post("/getHighestPaydirt", authenticate, async (req, res) => {
|
|
||||||
const { amount } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`
|
|
||||||
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
|
|
||||||
SUM(total_yards) AS TotalYards,
|
|
||||||
SUM(passing_yards) AS PassingYards,
|
|
||||||
SUM(rushing_yards) AS RushingYards,
|
|
||||||
SUM(receiving_yards) AS RecievingYards,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
|
|
||||||
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
|
|
||||||
SUM(receiving_touchdown) AS ReceivingTDs,
|
|
||||||
SUM(rush_touchdown) AS RushTDs,
|
|
||||||
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
|
|
||||||
SUM(interception) + sum(fumble_lost) AS Turnovers,
|
|
||||||
SUM(tackled_for_loss) AS TackledForLoss,
|
|
||||||
CASE
|
|
||||||
WHEN Player.Position = 'QB'
|
|
||||||
THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
|
|
||||||
ELSE 0
|
|
||||||
END AS Sacks,
|
|
||||||
SUM(safety) AS Safties,
|
|
||||||
|
|
||||||
SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss))
|
|
||||||
|
|
||||||
- (SUM(safety) * 100.0)
|
|
||||||
AS OffenseScore,
|
|
||||||
|
|
||||||
AvgPerYear,
|
|
||||||
(SUM(total_yards)
|
|
||||||
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
|
|
||||||
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
|
|
||||||
- ((SUM(interception) + sum(fumble_lost)) * 75)
|
|
||||||
- (
|
|
||||||
SUM(tackled_for_loss)
|
|
||||||
)
|
|
||||||
|
|
||||||
- (SUM(safety) * 100.0)) / AvgPerYear * 1000000 AS PaydirtScore
|
|
||||||
|
|
||||||
|
|
||||||
FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract ON Player.PlayerID = Contract.PlayerID
|
|
||||||
WHERE season = 2024 AND SeasonType = 'REG'
|
|
||||||
GROUP BY Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], Contract.AvgPerYear
|
|
||||||
ORDER BY PaydirtScore DESC
|
|
||||||
LIMIT @amount;
|
|
||||||
`);
|
|
||||||
const matches = query.all({ amount: amount });
|
|
||||||
|
|
||||||
res.status(200).json({ matches: matches });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getPlayer", authenticate, async (req, res) => {
|
|
||||||
const { id } = req.body;
|
|
||||||
|
|
||||||
const query = db.prepare(`SELECT p.PlayerName, p.PlayerID, c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, c.EndYear, p.Team, p.Position FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID WHERE p.PlayerID = @query`);
|
|
||||||
const matches = query.run({ query: id });
|
|
||||||
|
|
||||||
if (matches.length !== 1) {
|
|
||||||
res.status(400).json({ success: false })
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.status(200).json({ success: true, match: matches[0] });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/getInfo", authenticate, async (req, res) => {
|
app.post("/getInfo", authenticate, async (req, res) => {
|
||||||
const userData = req.user;
|
const userData = req.user;
|
||||||
|
|
||||||
res.status(200).json(userData);
|
res.status(200).json(userData);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/getCourses", authenticate, async (req, res) => {
|
|
||||||
const query = db.prepare("SELECT * FROM Courses");
|
|
||||||
const courses = query.all();
|
|
||||||
|
|
||||||
res.status(200).json(courses);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/setInfo", authenticate, async (req, res) => {
|
|
||||||
const { firstName, lastName, dob } = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const query = db.prepare(`
|
|
||||||
UPDATE Users
|
|
||||||
SET FirstName = @firstName,
|
|
||||||
LastName = @lastName,
|
|
||||||
DOB = @dob
|
|
||||||
WHERE Username = @username
|
|
||||||
`);
|
|
||||||
query.run({
|
|
||||||
username: req.user.Username,
|
|
||||||
firstName: firstName,
|
|
||||||
lastName: lastName,
|
|
||||||
dob: dob || null
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
if (error.message.includes("failed for parameter 'dob'.")) {
|
|
||||||
res.status(500).json({ message: "Must input date of birth" })
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.status(500).json({ message: "Update request failed" })
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedUser = req.user;
|
|
||||||
updatedUser.FirstName = firstName;
|
|
||||||
updatedUser.LastName = lastName;
|
|
||||||
updatedUser.DOB = dob;
|
|
||||||
|
|
||||||
const token = jwt.sign(updatedUser, JWT_SECRET);
|
|
||||||
console.log("Issued token: " + JSON.stringify(token))
|
|
||||||
res.status(200).send({
|
|
||||||
success: true,
|
|
||||||
message: "Information updated successfully",
|
|
||||||
token
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/delete", authenticate, async (req, res) => {
|
app.post("/delete", authenticate, async (req, res) => {
|
||||||
let { username, actor } = req.body;
|
let { username, actor } = req.body;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user