feat(cms): improve catalog, editorial recovery and operations
CI / check (push) Successful in 52s
CI / deploy (push) Successful in 2m10s
CI / publish-container (push) Failing after 1m18s

This commit is contained in:
Simo committed 2026-09-09 19:36:15 +02:00
1 parent 2fead134e6
commit c389c3893d
122 files changed
+8137 -703

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
import type { CmsErrorRecord } from "./error-monitor";
export function errorOperationLink(record: CmsErrorRecord): string | null {
const context = record.context;
if (record.source === "server") {
if (context.module === "news") {
const id = String(context.articleId ?? "");
return /^[1-9]\d{0,18}$/.test(id)
? `/admin/articles/${id}`
: "/admin/articles";
}
const routes: Record<string, string> = {
catalog: "/admin/catalog",
media: "/admin/media",
users: "/admin/users",
settings: "/admin/settings",
studio: "/admin/studio",
};
if (
typeof context.module === "string" &&
Object.hasOwn(routes, context.module)
)
return routes[context.module];
}
// Never use arbitrary error-provided URLs as navigation targets.
const candidate = context.path;
if (typeof candidate !== "string") return null;
if (
/^\/admin\/(articles|users|catalog|media|settings|studio)$/.test(candidate)
)
return candidate;
if (/^\/admin\/articles\/[1-9]\d{0,18}$/.test(candidate)) return candidate;
return null;
}
export function summarizeErrorGroup(events: CmsErrorRecord[]) {
const sorted = [...events].sort((a, b) => a.at.localeCompare(b.at));
const first = sorted[0];
const latest = sorted.at(-1);
const users = new Set<string>();
let unidentified = 0;
const releases = new Map<
string,
{ release: string; count: number; firstAt: string; lastAt: string }
>();
for (const event of sorted) {
const raw = event.source === "server" ? event.context.userId : null;
const id =
typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0
? String(raw)
: typeof raw === "string" && /^[1-9]\d{0,14}$/.test(raw)
? raw
: null;
if (id) users.add(id);
else unidentified++;
const prior = releases.get(event.release);
releases.set(event.release, {
release: event.release,
count: (prior?.count ?? 0) + 1,
firstAt: prior?.firstAt ?? event.at,
lastAt: event.at,
});
}
return {
count: events.length,
firstAt: first?.at ?? null,
lastAt: latest?.at ?? null,
affectedUsers: users.size,
unidentified,
releases: [...releases.values()],
latest,
operationLink: latest ? errorOperationLink(latest) : null,
};
}