diff --git a/.env.sample b/.env.sample index 12ca5abc..6cb83b7c 100644 --- a/.env.sample +++ b/.env.sample @@ -8,6 +8,7 @@ DATA_DIR=/home/respec.org/data # GitHub webhook secrets W3C_WEBREF_SECRET=xxxxxxxxxxxxxxxxxxxxxx +W3C_GROUPS_SECRET=xxxxxxxxxxxxxxxxxxxxxx CANIUSE_SECRET=xxxxxxxxxxxxxxxxxxxxxx RESPEC_SECRET=xxxxxxxxxxxxxxxxxxxxxx WEB_FEATURES_SECRET=xxxxxxxxxxxxxxxxxxxxxx diff --git a/routes/w3c/group.ts b/routes/w3c/group.ts index f3c0be05..984137ab 100644 --- a/routes/w3c/group.ts +++ b/routes/w3c/group.ts @@ -18,7 +18,7 @@ type GroupType = "wg" | "cg" | "ig" | "bg" | "other"; export type Groups = Record; export type GroupsByType = Record; -const groups: GroupsByType = JSON.parse(readFileSync(dataSource, "utf-8")); +const EMPTY_GROUPS: GroupsByType = { wg: {}, cg: {}, ig: {}, bg: {}, other: {} }; interface Group { id: number; @@ -32,6 +32,24 @@ interface Group { } const cache = new MemCache(ms("1 day")); +let groups: GroupsByType = EMPTY_GROUPS; +try { + groups = JSON.parse(readFileSync(dataSource, "utf-8")); +} catch (error) { + console.error("Failed to parse groups.json at startup:", error); +} + +export function reloadGroups(): boolean { + try { + groups = JSON.parse(readFileSync(dataSource, "utf-8")); + cache.clear(); + return true; + } catch (error) { + console.error("Failed to reload groups.json:", error); + return false; + } +} + // Support non W3C shortnames for backward compatibility. const LEGACY_SHORTNAMES = new Map([ ["wai-apa", "apa"], diff --git a/routes/w3c/index.ts b/routes/w3c/index.ts index 4a809574..3bdcc40f 100644 --- a/routes/w3c/index.ts +++ b/routes/w3c/index.ts @@ -1,9 +1,14 @@ import { Router } from "express"; import cors from "cors"; +import authGithubWebhook from "../../utils/auth-github-webhook.js"; +import { env } from "../../utils/misc.js"; + import groupsRoute from "./group.js"; +import updateRoute from "./update.js"; const w3c = Router(); w3c.get("/groups{/:shortname}{/:type}", cors(), groupsRoute); +w3c.post("/update", authGithubWebhook(env("W3C_GROUPS_SECRET")), updateRoute); export default w3c; diff --git a/routes/w3c/update.ts b/routes/w3c/update.ts new file mode 100644 index 00000000..80e59bcc --- /dev/null +++ b/routes/w3c/update.ts @@ -0,0 +1,28 @@ +import path from "path"; + +import { Request, Response } from "express"; + +import { BackgroundTaskQueue } from "../../utils/background-task-queue.js"; + +import { reloadGroups } from "./group.js"; + +const workerFile = path.join(import.meta.dirname, "update.worker.js"); +const taskQueue = new BackgroundTaskQueue( + workerFile, + "w3c_groups_update", +); + +export default async function route(_req: Request, res: Response) { + const job = taskQueue.add(); + try { + const { updated } = await job.run(); + if (updated && !reloadGroups()) { + res.status(500); + } + } catch { + res.status(500); + } finally { + res.locals.job = job.id; + res.send(job.id); + } +} diff --git a/routes/w3c/update.worker.ts b/routes/w3c/update.worker.ts new file mode 100644 index 00000000..fd7d8801 --- /dev/null +++ b/routes/w3c/update.worker.ts @@ -0,0 +1,11 @@ +import w3cGroupsScraper from "../../scripts/update-w3c-groups-list.js"; + +export default async function w3cGroupsUpdate() { + try { + await w3cGroupsScraper(); + return { updated: true }; + } catch (error) { + console.error("W3C groups update failed:", error); + return { updated: false }; + } +}