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
This commit is contained in:
@@ -17,6 +17,35 @@ import { verifySignature, buildSignMessage, hashBody } from "../auth.js";
|
||||
const router = express.Router();
|
||||
const TIMESTAMP_TOLERANCE_MS = 5 * 60 * 1000;
|
||||
|
||||
/** @param {string} timestamp
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTimestampValid(timestamp) {
|
||||
const now = Date.now();
|
||||
const ts = new Date(timestamp).getTime();
|
||||
return !Number.isNaN(ts) && Math.abs(now - ts) <= TIMESTAMP_TOLERANCE_MS;
|
||||
}
|
||||
|
||||
/** @param {import("express").Request} req
|
||||
* @returns {string}
|
||||
*/
|
||||
function getBodyForSignature(req) {
|
||||
return req.bodyRaw !== undefined && req.bodyRaw !== null ? req.bodyRaw : "";
|
||||
}
|
||||
|
||||
/** @param {import("express").Request} req
|
||||
* @param {string} publicKey
|
||||
* @param {string} signature
|
||||
* @param {string} timestamp
|
||||
* @returns {boolean}
|
||||
*/
|
||||
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"];
|
||||
@@ -26,17 +55,11 @@ function requireSignature() {
|
||||
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) {
|
||||
if (!isTimestampValid(timestamp)) {
|
||||
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)) {
|
||||
if (!isSignatureValid(req, publicKey, signature, timestamp)) {
|
||||
res.status(401).json({ error: "Invalid signature" });
|
||||
return;
|
||||
}
|
||||
@@ -60,17 +83,7 @@ function optionalSignature() {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
const ts = new Date(timestamp).getTime();
|
||||
if (Number.isNaN(ts) || Math.abs(now - ts) > TIMESTAMP_TOLERANCE_MS) {
|
||||
next();
|
||||
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)) {
|
||||
if (!isTimestampValid(timestamp) || !isSignatureValid(req, publicKey, signature, timestamp)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
@@ -81,28 +94,62 @@ function optionalSignature() {
|
||||
};
|
||||
}
|
||||
|
||||
/** GET /api/sales — optional auth: with auth returns asSeller, asBuyerUndelivered, active; without auth returns active only. */
|
||||
/** GET /api/sales — optional auth: with auth returns asSeller, asBuyerUndelivered, active; without auth returns active only.
|
||||
* @param {import("express").Request} req
|
||||
* @returns {Promise<{ active: Array<object> } | { asSeller: Array<object>, asBuyerUndelivered: Array<object>, active: Array<object> }>}
|
||||
*/
|
||||
async function getSalesResponse(req) {
|
||||
await expireSaleListings();
|
||||
if (!req.account) {
|
||||
const active = await getActiveSaleListings();
|
||||
return { active };
|
||||
}
|
||||
await processValidatedSales();
|
||||
const zoo = await getZooByAccountId(req.account.id);
|
||||
if (!zoo) {
|
||||
return { asSeller: [], asBuyerUndelivered: [], active: await getActiveSaleListings() };
|
||||
}
|
||||
return getSalesForZoo(zoo.id);
|
||||
}
|
||||
|
||||
router.get("/", optionalSignature(), async (req, res, next) => {
|
||||
try {
|
||||
await expireSaleListings();
|
||||
if (req.account) {
|
||||
await processValidatedSales();
|
||||
const zoo = await getZooByAccountId(req.account.id);
|
||||
if (!zoo) {
|
||||
res.json({ asSeller: [], asBuyerUndelivered: [], active: await getActiveSaleListings() });
|
||||
return;
|
||||
}
|
||||
const data = await getSalesForZoo(zoo.id);
|
||||
res.json(data);
|
||||
return;
|
||||
}
|
||||
const active = await getActiveSaleListings();
|
||||
res.json({ active });
|
||||
const data = await getSalesResponse(req);
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {unknown} body
|
||||
* @returns {{ status: number, error: string } | null}
|
||||
*/
|
||||
function validateCreateListingBody(body) {
|
||||
const { animalId, isBaby, price, endAt } = body ?? {};
|
||||
if (typeof animalId !== "string" || !animalId.trim()) return { status: 400, error: "animalId required" };
|
||||
if (typeof isBaby !== "boolean") return { status: 400, error: "isBaby boolean required" };
|
||||
const initialPrice = Number(price);
|
||||
if (!Number.isFinite(initialPrice) || initialPrice < 0) return { status: 400, error: "price must be a non-negative number" };
|
||||
const endAtDate = endAt ? new Date(endAt) : null;
|
||||
if (!endAtDate || Number.isNaN(endAtDate.getTime())) return { status: 400, error: "endAt required (ISO date string)" };
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} body
|
||||
* @returns {{ ok: true, animalId: string, isBaby: boolean, initialPrice: number, endAtDate: Date, reproductionScoreAtSale?: number } | { ok: false, status: number, error: string }}
|
||||
*/
|
||||
function parseCreateListingBody(body) {
|
||||
const err = validateCreateListingBody(body);
|
||||
if (err) return { ok: false, status: err.status, error: err.error };
|
||||
const { animalId, isBaby, price, endAt, reproductionScoreAtSale } = body ?? {};
|
||||
const endAtDate = endAt ? new Date(endAt) : null;
|
||||
const initialPrice = Number(price);
|
||||
const repScore = reproductionScoreAtSale !== null && reproductionScoreAtSale !== undefined ? Number(reproductionScoreAtSale) : undefined;
|
||||
return { ok: true, animalId: String(animalId).trim(), isBaby, initialPrice, endAtDate: /** @type {Date} */ (endAtDate), reproductionScoreAtSale: repScore };
|
||||
}
|
||||
|
||||
/** POST /api/sales — create listing (auth). Body: { animalId, isBaby, price, endAt, reproductionScoreAtSale? }. */
|
||||
router.post("/", requireSignature(), async (req, res, next) => {
|
||||
try {
|
||||
@@ -111,32 +158,18 @@ router.post("/", requireSignature(), async (req, res, next) => {
|
||||
res.status(404).json({ error: "No zoo for this account" });
|
||||
return;
|
||||
}
|
||||
const { animalId, isBaby, price, endAt, reproductionScoreAtSale } = req.body ?? {};
|
||||
if (typeof animalId !== "string" || !animalId.trim()) {
|
||||
res.status(400).json({ error: "animalId required" });
|
||||
return;
|
||||
}
|
||||
if (typeof isBaby !== "boolean") {
|
||||
res.status(400).json({ error: "isBaby boolean required" });
|
||||
return;
|
||||
}
|
||||
const initialPrice = Number(price);
|
||||
if (!Number.isFinite(initialPrice) || initialPrice < 0) {
|
||||
res.status(400).json({ error: "price must be a non-negative number" });
|
||||
return;
|
||||
}
|
||||
const endAtDate = endAt ? new Date(endAt) : null;
|
||||
if (!endAtDate || Number.isNaN(endAtDate.getTime())) {
|
||||
res.status(400).json({ error: "endAt required (ISO date string)" });
|
||||
const parsed = parseCreateListingBody(req.body);
|
||||
if (!parsed.ok) {
|
||||
res.status(parsed.status).json({ error: parsed.error });
|
||||
return;
|
||||
}
|
||||
const { id } = await createSaleListing({
|
||||
sellerZooId: zoo.id,
|
||||
animalId: animalId.trim(),
|
||||
isBaby,
|
||||
initialPrice,
|
||||
endAt: endAtDate.toISOString(),
|
||||
reproductionScoreAtSale: reproductionScoreAtSale != null ? Number(reproductionScoreAtSale) : undefined,
|
||||
animalId: parsed.animalId,
|
||||
isBaby: parsed.isBaby,
|
||||
initialPrice: parsed.initialPrice,
|
||||
endAt: parsed.endAtDate.toISOString(),
|
||||
reproductionScoreAtSale: parsed.reproductionScoreAtSale,
|
||||
});
|
||||
res.status(201).json({ id });
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user