**Motivations:** - Ensure lint config is not degraded and fix all lint errors for pousse workflow. **Root causes:** - Unused variables kept with _ prefix instead of removed (_row, _questReward, _i). - getAnimalBlockOrigin had 5 parameters (max 4). - use of continue statement (no-continue rule). **Correctifs:** - ESLint config verified; no eslint-disable in codebase. - Removed unused variable _row (biome-rules); removed dead function _questReward (quests); removed unused map param _i (state.js). - getAnimalBlockOrigin refactored to 4 params (pos object instead of x, y). - Replaced continue with if (cell) block in normalizeLoadedCells (state.js). - JSDoc param names aligned with _height, _y (biome-rules). **Evolutions:** - (none) **Pages affectées:** - web/js/biome-rules.js - web/js/quests.js - web/js/state.js - web/js/placement.js
166 lines
5.1 KiB
JavaScript
166 lines
5.1 KiB
JavaScript
import express from "express";
|
|
import {
|
|
getAccountByPublicKey,
|
|
getZooByAccountId,
|
|
createZoo,
|
|
getMapParams,
|
|
getAllZoos,
|
|
countPlayerZoos,
|
|
createBotZoo,
|
|
updateLastSeen,
|
|
updateZooGameState,
|
|
} from "../db.js";
|
|
import { verifySignature, buildSignMessage, hashBody } from "../auth.js";
|
|
|
|
const router = express.Router();
|
|
const TIMESTAMP_TOLERANCE_MS = 5 * 60 * 1000;
|
|
|
|
function isTimestampValid(timestamp) {
|
|
const now = Date.now();
|
|
const ts = new Date(timestamp).getTime();
|
|
return !Number.isNaN(ts) && Math.abs(now - ts) <= TIMESTAMP_TOLERANCE_MS;
|
|
}
|
|
|
|
function getBodyForSignature(req) {
|
|
return req.bodyRaw !== undefined && req.bodyRaw !== null ? req.bodyRaw : "";
|
|
}
|
|
|
|
function isSignatureValid(req, publicKey, signature, timestamp) {
|
|
const bodyHash = hashBody(getBodyForSignature(req));
|
|
const path = req.originalUrl || req.baseUrl + req.path || req.path;
|
|
const message = buildSignMessage(req.method, path, timestamp, bodyHash);
|
|
return verifySignature(publicKey, signature, message);
|
|
}
|
|
|
|
function requireSignature() {
|
|
return (req, res, next) => {
|
|
const publicKey = req.headers["x-public-key"];
|
|
const signature = req.headers["x-signature"];
|
|
const timestamp = req.headers["x-timestamp"];
|
|
if (!publicKey || !signature || !timestamp) {
|
|
res.status(401).json({ error: "Missing X-Public-Key, X-Signature, or X-Timestamp" });
|
|
return;
|
|
}
|
|
if (!isTimestampValid(timestamp)) {
|
|
res.status(401).json({ error: "Invalid or expired timestamp" });
|
|
return;
|
|
}
|
|
if (!isSignatureValid(req, publicKey, signature, timestamp)) {
|
|
res.status(401).json({ error: "Invalid signature" });
|
|
return;
|
|
}
|
|
getAccountByPublicKey(publicKey).then((account) => {
|
|
if (!account) {
|
|
res.status(401).json({ error: "Unknown account" });
|
|
return;
|
|
}
|
|
req.account = account;
|
|
next();
|
|
}).catch(next);
|
|
};
|
|
}
|
|
|
|
/** @returns {Promise<{ zoos: Array<object>, mapWidth: number, mapHeight: number }>}
|
|
*/
|
|
async function getZoosForMap() {
|
|
const params = await getMapParams();
|
|
let zoos = await getAllZoos();
|
|
await countPlayerZoos();
|
|
const need = Math.max(0, params.minZoos - zoos.length);
|
|
for (let i = 0; i < need; i++) {
|
|
const x = 10 + Math.random() * 80;
|
|
const y = 10 + Math.random() * 80;
|
|
const weights = { Basic: 1 + Math.floor(Math.random() * 2), Ocean: Math.floor(Math.random() * 2), Mountain: Math.floor(Math.random() * 2) };
|
|
await createBotZoo(x, y, weights);
|
|
}
|
|
if (need > 0) zoos = await getAllZoos();
|
|
return {
|
|
zoos,
|
|
mapWidth: params.mapWidth,
|
|
mapHeight: params.mapHeight,
|
|
};
|
|
}
|
|
|
|
/** GET /api/zoos — list zoos for map (no auth). Ensures minZoos with bots. */
|
|
router.get("/", async (req, res, next) => {
|
|
try {
|
|
const { zoos, mapWidth, mapHeight } = await getZoosForMap();
|
|
const worldZoos = zoos.map((z) => ({
|
|
id: z.id,
|
|
name: z.name,
|
|
x: z.x,
|
|
y: z.y,
|
|
animalWeights: z.animal_weights,
|
|
game_state: z.game_state ?? null,
|
|
}));
|
|
res.json({ worldZoos, mapWidth, mapHeight });
|
|
} catch (e) {
|
|
next(e);
|
|
}
|
|
});
|
|
|
|
/** GET /api/zoos/me — my zoo + game_state (auth). */
|
|
router.get("/me", requireSignature(), (req, res, next) => {
|
|
getZooByAccountId(req.account.id)
|
|
.then((zoo) => {
|
|
if (!zoo) {
|
|
res.status(404).json({ error: "No zoo for this account" });
|
|
return;
|
|
}
|
|
updateLastSeen(req.account.id);
|
|
res.json({
|
|
zooId: zoo.id,
|
|
name: zoo.name,
|
|
x: zoo.x,
|
|
y: zoo.y,
|
|
game_state: zoo.game_state,
|
|
});
|
|
})
|
|
.catch(next);
|
|
});
|
|
|
|
/** PATCH /api/zoos/me — update game_state (auth). Body: { game_state }. */
|
|
router.patch("/me", requireSignature(), (req, res, next) => {
|
|
getZooByAccountId(req.account.id)
|
|
.then((zoo) => {
|
|
if (!zoo) {
|
|
res.status(404).json({ error: "No zoo for this account" });
|
|
return;
|
|
}
|
|
const game_state = req.body?.game_state;
|
|
if (game_state === null || game_state === undefined || typeof game_state !== "object") {
|
|
res.status(400).json({ error: "game_state object required" });
|
|
return;
|
|
}
|
|
return updateZooGameState(zoo.id, game_state).then(() => {
|
|
res.json({ ok: true });
|
|
});
|
|
})
|
|
.catch(next);
|
|
});
|
|
|
|
/** POST /api/zoos/me — create my zoo (auth). Body: { name?, game_state }. */
|
|
router.post("/me", requireSignature(), (req, res, next) => {
|
|
getZooByAccountId(req.account.id)
|
|
.then((existing) => {
|
|
if (existing) {
|
|
res.status(409).json({ error: "Zoo already exists" });
|
|
return;
|
|
}
|
|
const name = req.body?.name?.trim() || req.account.pseudo;
|
|
const game_state = req.body?.game_state;
|
|
if (game_state === null || game_state === undefined || typeof game_state !== "object") {
|
|
res.status(400).json({ error: "game_state object required" });
|
|
return;
|
|
}
|
|
const x = 25 + Math.random() * 50;
|
|
const y = 25 + Math.random() * 50;
|
|
return createZoo({ accountId: req.account.id, name, x, y, gameState: game_state }).then(({ id }) => {
|
|
res.status(201).json({ zooId: id, name, x, y });
|
|
});
|
|
})
|
|
.catch(next);
|
|
});
|
|
|
|
export default router;
|