**Motivations:** - Initialisation du versionning git pour le projet **Root causes:** - N/A (Nouveau projet) **Correctifs:** - N/A **Evolutions:** - Structure initiale du projet - Ajout du .gitignore **Pages affectées:** - Tous les fichiers
144 lines
4.6 KiB
JavaScript
144 lines
4.6 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 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;
|
|
}
|
|
const now = Date.now();
|
|
const ts = new Date(timestamp).getTime();
|
|
if (Number.isNaN(ts) || Math.abs(now - ts) > TIMESTAMP_TOLERANCE_MS) {
|
|
res.status(401).json({ error: "Invalid or expired timestamp" });
|
|
return;
|
|
}
|
|
const body = req.bodyRaw !== undefined && req.bodyRaw !== null ? req.bodyRaw : "";
|
|
const bodyHash = hashBody(body);
|
|
const path = req.originalUrl || req.baseUrl + req.path || req.path;
|
|
const message = buildSignMessage(req.method, path, timestamp, bodyHash);
|
|
if (!verifySignature(publicKey, signature, message)) {
|
|
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);
|
|
};
|
|
}
|
|
|
|
/** GET /api/zoos — list zoos for map (no auth). Ensures minZoos with bots. */
|
|
router.get("/", async (req, res, next) => {
|
|
try {
|
|
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();
|
|
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: params.mapWidth, mapHeight: params.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;
|