From 50bed6159d1aefa1fa3231232c1e91844e293714 Mon Sep 17 00:00:00 2001 From: RochesterX Date: Sun, 25 Jan 2026 01:36:32 -0500 Subject: [PATCH] Sqliteification --- mssql-wrapper.js | 101 ----------------------- project.db-shm | Bin 32768 -> 32768 bytes project.db-wal | Bin 24752 -> 201912 bytes server.js | 206 +++++++++++++++++++++-------------------------- 4 files changed, 93 insertions(+), 214 deletions(-) delete mode 100644 mssql-wrapper.js diff --git a/mssql-wrapper.js b/mssql-wrapper.js deleted file mode 100644 index f0dc075..0000000 --- a/mssql-wrapper.js +++ /dev/null @@ -1,101 +0,0 @@ -// Vibe coded since I needed more resources on my VPS :/ - -const Database = require('better-sqlite3'); - -// 1. Connect to the file (creates it if missing) -const db = new Database('project.db'); -db.pragma('journal_mode = WAL'); // Faster, safer writes - -class FakeMssqlRequest { - constructor(db) { - this.db = db; - this.params = {}; // Stores your inputs: .input('id', 123) - } - - // Mimic the .input() method - // We ignore 'type' because SQLite is loosely typed - input(name, type, value) { - // Handle case where value is passed as 2nd arg (omitting type) - if (value === undefined) value = type; - - this.params[name] = value; - return this; // Return 'this' to allow chaining (.input().input()) - } - - // Mimic the .query() method - async query(sqlString) { - try { - let cleanSql = sqlString; - - // --- FIX 1: Handle TOP -> LIMIT --- - // Regex detects: SELECT TOP (@amount) ... OR SELECT TOP 10 ... - // It captures the value (@amount or 10) - const topRegex = /SELECT\s+TOP\s*\(?(@?\w+)\)?/i; - const topMatch = cleanSql.match(topRegex); - - if (topMatch) { - const limitValue = topMatch[1]; // Extracts '@amount' or '10' - - // 1. Remove "TOP (@amount)" from the start - cleanSql = cleanSql.replace(topRegex, 'SELECT '); - - // 2. Remove any trailing semicolon so we can append - cleanSql = cleanSql.replace(/;\s*$/, ''); - - // 3. Append LIMIT to the end - cleanSql += ` LIMIT ${limitValue}`; - } - - // 1. Compatibility Fixes (Optional but helpful) - // Replace 'GETDATE()' with SQLite's "datetime('now')" - cleanSql = cleanSql.replace(/GETDATE\(\)/gi, "datetime('now', 'localtime')") - // MS SQL: SYSUTCDATETIME() -> SQLite: datetime('now') (Already UTC) - .replace(/SYSUTCDATETIME\(\)/gi, "datetime('now')") - // --- FIX 3: Clean up Square Brackets --- - .replace(/\[/g, '"').replace(/\]/g, '"') - - // --- FIX 4: Handle String Concatenation in LIKE clauses --- - // MSSQL: LIKE '%' + @param + '%' - // SQLite: LIKE '%' || @param || '%' - // This regex looks for: LIKE '%' + @anything + '%' - .replace(/LIKE\s+'%'\s*\+\s*(@\w+)\s*\+\s*'%'/gi, "LIKE '%' || $1 || '%'"); - // 2. Determine if it's a SELECT or INSERT/UPDATE - const stmt = this.db.prepare(cleanSql); - - if (cleanSql.trim().toUpperCase().startsWith('SELECT')) { - // READ: Use .all() - const rows = stmt.all(this.params); - return { recordset: rows, rowsAffected: [rows.length] }; - } else { - // WRITE: Use .run() - const info = stmt.run(this.params); - return { recordset: [], rowsAffected: [info.changes] }; - } - } catch (err) { - console.error("SQLite Error:", err.message); - console.error("Query was:", sqlString); - throw err; - } - } -} - -// Mimic the main 'pool' object -const pool = { - connected: true, - request: () => new FakeMssqlRequest(db), - close: () => db.close() -}; - -module.exports = { - // Mimic the sql.connect() function - connect: async () => { - console.log("Connected to SQLite (via Wrapper)"); - return pool; - }, - // Export types to prevent "sql.Int is undefined" errors - Int: 'Int', - NVarChar: 'NVarChar', - Bit: 'Bit', - DateTime: 'DateTime' -}; - diff --git a/project.db-shm b/project.db-shm index 0cb38d33607ece3dd4979f1eccdeffd3a95d94b7..cff3ff4cfdf2e0d17a5b7fac2900da0a6f33cddd 100644 GIT binary patch literal 32768 zcmeI*T`pun7zW_iUmG-fW@t_On=v!Sf7^jQxE3y~!Jb^Zp4kZ!i*QRFL|hOgB%Sp0 zC3WhYO4V0iJqxJQy4p$=wbJ^M#%}2^o5_Cj-}OYNwt0MdzF0pz8td$z{BNHf?6uxG zzsg-jmG|aJ+zpn;#>2~C>EF`oW!`vc7p2Ck&X4}CYIrT@OLdl|f&c*m1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!Cur0)6SvK)G>isNDQDoG%&4XjZbBt!!r}oxA(Go?U2= z009C72oNAZfB*pk1PBlyK!5-N0t5&U=o*2sOy+xL(#}$T#@Gnd8u%YUG5#?I05*~^(f|Me diff --git a/project.db-wal b/project.db-wal index 5e118afe55b275ac0475d5336ba3623c9fb7354c..3a8c7ca1a86efe2713d8a53c5fe50dd212d2f840 100644 GIT binary patch literal 201912 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 delta 9 Qcmdn7l4rv~#tj=102kH-pa1{> diff --git a/server.js b/server.js index c9c5176..2812eb0 100644 --- a/server.js +++ b/server.js @@ -1,9 +1,10 @@ const express = require("express"); -const sql = require("./mssql-wrapper"); +const sqlite = require("better-sqlite3"); const bcrypt = require("bcrypt"); const cors = require("cors"); const jwt = require("jsonwebtoken") -const dotenv = require("dotenv") +const dotenv = require("dotenv"); +const Database = require("better-sqlite3"); const serverPort = 3001; @@ -26,18 +27,8 @@ app.use(cors({ ] })); -let dbConfig = null; - -dbConfig = { - user: "server", - password: "TorusField1*", - server: "mssql.rochesterx.dev", - database: "Project", - options: { trustServerCertificate: true } -}; - -let pool = null; -setupPool(); +const db = new Database("project.db"); +db.pragma("journal_mode = WAL") dotenv.config(); @@ -46,20 +37,21 @@ if (!JWT_SECRET) { throw new Error("JWT_SECRET not set in environment."); } -async function setupPool() { - pool = await sql.connect(dbConfig); -} - app.post("/register", async (req, res) => { const { username, password, role } = req.body; try { const hash = await bcrypt.hash(password, 10); - await pool.request() - .input("username", sql.VarChar, username) - .input("hash", sql.VarChar, hash) - .input("role", sql.VarChar, role) - .query("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, SYSUTCDATETIME())"); + + const inputs = { + username: username, + hash: hash, + role: role + } + const query = db.prepare("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, datetime('now'))"); + query.run(inputs); + res.send({ success: true, message: "User registered" }) + } catch (err) { if (err.message.includes("Violation of UNIQUE KEY constraint")) { res.status(500).send({ success: false, message: `Username "${username}" is already taken.` }); @@ -71,14 +63,13 @@ app.post("/register", async (req, res) => { app.post("/login", async (req, res) => { const { username, password } = req.body; try { - const result = await pool.request() - .input("username", sql.VarChar, username) - .query("SELECT * FROM Users WHERE Username = @username"); + const query = db.prepare("SELECT * FROM Users WHERE Username = @username"); + const results = query.all({ username: username }); - if (result.recordset.length == 0) return res.status(400).send({ message: "User not found" }); + if (results.length == 0) return res.status(400).send({ message: "User not found" }); - const hash = result.recordset[0].PasswordHash; - const isDeleted = result.recordset[0].IsDeleted; + const hash = results[0].PasswordHash; + const isDeleted = results[0].IsDeleted; if (isDeleted === true) { return res.status(400).json({ message: "User not found (deleted)" }) @@ -87,15 +78,14 @@ app.post("/login", async (req, res) => { const match = await bcrypt.compare(password, hash); if (match){ - const token = jwt.sign(result.recordset[0], JWT_SECRET, { expiresIn: "1h" }); + const token = jwt.sign(results[0], JWT_SECRET, { expiresIn: "1h" }); res.send({ success: true, message: "Login successful", token }); - await pool.request() - .input("username", sql.VarChar, username) - .query("UPDATE Users SET LastLogin = SYSUTCDATETIME() WHERE Username = @username"); + const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username"); + update.run({ username: username }); console.log("Issued token: " + JSON.stringify(token)) } @@ -108,23 +98,23 @@ app.post("/login", async (req, res) => { app.post("/getWatchlist", authenticate, async (req, res) => { const { id } = req.body; - const watchlist = await pool.request() - .input("userID", sql.Int, req.user.Id) - .input("id", sql.Int, id) - .query(`SELECT Player.PlayerName, Player.Team, Player.Position, Player.PlayerID FROM Watch JOIN Player ON Watch.PlayerID = Player.PlayerID WHERE UserID = @userID ORDER BY Player.PlayerName`); + const query = db.prepare(`SELECT Player.PlayerName, Player.Team, Player.Position, Player.PlayerID FROM Watch JOIN Player ON Watch.PlayerID = Player.PlayerID WHERE UserID = @userID ORDER BY Player.PlayerName`); + const watchlist = query.all({ + userID: req.user.Id, + id: id + }); - res.status(200).json({ watchlist: watchlist.recordset }); + + res.status(200).json({ watchlist: watchlist }); }); app.post("/isWatched", authenticate, async (req, res) => { const { id } = req.body; - const watchlist = await pool.request() - .input("userID", sql.Int, req.user.Id) - .input("id", sql.Int, id) - .query(`SELECT PlayerID FROM Watch WHERE UserID = @userID`); + const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`); + const watchlist = query.all({ userID: req.user.Id, id: id}); - const isWatched = watchlist.recordset.some(row => row.PlayerID === parseInt(id)); + const isWatched = watchlist.some(row => row.PlayerID === parseInt(id)); res.status(200).json({ isWatched: isWatched }); }); @@ -132,30 +122,22 @@ app.post("/isWatched", authenticate, async (req, res) => { app.post("/toggleWatched", authenticate, async (req, res) => { const { id } = req.body; - const watchlist = await pool.request() - .input("userID", sql.Int, req.user.Id) - .input("id", sql.Int, id) - .query(`SELECT PlayerID FROM Watch WHERE UserID = @userID`); - console.log(watchlist.recordset); + const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`); + const watchlist = query.all({ userID: req.user.Id, id: id }); - const isWatched = watchlist.recordset.some(row => row.PlayerID === parseInt(id)); - console.log(isWatched); + const isWatched = watchlist.some(row => row.PlayerID === parseInt(id)); if (isWatched) { - const result = await pool.request() - .input("userID", sql.Int, req.user.Id) - .input("id", sql.Int, id) - .query(`DELETE FROM Watch WHERE UserID = @userID AND PlayerID = @id`); + 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 result = await pool.request() - .input("userID", sql.Int, req.user.Id) - .input("id", sql.Int, id) - .query(`INSERT INTO Watch (UserID, PlayerID) VALUES (@userID, @id)`); + 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" }); }); @@ -163,38 +145,37 @@ app.post("/toggleWatched", authenticate, async (req, res) => { app.post("/getPlayers", authenticate, async (req, res) => { const { player, positions } = req.body; - const result = await pool.request() - .input("query", sql.VarChar, player) - .input("one", sql.VarChar, positions[0]) - .input("two", sql.VarChar, positions[1]) - .input("three", sql.VarChar, positions[2]) - .input("four", sql.VarChar, positions[3]) - .query(`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 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: result.recordset }); + res.status(200).json({ query: player, matches: matches }); }); app.post("/getHighest", authenticate, async (req, res) => { const { amount } = req.body; - const result = await pool.request() - .input("amount", sql.Int, amount) - .query(` - SELECT TOP (@amount) p.PlayerID, p.PlayerName, p.[Position], p.Team, TotalValue, TrueAvgPerYear, Years + 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; + ORDER BY TotalValue DESC + LIMIT @amount; `); + const matches = query.all({ amount: amount }); - res.status(200).json({ matches: result.recordset }); + res.status(200).json({ matches: matches }); }); app.post("/getHighestOffense", authenticate, async (req, res) => { const { amount } = req.body; - const result = await pool.request() - .input("amount", sql.Int, amount) - .query(` -SELECT TOP (@amount) Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team, + 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, @@ -233,38 +214,36 @@ 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; +ORDER BY OffenseScore DESC +LIMIT @amount; `); + const matches = query.all({ amount: amount }); - res.status(200).json({ matches: result.recordset }); + res.status(200).json({ matches: matches }); }); app.post("/getPlayerStats", authenticate, async (req, res) => { const { playerID } = req.body; - const result = await pool.request() - .input("playerID", sql.Int, playerID) - .query(` + 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: result.recordset }); + res.status(200).json({ matches: matches }); }); app.post("/getPlayerScores", authenticate, async (req, res) => { const { playerID } = req.body; - const result = await pool.request() - .input("playerID", sql.Int, playerID) - .query(` + 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, @@ -312,9 +291,9 @@ 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; `); - console.log(result.recordset); + const matches = query.all({ playerID: playerID }) - res.status(200).json({ match: result.recordset[0] }); + res.status(200).json({ match: matches[0] }); }); @@ -322,10 +301,8 @@ ORDER BY PaydirtScore DESC; app.post("/getHighestPaydirt", authenticate, async (req, res) => { const { amount } = req.body; - const result = await pool.request() - .input("amount", sql.Int, amount) - .query(` -SELECT TOP (@amount) Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team, + 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, @@ -369,24 +346,25 @@ SUM(tackled_for_loss) 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; +ORDER BY PaydirtScore DESC +LIMIT @amount; `); + const matches = query.all({ amount: amount }); - res.status(200).json({ matches: result.recordset }); + res.status(200).json({ matches: matches }); }); app.post("/getPlayer", authenticate, async (req, res) => { const { id } = req.body; - const result = await pool.request() - .input("query", sql.VarChar, id) - .query(`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 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 (result.recordset.length !== 1) { + if (matches.length !== 1) { res.status(400).json({ success: false }) return; } - res.status(200).json({ success: true, match: result.recordset[0] }); + res.status(200).json({ success: true, match: matches[0] }); }); app.post("/getInfo", authenticate, async (req, res) => { @@ -396,10 +374,8 @@ app.post("/getInfo", authenticate, async (req, res) => { }); app.post("/getCourses", authenticate, async (req, res) => { - const result = await pool.request() - .query("SELECT * FROM Courses"); - - const courses = result.recordset; + const query = db.prepare("SELECT * FROM Courses"); + const courses = query.all(); res.status(200).json(courses); }); @@ -408,18 +384,19 @@ app.post("/setInfo", authenticate, async (req, res) => { const { firstName, lastName, dob } = req.body; try { - await pool.request() - .input("username", sql.VarChar, req.user.Username) - .input("firstName", sql.NVarChar(50), firstName) - .input("lastName", sql.NVarChar(50), lastName) - .input("dob", sql.Date, dob || null) - .query(` + 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'.")) { @@ -456,10 +433,11 @@ app.post("/delete", authenticate, async (req, res) => { console.log(`Deleting user ${username}`); - await pool.request() - .input("username", sql.VarChar, username) - .input("actor", sql.VarChar, actor) - .query("UPDATE Users SET IsDeleted = 1, DeletedAt = SYSUTCDATETIME(), DeletedBy = @actor WHERE Username = @username"); + const query = ("UPDATE Users SET IsDeleted = 1, DeletedAt = datetime('now'), DeletedBy = @actor WHERE Username = @username"); + query.run({ + username: username, + actor: actor + }); console.log(`User ${username} deleted`); res.status(200).json({ message: `User "${username}" deleted.` }); @@ -511,4 +489,6 @@ app.get("/register", (req, res) => { app.get("/login", (req, res) => { res.sendFile(__dirname + "/public/login.html"); }) + app.listen(serverPort, "0.0.0.0", () => console.log(`Running ${dev ? "dev " : ""}server on port ${serverPort}`)); +