171 lines
4.2 KiB
TypeScript
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));
|
|
}
|