Files
builazoo/server/routes/zoos.js
ncantu c7d389ecbb Lint: fix errors and remove unused variables
**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
2026-03-04 15:32:27 +01:00

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;