import express, { Request, Response } from "express"; import { Database } from "sqlite3"; import bcrypt from "bcrypt"; import session from "express-session"; const db = new Database("db.sqlite"); declare module "express-session" { // noinspection JSUnusedGlobalSymbols interface SessionData { user?: number; } } (async () => { await dbRun("CREATE TABLE IF NOT EXISTS version ( version INT );"); const dbVersion: { version: number } | undefined = await dbGet("SELECT version FROM version;"); if (dbVersion && dbVersion.version === 1) { console.log("skip db setup") return; } await dbRun(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, pass TEXT NOT NULL )`); await dbRun("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_name ON users (name);"); await dbRun(`CREATE TABLE IF NOT EXISTS highScores ( id INTEGER PRIMARY KEY AUTOINCREMENT, user INTEGER, score BIGINT NOT NULL, FOREIGN KEY (user) REFERENCES users(id) )`); await dbRun("INSERT INTO version VALUES (1)"); })(); const app = express(); app.use(express.json()); app.use(session({ secret: "your mom", resave: true, })); app.get("/", (_req: Request, res: Response) => { res.send("Hello World!"); }); app.get("/leaderboard", async (req: Request, res: Response) => { const { limit = 100, offset = 0 } = req.query; const leaders = await dbAll(` select h.score, u.name from highScores h join main.users u on u.id = h.user order by score desc limit ? offset ?`, limit, offset); res.json(leaders); }); app.put("/leaderboard", async (req: Request, res: Response) => { const { score } = req.body; if (!req.session.user) { res.status(403).json({ success: false, message: "unauthorised" }); return; } if (!score) { res.status(400).json({ success: false, message: "provide score" }); return; } await dbRun("insert into highScores (user, score) VALUES (?, ?)", req.session.user, score); res.status(200).json({ success: true }); }); app.post("/login", async (req: Request, res: Response) => { await sleep(Math.random() * 50 + 50); const { username, password } = req.body; const user: { name: string, pass: string, id: number, } = await dbGet("SELECT * FROM users WHERE name = ?", username); if (!user) { res.status(403).json({ success: false, message: "invalid credentials" }); return; } if (!await bcrypt.compare(password, user.pass)) { res.status(403).json({ success: false, message: "invalid credentials" }); return; } await new Promise(res => req.session.regenerate(res)); req.session.user = user.id; res.status(200).json({ success: true }); }); app.post("/register", async (req: Request, res: Response) => { await sleep(Math.random() * 50 + 50); const { username, password } = req.body; if (!username || !password) { res.status(400).json({ success: false, message: "invalid credentials" }); return; } const pass = await bcrypt.hash(password, 12); const success = await dbRun("INSERT INTO users (name, pass) VALUES (?, ?)", [ username, pass ]) .then(() => true, () => false); if (!success) { res.status(400).json({ success: false, message: "invalid credentials" }); } else { res.status(200).json({ success: true }); } }); app.post("/logout", async (req: Request, res: Response) => { await new Promise(res => req.session.destroy(res)); res.send(200).json({ success: true }); }); app.listen(3000, () => { console.log("Server running on port 3000"); }); function dbGet(query: string, ...args: any[]): Promise { return new Promise((res, rej) => { db.get(query, ...args, (err: unknown, result: T) => { if (err) rej(err); else res(result); }); }); } function dbAll(query: string, ...args: any[]): Promise { return new Promise((res, rej) => { db.all(query, ...args, (err: unknown, result: T[]) => { if (err) rej(err); else res(result); }); }); } function dbRun(query: string, ...args: any[]): Promise { return new Promise((res, rej) => { db.run(query, ...args, (err: unknown) => { if (err) rej(err); else res(); }); }) } function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); }