diff --git a/project.db b/project.db index 5fb1d39..780a58c 100644 Binary files a/project.db and b/project.db differ diff --git a/project.db-shm b/project.db-shm index cff3ff4..e78583a 100644 Binary files a/project.db-shm and b/project.db-shm differ diff --git a/project.db-wal b/project.db-wal index 3a8c7ca..6da7dfa 100644 Binary files a/project.db-wal and b/project.db-wal differ diff --git a/public/assets/css/oldstyle.css b/public/assets/css/styleold.css similarity index 100% rename from public/assets/css/oldstyle.css rename to public/assets/css/styleold.css diff --git a/public/home.html b/public/home.html index 71ce6e6..2c438aa 100644 --- a/public/home.html +++ b/public/home.html @@ -1,74 +1,39 @@ - Home + "Password Manager" -
-
-
-
- -

NFL Stats

-
- Search Players -
+
+
+

Personal Information

+
+
+

Password Name

+
-
- Welcome, % - - +
+

Password:

+
+
+

Set password?

+ +
+ + Log Out + +
+

Error Message

-
-

Watched Players

- - - - - -
-
-
-

Highest Contracts

- - - - - -
-
-
-
-

Highest PayDirt Score

- A player's PayDirt score is calculated by weighting their offense score by their annual salary. It is used to evaluate the worth of their contract. -
- - - - - -
-
-
-
-

Highest Offense Score

- A player's offense score is calculated by combining several of their offense stats into a weighted score. It is indicative of their overall performance. -
- - - - - -
+
+

Password successfully retrieved

+
- - - diff --git a/public/home.js b/public/home.js index a3974cd..60f31cf 100644 --- a/public/home.js +++ b/public/home.js @@ -1,212 +1,185 @@ import { postData, verifyLogin } from "./client.js"; -import { formatSalary } from "./utils.js"; +verifyLogin(); const token = window.localStorage.getItem("token"); -if (!token) { - window.location.href = "/login"; +const infoForm = document.getElementById("infoForm"); + +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(); -updateFavorites(); -updatePaydirt(); -updateOffense(); +async function setPassword(name, password) { + const encoder = new TextEncoder(); + const encodedData = encoder.encode(password); -window.addEventListener("pageshow", e => { - if (e.persisted) { - updateFavorites(); - } -}) + const iv = window.crypto.getRandomValues(new Uint8Array(12)); -async function updateOffense() { - if (!verifyLogin()) return; + const cyphertextBuffer = await window.crypto.subtle.encrypt( + { + name: "AES-GCM", + iv: iv + }, + encryptionKey, + encodedData + ); - - const offenseHeader = document.querySelector("#offense thead"); - const offenseBody = document.querySelector("#offense tbody"); - - - let resultObject = await postData("/getHighestOffense", { amount: 100 }, token); - console.log(resultObject); - - if (resultObject.matches.length === 0) { - alert("Error loading scores"); - return; + const data = { + name: name, + iv: bufferToBase64(iv), + password: bufferToBase64(cyphertextBuffer) } - offenseHeader.innerHTML = ""; - const headerRow = document.createElement("tr"); - //Object.keys(resultObject.matches[0]).forEach(attribute => { - // headerRow.innerHTML += `${attribute}`; - //}); - headerRow.innerHTML = ` - Rank - Position - Name - Team - Offense Score - `; - offenseHeader.appendChild(headerRow); + var resultObject = await postData("/setPassword", data, token); - offenseBody.innerHTML = ""; - let i = 0; - resultObject.matches.forEach(player => { - i++; - const row = document.createElement("tr"); - - row.innerHTML = ` - ${i} - ${player.Position} - ${player.PlayerName} - ${player.Team} - ${Math.floor(player.OffenseScore)} - `; - offenseBody.appendChild(row); - }); -}; - -async function updatePaydirt() { - if (!verifyLogin()) return; - - - 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; + if (resultObject.message.includes("UNIQUE")) { + resultObject = await postData("/updatePassword", data, token); } - paydirtHeader.innerHTML = ""; - const headerRow = document.createElement("tr"); - //Object.keys(resultObject.matches[0]).forEach(attribute => { - // headerRow.innerHTML += `${attribute}`; - //}); - headerRow.innerHTML = ` - Rank - Position - Name - Team - Paydirt Score - `; - paydirtHeader.appendChild(headerRow); + if (resultObject.success) { + 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"; + } +} - paydirtBody.innerHTML = ""; - let i = 0; - resultObject.matches.forEach(player => { - i++; - const row = document.createElement("tr"); +async function getPassword(name) { + const data = { + name: name + } + const resultObject = await postData("/getPassword", data, token); - row.innerHTML = ` - ${i} - ${player.Position} - ${player.PlayerName} - ${player.Team} - ${Math.floor(player.PaydirtScore)} - `; - paydirtBody.appendChild(row); - }); -}; - -async function updateHighest() { - if (!verifyLogin()) return; - - 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; + if (resultObject.success) { + error.style.display = "none"; + success.style.display = "flex"; + } else { + errorMessage.innerHTML = resultObject.message; + success.style.display = "none"; + error.style.display = "flex"; + return; } - tableHeader.innerHTML = ""; - const headerRow = document.createElement("tr"); - //Object.keys(resultObject.matches[0]).forEach(attribute => { - // headerRow.innerHTML += `${attribute}`; - //}); - headerRow.innerHTML = ` - Rank - Position - Name - Team - Contract - Annual Salary - `; - tableHeader.appendChild(headerRow); + const ivBuffer = base64ToBuffer(resultObject.iv); + const cyphertextBuffer = base64ToBuffer(resultObject.password); - tableBody.innerHTML = ""; - let i = 0; - resultObject.matches.forEach(player => { - i++; - const row = document.createElement("tr"); - - //for (attribute in player) { - // row.innerHTML += `${player}l`; - //} - row.innerHTML = ` - ${i} - ${player.Position} - ${player.PlayerName} - ${player.Team} - ${formatSalary(player.TrueAvgPerYear * player.Years)} - ${formatSalary(player.TrueAvgPerYear)} - `; - tableBody.appendChild(row); - }); -}; - -async function updateFavorites() { - if (!verifyLogin()) return; - - const tableHeader = document.querySelector("#favorites thead"); - const tableBody = document.querySelector("#favorites tbody"); + const decryptedBuffer = await window.crypto.subtle.decrypt( + { + name: "AES-GCM", + iv: ivBuffer + }, + encryptionKey, + cyphertextBuffer + ); - let resultObject = await postData("/getWatchlist", {}, token); + const decoder = new TextDecoder(); + const pw = decoder.decode(decryptedBuffer); + document.getElementById("password").value = pw; + //console.log("Password: " + pw); - tableHeader.innerHTML = ""; - const headerRow = document.createElement("tr"); - //Object.keys(resultObject.matches[0]).forEach(attribute => { - // headerRow.innerHTML += `${attribute}`; - //}); - headerRow.innerHTML = ` - Rank - Position - Name - Team - Remove - `; - tableHeader.appendChild(headerRow); + //console.log(resultObject); - tableBody.innerHTML = ""; - console.log(resultObject.watchlist); - let i = 0; - resultObject.watchlist.forEach(player => { - i++; - const row = document.createElement("tr"); + successMessage.innerHTML = `Password for "${name}" is "${pw}"` +} - row.innerHTML = ` - ${i} - ${player.Position} - ${player.PlayerName} - ${player.Team} - Stop Watching - `; - tableBody.appendChild(row); + + + +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); }); +} - document.querySelectorAll(".remove").forEach(element => { - element.addEventListener("click", async e => { - postData("/toggleWatched", { id: element.dataset.id }, token); - element.closest("tr").remove(); - }) +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); }); -}; +} + +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); + }); +} + +function bufferToBase64(buffer) { + const bytes = new Uint8Array(buffer); + let binary = ''; + 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; +} diff --git a/public/login.js b/public/login.js index 7f36992..205aa49 100644 --- a/public/login.js +++ b/public/login.js @@ -6,11 +6,28 @@ const error = document.querySelector(".error"); const errorMessage = document.querySelector(".error p"); const success = document.querySelector(".success"); +openKeyDB(); + if (loginForm) loginForm.onsubmit = async e => { 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", { username: document.getElementById("logUser").value, - password: document.getElementById("logPass").value + password: authenticationKeyString }); if (resultObject.message.includes("success")) { error.style.display = "none"; @@ -24,3 +41,102 @@ if (loginForm) loginForm.onsubmit = async e => { 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; +} + diff --git a/public/paydirt.html b/public/paydirt.html new file mode 100644 index 0000000..71ce6e6 --- /dev/null +++ b/public/paydirt.html @@ -0,0 +1,74 @@ + + + + Home + + + +
+
+
+
+ +

NFL Stats

+
+ Search Players +
+
+
+ Welcome, % + + +
+ +
+
+

Watched Players

+ + + + + +
+
+
+

Highest Contracts

+ + + + + +
+
+
+
+

Highest PayDirt Score

+ A player's PayDirt score is calculated by weighting their offense score by their annual salary. It is used to evaluate the worth of their contract. +
+ + + + + +
+
+
+
+

Highest Offense Score

+ A player's offense score is calculated by combining several of their offense stats into a weighted score. It is indicative of their overall performance. +
+ + + + + +
+
+
+ + + + + + + diff --git a/public/register.js b/public/register.js index f96cb2e..25408df 100644 --- a/public/register.js +++ b/public/register.js @@ -8,16 +8,27 @@ const success = document.querySelector(".success"); if (registerForm) registerForm.onsubmit = async e => { e.preventDefault(); + if (registerForm.regPass.value !== registerForm.regVerify.value) { error.style.display = "flex"; errorMessage.innerHTML = "Passwords must match"; 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", { username: registerForm.regUser.value, - password: registerForm.regPass.value, - role: registerForm.regRole.value + password: authenticationKeyString, + role: registerForm.regRole.value, + salt: convertBufferToHex(salt) }); if (resultObject.message.includes("User registered")) { error.style.display = "none"; @@ -30,3 +41,57 @@ if (registerForm) registerForm.onsubmit = async e => { 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(''); +} + diff --git a/server.js b/server.js index 2812eb0..98bafde 100644 --- a/server.js +++ b/server.js @@ -38,23 +38,25 @@ if (!JWT_SECRET) { } app.post("/register", async (req, res) => { - const { username, password, role } = req.body; + const { username, password, role, salt } = req.body; try { const hash = await bcrypt.hash(password, 10); const inputs = { username: username, 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); res.send({ success: true, message: "User registered" }) } 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.` }); + return; } 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) => { const { username, password } = req.body; - try { + //try { const query = db.prepare("SELECT * FROM Users WHERE Username = @username"); const results = query.all({ username: username }); @@ -82,7 +84,8 @@ app.post("/login", async (req, res) => { res.send({ success: true, message: "Login successful", - token + token, + salt: results[0].Salt }); const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username"); update.run({ username: username }); @@ -90,11 +93,97 @@ app.post("/login", async (req, res) => { console.log("Issued token: " + JSON.stringify(token)) } 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) { 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) => { const { id } = req.body; @@ -108,319 +197,12 @@ app.post("/getWatchlist", authenticate, async (req, res) => { 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) => { const userData = req.user; 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) => { let { username, actor } = req.body;