feat: Implement Telegram alerts for catalog updates and failures

This commit is contained in:
2026-10-20 23:14:32 +03:00
parent a95784df93
commit e28d65310d
10 changed files with 541 additions and 43 deletions

View File

@@ -1,4 +1,5 @@
import { createServer } from "node:http";
import { createHash } from "node:crypto";
import { readFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -22,6 +23,12 @@ const files = {
[`${FOLDER}Медленный.docx`]: "slow",
};
const folders = [SHARE, `${SHARE}${YEAR}/`, `${SHARE}${YEAR}/Финансово-экономический факультет/`, FOLDER];
// Dates of files changed during a test; the others keep MODIFIED.
const dates = {};
const dateOf = (path) => dates[path] || MODIFIED;
// As in Nextcloud, a folder's ETag changes whenever anything below it does.
const etagOf = (folder) =>
`"${createHash("sha1").update(Object.keys(files).filter((f) => f.startsWith(folder)).sort().map((f) => f + dateOf(f)).join("|")).digest("hex").slice(0, 13)}"`;
const encode = (path) => path.split("/").map(encodeURIComponent).join("/");
const listing = (folder) => {
@@ -32,19 +39,23 @@ const listing = (folder) => {
const size = Buffer.isBuffer(files[path]) ? files[path].length : 1000;
return `<d:response><d:href>${encode(path)}</d:href><d:propstat><d:prop>${
folder ? "<d:resourcetype><d:collection/></d:resourcetype>" : `<d:resourcetype/><d:getcontentlength>${size}</d:getcontentlength>`
}<d:getlastmodified>${MODIFIED}</d:getlastmodified></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>`;
}<d:getlastmodified>${dateOf(path)}</d:getlastmodified><d:getetag>${folder ? etagOf(path) : `"${dateOf(path)}"`}</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>`;
};
return `<?xml version="1.0"?><d:multistatus xmlns:d="DAV:">${[folder, ...children].map(entry).join("")}</d:multistatus>`;
};
/**
* Starts the stand-in and the API server; `env` adds settings such as lower limits.
* Returns the server's base URL, request statistics of the stand-in and `stop()`.
* Returns the server's base URL, request statistics of the stand-in, `catalog` to change the
* stand-in's documents during a test, and `stop()`.
*/
export async function startApi(env = {}) {
const stats = { requests: 0, connections: 0 };
const stats = { requests: 0, connections: 0, listings: 0 };
let down = false;
const source = createServer((req, res) => {
stats.requests++;
if (req.method === "PROPFIND") stats.listings++;
if (down) return res.writeHead(503).end();
const path = decodeURIComponent(req.url);
if (req.method === "PROPFIND" && folders.includes(path)) {
res.writeHead(207, { "Content-Type": "application/xml" });
@@ -67,9 +78,22 @@ export async function startApi(env = {}) {
const { app } = await import("../../server/index.js");
const server = app.listen(0, "127.0.0.1");
await new Promise((resolve) => server.on("listening", resolve));
let tick = 0;
return {
base: `http://127.0.0.1:${server.address().port}`,
stats,
catalog: {
/** Publishes a document in the faculty folder (or republishes it) with a new date. */
put(name, body) {
const path = FOLDER + name;
files[path] = body;
dates[path] = new Date(Date.parse(MODIFIED) + ++tick * 60_000).toUTCString();
},
/** The university stops (true) or starts again (false) answering. */
down(on) {
down = on;
},
},
stop() {
server.close();
server.closeAllConnections();