Initial commit

**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
This commit is contained in:
2026-03-03 22:24:17 +01:00
commit e031c9a1d2
155 changed files with 22334 additions and 0 deletions

143
server/routes/zoos.js Normal file
View File

@@ -0,0 +1,143 @@
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;