backend/index.ts

171 lines
4.2 KiB
TypeScript

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<T>(query: string, ...args: any[]): Promise<T> {
return new Promise((res, rej) => {
db.get(query, ...args, (err: unknown, result: T) => {
if (err) rej(err);
else res(result);
});
});
}
function dbAll<T>(query: string, ...args: any[]): Promise<T[]> {
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<void> {
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));
}