implement basics
This commit is contained in:
parent
7360cbc029
commit
9d1c43e593
|
@ -0,0 +1,170 @@
|
||||||
|
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));
|
||||||
|
}
|
19
package.json
19
package.json
|
@ -1,4 +1,21 @@
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"packageManager": "yarn@4.5.1"
|
"packageManager": "yarn@4.5.1",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bcrypt": "^5",
|
||||||
|
"@types/express": "^4",
|
||||||
|
"@types/express-session": "^1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "ts-node-dev ./index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"express-session": "^1.18.1",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue