addition of new dashboard

This commit is contained in:
2026-08-20 15:08:32 +00:00
parent 0622018d95
commit bf59249ed7
161 changed files with 13170 additions and 119 deletions

View File

@@ -0,0 +1,78 @@
import axios from "axios";
import { createHash } from "crypto";
import { getPool } from "../db/index.js";
import { getConfig } from "../common/config.js";
import { firstNonBlankLine } from "./firstNonBlankLine.js";
// Delay (ms) before the next attempt, given how many have already failed.
// Exponential (startingDelay * factor^priorFailures), capped at maxDelay.
function backoffMs(priorFailures, { startingDelayMs, maxDelayMs, factor }) {
return Math.min(maxDelayMs, startingDelayMs * Math.pow(factor, priorFailures));
}
// Best-effort remote agreement fetch. A failure is logged, never thrown. Local
// URLs and already-cached agreements are skipped. Failures are tracked in
// agreement_fetch with exponential backoff so an invalid URL isn't re-fetched on
// every referencing create; after maxRetries we stop trying.
export async function cacheAgreementMarkdown({ userId = null, agreementUuid, uri }, _client) {
const config = getConfig();
if (typeof uri !== "string" || !uri || !agreementUuid) return;
if (uri.startsWith(config.publicCoreUrl)) return;
const client = _client || await getPool();
try {
const cached = await client.query(
`SELECT 1 FROM agreement_content WHERE agreement_uuid = $1 AND markdown IS NOT NULL`,
[agreementUuid],
);
if (cached.rowCount > 0) return;
const { maxRetries } = config.agreementCache;
const prior = await client.query(
`SELECT attempts, next_attempt_ts FROM agreement_fetch WHERE agreement_uuid = $1`,
[agreementUuid],
);
const attempts = prior.rows[0]?.attempts ?? 0;
if (attempts >= maxRetries) return; // gave up
if (prior.rowCount > 0 && new Date(prior.rows[0].next_attempt_ts) > new Date()) return; // not due yet
try {
const res = await axios.get(uri, { responseType: "text" });
const markdown = String(res.data ?? "").trim();
if (!markdown) throw new Error("empty response");
const title = firstNonBlankLine(markdown);
const hash = createHash("sha256").update(markdown).digest("hex");
// Content is immutable once inserted — INSERT only, never overwrite.
await client.query(
`INSERT INTO agreement_content (user_id, title, markdown, hash, agreement_uuid, remote_url)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT DO NOTHING`,
[userId, title, markdown, hash, agreementUuid, uri],
);
await client.query(`DELETE FROM agreement_fetch WHERE agreement_uuid = $1`, [agreementUuid]);
} catch (fetchErr) {
await recordFailure(client, agreementUuid, uri, attempts, fetchErr.message, config.agreementCache);
}
} catch (e) {
console.error(`cacheAgreementMarkdown(${agreementUuid}):`, e.message);
} finally {
if (!_client) await client.release();
}
}
// Bump the attempt counter and schedule the next try with exponential backoff.
async function recordFailure(client, agreementUuid, uri, priorFailures, message, cfg) {
const delayMs = Math.round(backoffMs(priorFailures, cfg));
await client.query(
`INSERT INTO agreement_fetch (agreement_uuid, remote_url, attempts, next_attempt_ts, last_error)
VALUES ($1, $2, $3, NOW() + ($4 || ' milliseconds')::interval, $5)
ON CONFLICT (agreement_uuid) DO UPDATE SET
attempts = $3,
next_attempt_ts = NOW() + ($4 || ' milliseconds')::interval,
last_error = $5,
remote_url = $2,
updated_ts = NOW()`,
[agreementUuid, uri, priorFailures + 1, String(delayMs), message],
);
}