feat(worker): EOC push-agent for ops summary
- Add EOC_URL + EOC_PUSH_TOKEN config - Add pushOpsSummaryToEoc processor - Schedule PUSH_OPS_SUMMARY_TO_EOC every 2 minutes - Add unit tests
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import type { Redis } from "ioredis";
|
||||
import { CACHE_KEY } from "./ops-summary.js";
|
||||
|
||||
export interface EocPushResult {
|
||||
ok: boolean;
|
||||
status: number | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const EOC_PUSH_TIMEOUT_MS = 15_000;
|
||||
|
||||
function ingestUrl(base: string): string {
|
||||
const trimmed = base.trim().replace(/\/+$/, "");
|
||||
return `${trimmed}/api/v1/apps/cibello/ingest`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the cached ops summary to EOC. Never throws; failures are returned
|
||||
* as `ok: false` and must be logged by the caller.
|
||||
*/
|
||||
export async function pushOpsSummaryToEoc(redis: Redis): Promise<EocPushResult> {
|
||||
const eocUrl = process.env.EOC_URL?.trim();
|
||||
const token = process.env.EOC_PUSH_TOKEN?.trim();
|
||||
|
||||
if (!eocUrl || !token) {
|
||||
return { ok: false, status: null, error: "EOC_URL or EOC_PUSH_TOKEN not configured" };
|
||||
}
|
||||
|
||||
const json = await redis.get(CACHE_KEY);
|
||||
if (!json) {
|
||||
return { ok: false, status: null, error: "ops summary cache miss" };
|
||||
}
|
||||
|
||||
// Guard against accidentally sending non-object payloads.
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(json);
|
||||
} catch {
|
||||
return { ok: false, status: null, error: "ops summary cache contains invalid JSON" };
|
||||
}
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
||||
return { ok: false, status: null, error: "ops summary cache is not a JSON object" };
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), EOC_PUSH_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const res = await fetch(ingestUrl(eocUrl), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: json,
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (res.status === 204) {
|
||||
return { ok: true, status: 204, error: null };
|
||||
}
|
||||
|
||||
const text = await res.text().catch(() => "<unreadable body>");
|
||||
return { ok: false, status: res.status, error: text.slice(0, 500) };
|
||||
} catch (err) {
|
||||
clearTimeout(timeout);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return { ok: false, status: null, error: message };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user