miauw
This commit is contained in:
@@ -3,115 +3,234 @@ import { pool } from './db.js';
|
|||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /healthcheck:
|
||||||
|
* get:
|
||||||
|
* summary: Check if the API is running
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: API is healthy
|
||||||
|
*/
|
||||||
router.get("/healthcheck", (req, res) => {
|
router.get("/healthcheck", (req, res) => {
|
||||||
res.json({ status: "ok" })
|
res.json({ status: "ok" })
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/users", async (req, res) => {
|
|
||||||
const [rows] = await pool.query(`SELECT * FROM users`);
|
|
||||||
res.json(rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/users/:id", async (req, res) => {
|
|
||||||
const [rows] = await pool.query(`SELECT * FROM users WHERE id = ?`, [req.params.id]);
|
|
||||||
res.json(rows[0] || null);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/users", async (req, res) => {
|
router.post("/users", async (req, res) => {
|
||||||
const { username } = req.body;
|
const { username, input_method = 0 } = req.body;
|
||||||
const [result] = await pool.query(`INSERT INTO users (username) VALUES (?)`, [username]);
|
|
||||||
res.status(201).json({ id: result.insertId, username });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put("/users/:id", async (req, res) => {
|
const [result] = await pool.query(
|
||||||
const { username } = req.body;
|
"INSERT INTO users (username, input_method) VALUES (?, ?)",
|
||||||
await pool.query(`UPDATE users SET username = ? WHERE id = ?`, [username, req.params.id]);
|
[username, input_method]
|
||||||
res.json({ id: req.params.id, username });
|
);
|
||||||
});
|
|
||||||
|
|
||||||
router.delete("/users/:id", async (req, res) => {
|
const [userRow] = await pool.query("SELECT * FROM users WHERE id = ?", [result.insertId]);
|
||||||
await pool.query(`DELETE FROM users WHERE id = ?`, [req.params.id]);
|
res.status(201).json(userRow[0]);
|
||||||
res.status(204).send();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/users/:id/settings", async (req, res) => {
|
|
||||||
const [rows] = await pool.query(`SELECT * FROM user_settings WHERE user = ?`, [req.params.id]);
|
|
||||||
res.json(rows[0] || null);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put("/users/:id/settings", async (req, res) => {
|
|
||||||
const { input_method } = req.body;
|
|
||||||
await pool.query(`
|
|
||||||
INSERT INTO user_settings (user, input_method)
|
|
||||||
VALUES (?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE input_method = ?
|
|
||||||
`, [req.params.id, input_method, input_method]);
|
|
||||||
res.json({ user: req.params.id, input_method });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/games", async (req, res) => {
|
router.get("/games", async (req, res) => {
|
||||||
const [rows] = await pool.query(`SELECT * FROM games`);
|
const [rows] = await pool.query("SELECT * FROM current_games");
|
||||||
res.json(rows);
|
res.json(rows);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/games/:id", async (req, res) => {
|
router.get("/games/:id", async (req, res) => {
|
||||||
const [rows] = await pool.query(`SELECT * FROM games WHERE id = ?`, [req.params.id]);
|
const [rows] = await pool.query("SELECT * FROM current_games WHERE id = ?", [req.params.id]);
|
||||||
res.json(rows[0] || null);
|
res.json(rows[0] || null);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/games", async (req, res) => {
|
router.post("/games", async (req, res) => {
|
||||||
const [result] = await pool.query(`INSERT INTO games (created_at) VALUES (NOW())`);
|
const { user, is_local = false } = req.body;
|
||||||
res.status(201).json({ id: result.insertId, created_at: new Date() });
|
const conn = await pool.getConnection();
|
||||||
});
|
|
||||||
|
|
||||||
router.put("/games/:id", async (req, res) => {
|
try {
|
||||||
const { winner } = req.body;
|
await conn.beginTransaction();
|
||||||
await pool.query(`UPDATE games SET winner = ? WHERE id = ?`, [winner, req.params.id]);
|
|
||||||
res.json({ id: req.params.id, winner });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.delete("/games/:id", async (req, res) => {
|
const [gameResult] = await conn.query(
|
||||||
await pool.query(`DELETE FROM games WHERE id = ?`, [req.params.id]);
|
"INSERT INTO current_games (is_open, is_local, current_playing_user) VALUES (?, ?, ?)",
|
||||||
res.status(204).send();
|
[true, is_local, user]
|
||||||
});
|
);
|
||||||
|
|
||||||
router.get("/games/:id/players", async (req, res) => {
|
const gameId = gameResult.insertId;
|
||||||
const [rows] = await pool.query(`
|
|
||||||
SELECT u.id, u.username
|
await conn.query("INSERT INTO game_players (game, user, is_creator) VALUES (?, ?)", [gameId, user, true]);
|
||||||
FROM game_players gp
|
|
||||||
JOIN users u ON gp.user = u.id
|
const [gameRow] = await conn.query("SELECT * FROM current_games WHERE id = ?", [gameId]);
|
||||||
WHERE gp.game = ?
|
const [players] = await conn.query(
|
||||||
`, [req.params.id]);
|
`SELECT u.id, u.username FROM game_players gp
|
||||||
res.json(rows);
|
JOIN users u ON gp.user = u.id
|
||||||
|
WHERE gp.game = ?`,
|
||||||
|
[gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
await conn.commit();
|
||||||
|
res.status(201).json({ game: gameRow[0], players });
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/games/:id/players", async (req, res) => {
|
router.post("/games/:id/players", async (req, res) => {
|
||||||
const { user } = req.body;
|
const { user } = req.body;
|
||||||
await pool.query(`INSERT IGNORE INTO game_players (game, user) VALUES (?, ?)`, [req.params.id, user]);
|
const gameId = req.params.id;
|
||||||
res.status(201).json({ game: req.params.id, user });
|
|
||||||
|
const conn = await pool.getConnection();
|
||||||
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
|
||||||
|
const [gameRows] = await conn.query(
|
||||||
|
"SELECT * FROM current_games WHERE id = ? AND is_open = TRUE AND is_local = FALSE",
|
||||||
|
[gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gameRows.length === 0) {
|
||||||
|
return res.status(400).json({ error: "Game is not open or is local" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await conn.query(
|
||||||
|
"INSERT IGNORE INTO game_players (game, user, is_creator) VALUES (?, ?, ?)",
|
||||||
|
[gameId, user, false]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [players] = await conn.query(
|
||||||
|
`SELECT u.id, u.username FROM game_players gp
|
||||||
|
JOIN users u ON gp.user = u.id
|
||||||
|
WHERE gp.game = ?`,
|
||||||
|
[gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
await conn.commit();
|
||||||
|
res.status(201).json({ game: gameId, players });
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete("/games/:id/players/:user", async (req, res) => {
|
router.patch("/games/:id/lock", async (req, res) => {
|
||||||
await pool.query(`DELETE FROM game_players WHERE game = ? AND user = ?`, [req.params.id, req.params.user]);
|
const { user } = req.body;
|
||||||
res.status(204).send();
|
const gameId = req.params.id;
|
||||||
|
|
||||||
|
const conn = await pool.getConnection();
|
||||||
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
|
||||||
|
const [creatorRows] = await pool.query(
|
||||||
|
"SELECT * FROM game_players WHERE game = ? AND user = ? AND is_creator = TRUE",
|
||||||
|
[gameId, user]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (creatorRows.length === 0) {
|
||||||
|
return res.status(403).json({ error: "Only the creator can lock the game" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [players] = await conn.query(
|
||||||
|
`SELECT user FROM game_players WHERE game = ?`,
|
||||||
|
[gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const turnOrder = players.map(p => p.user);
|
||||||
|
for (let i = turnOrder.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[turnOrder[i], turnOrder[j]] = [turnOrder[j], turnOrder[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
await conn.query(
|
||||||
|
`UPDATE current_games SET is_open = FALSE, turn_order = ? WHERE id = ?`,
|
||||||
|
[JSON.stringify(turnOrder), gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
await conn.commit();
|
||||||
|
res.json({ message: "Game locked", turnOrder });
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query("UPDATE current_games SET is_open = FALSE WHERE id = ?", [gameId]);
|
||||||
|
res.json({ message: "Game locked" });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/games/:id/players", async (req, res) => {
|
||||||
|
const [players] = await pool.query(
|
||||||
|
`SELECT u.id, u.username FROM game_players gp
|
||||||
|
JOIN users u ON gp.user = u.id
|
||||||
|
WHERE gp.game = ?`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.json(players);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/games/:id/turns", async (req, res) => {
|
router.get("/games/:id/turns", async (req, res) => {
|
||||||
const [rows] = await pool.query(`SELECT * FROM turns WHERE game = ? ORDER BY round_number`, [req.params.id]);
|
const [rows] = await pool.query(
|
||||||
|
"SELECT * FROM turns WHERE game = ? ORDER BY round_number",
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
res.json(rows);
|
res.json(rows);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/games/:id/turns", async (req, res) => {
|
router.post("/games/:id/turns", async (req, res) => {
|
||||||
const { user, round_number, start_points, first_throw, second_throw, third_throw, end_points } = req.body;
|
const { user, round_number, start_points, first_throw, second_throw, third_throw, end_points } = req.body;
|
||||||
const [result] = await pool.query(`
|
const gameId = req.params.id;
|
||||||
INSERT INTO turns (game, user, round_number, start_points, first_throw, second_throw, third_throw, end_points)
|
const conn = await pool.getConnection();
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`, [req.params.id, user, round_number, start_points, first_throw, second_throw, third_throw, end_points]);
|
try {
|
||||||
res.status(201).json({ id: result.insertId });
|
await conn.beginTransaction();
|
||||||
|
|
||||||
|
const [gameRows] = await conn.query(
|
||||||
|
"SELECT current_playing_user FROM current_games WHERE id = ? FOR UPDATE",
|
||||||
|
[gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gameRows.length === 0) {
|
||||||
|
return res.status(404).json({ error: "Game not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const game = gameRows[0];
|
||||||
|
const turnOrder = JSON.parse(game.turn_order || '[]');
|
||||||
|
|
||||||
|
if (game.current_playing_user !== user) {
|
||||||
|
return res.status(403).json({ error: "Not your turn" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [result] = await conn.query(
|
||||||
|
`INSERT INTO turns
|
||||||
|
(game, user, round_number, start_points, first_throw, second_throw, third_throw, end_points)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[gameId, user, round_number, start_points, first_throw, second_throw, third_throw, end_points]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentIndex = turnOrder.indexOf(user);
|
||||||
|
const nextIndex = (currentIndex + 1) % turnOrder.length;
|
||||||
|
const nextPlayer = turnOrder[nextIndex];
|
||||||
|
|
||||||
|
await conn.query(
|
||||||
|
"UPDATE current_games SET current_playing_user = ? WHERE id = ?",
|
||||||
|
[nextPlayer, gameId]
|
||||||
|
);
|
||||||
|
|
||||||
|
await conn.commit();
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
turnId: result.insertId,
|
||||||
|
nextPlayer
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/turns/:id", async (req, res) => {
|
router.get("/turns/:id", async (req, res) => {
|
||||||
const [rows] = await pool.query(`SELECT * FROM turns WHERE id = ?`, [req.params.id]);
|
const [rows] = await pool.query("SELECT * FROM turns WHERE id = ?", [req.params.id]);
|
||||||
res.json(rows[0] || null);
|
res.json(rows[0] || null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,56 +21,60 @@ const migrations = [
|
|||||||
name: "init",
|
name: "init",
|
||||||
up: async (conn) => {
|
up: async (conn) => {
|
||||||
await conn.query(`
|
await conn.query(`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE users (
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
username VARCHAR(255) NOT NULL
|
username VARCHAR(32),
|
||||||
|
input_method INT
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await conn.query(`
|
await conn.query(`
|
||||||
CREATE TABLE IF NOT EXISTS user_settings (
|
CREATE TABLE games (
|
||||||
user INT NOT NULL,
|
|
||||||
input_method INT,
|
|
||||||
PRIMARY KEY (user),
|
|
||||||
FOREIGN KEY (user) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
await conn.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS games (
|
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
winner INT,
|
winner INT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (winner) REFERENCES users(id) ON DELETE SET NULL
|
FOREGIN KEY (winner) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await conn.query(`
|
await conn.query(`
|
||||||
CREATE TABLE IF NOT EXISTS game_players (
|
CREATE TABLE current_games (
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
game INT NOT NULL,
|
is_open BOOL,
|
||||||
user INT NOT NULL,
|
is_local BOOL,
|
||||||
FOREIGN KEY (game) REFERENCES games(id) ON DELETE CASCADE,
|
current_playing_user INT,
|
||||||
FOREIGN KEY (user) REFERENCES users(id) ON DELETE CASCADE,
|
turn_order JSON DEFAULT NULL,
|
||||||
UNIQUE (game, user)
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (current_playing_user) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await conn.query(`
|
await conn.query(`
|
||||||
CREATE TABLE IF NOT EXISTS turns (
|
CREATE TABLE game_players (
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
game INT NOT NULL,
|
game INT,
|
||||||
user INT NOT NULL,
|
user INT,
|
||||||
round_number INT NOT NULL,
|
is_creator BOOL,
|
||||||
|
FOREIGN KEY (game) REFERENCES current_games(id),
|
||||||
|
FOREIGN KEY (user) REFERENCES users(id)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
|
await conn.query(`
|
||||||
|
CREATE TABLE turns (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
game INT,
|
||||||
|
user INT,
|
||||||
|
round_number INT,
|
||||||
start_points INT,
|
start_points INT,
|
||||||
first_throw INT,
|
first_throw INT,
|
||||||
second_throw INT,
|
second_throw INT,
|
||||||
third_throw INT,
|
third_throw INT,
|
||||||
end_points INT,
|
end_points INT,
|
||||||
FOREIGN KEY (game) REFERENCES games(id) ON DELETE CASCADE,
|
FOREIGN KEY (game) REFERENCES current_games(id),
|
||||||
FOREIGN KEY (user) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY (user) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
`);
|
`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import swaggerJsdoc from "swagger-jsdoc";
|
||||||
|
import swaggerUi from "swagger-ui-express";
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
@@ -10,6 +12,16 @@ import { fileURLToPath } from 'url';
|
|||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.join(path.dirname(__filename), ".."); // going back a dir cuz code is in src/
|
const __dirname = path.join(path.dirname(__filename), ".."); // going back a dir cuz code is in src/
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const specs = swaggerJsdoc({
|
||||||
|
definition: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: {
|
||||||
|
title: "Analogue Game Assistent API",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apis: ["./src/api.js"],
|
||||||
|
});
|
||||||
|
|
||||||
initDB();
|
initDB();
|
||||||
|
|
||||||
@@ -19,13 +31,12 @@ app.use(express.urlencoded({ extended: true }))
|
|||||||
app.use(express.static(path.join(__dirname, "public"), {
|
app.use(express.static(path.join(__dirname, "public"), {
|
||||||
dotfiles: "ignore"
|
dotfiles: "ignore"
|
||||||
}));
|
}));
|
||||||
|
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(specs));
|
||||||
app.use("/api", apiRouter);
|
app.use("/api", apiRouter);
|
||||||
|
|
||||||
const options = {
|
const server = https.createServer({
|
||||||
key: fs.readFileSync('server.key'),
|
key: fs.readFileSync('server.key'),
|
||||||
cert: fs.readFileSync('server.cert')
|
cert: fs.readFileSync('server.cert')
|
||||||
};
|
}, app).listen(5555, () => {
|
||||||
|
|
||||||
const server = https.createServer(options, app).listen(5555, () => {
|
|
||||||
console.log(`Server running on http://localhost:${server.address().port}`);
|
console.log(`Server running on http://localhost:${server.address().port}`);
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user