This repository has been archived on 2026-10-08. You can view files and clone it, but cannot push or open issues or pull requests.

100 lines
2.8 KiB
JavaScript

import crypto from "crypto";
import express from "express";
import config from "../config.js";
import { Patch } from "../db/models/index.js";
import { getConfiguredDevice } from "../services/devices.js";
import { normalizeProduceIcon } from "../services/patches.js";
import {
NodeInputError,
listNodes,
setNodePatches,
upsertNodes,
} from "../services/nodes.js";
import { getLogger } from "../logger.js";
const logger = getLogger("HubApi");
function keysMatch(given, expected) {
const a = Buffer.from(given);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function requireHubKey(req, res, next) {
const expected = config.hub?.apiKey;
if (!expected) {
res.status(503).json({ status: "error", error: "Hub API not configured" });
return;
}
const match = /^Bearer\s+(.+)$/i.exec(req.get("authorization") || "");
if (!match || !keysMatch(match[1].trim(), expected)) {
res.status(401).json({ status: "error", error: "Unauthorized" });
return;
}
next();
}
function sendError(res, e, fallback) {
if (e instanceof NodeInputError) {
res.status(400).json({ status: "error", error: e.message });
return;
}
logger.error(fallback, e);
res.status(500).json({ status: "error", error: e.message || fallback });
}
/** HTTP API used by the hub firmware to relay BLE patch assignments (BLE_PROTOCOL.md Appendix A). */
export function createHubRouter() {
const router = express.Router();
router.use(requireHubKey);
router.use(express.json());
router.get("/patches", async (_req, res) => {
try {
const device = await getConfiguredDevice();
const docs = device
? await Patch.find({ deviceId: device._id })
.select("patchId name icon")
.sort({ patchId: 1 })
.lean()
: [];
const patches = docs.map((p) => ({
id: p.patchId,
name: p.name ?? "",
icon: normalizeProduceIcon(p.icon),
}));
res.json({ status: "ok", data: { patches } });
} catch (e) {
sendError(res, e, "list patches failed");
}
});
router.get("/nodes", async (_req, res) => {
try {
res.json({ status: "ok", data: { nodes: await listNodes() } });
} catch (e) {
sendError(res, e, "list nodes failed");
}
});
router.post("/nodes", async (req, res) => {
try {
const nodes = await upsertNodes(req.body?.serials);
res.json({ status: "ok", data: { nodes } });
} catch (e) {
sendError(res, e, "register nodes failed");
}
});
router.put("/nodes/:serial", async (req, res) => {
try {
const node = await setNodePatches(req.params.serial, req.body?.patches);
res.json({ status: "ok", data: { node } });
} catch (e) {
sendError(res, e, "set node patches failed");
}
});
return router;
}