70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
"use server";
|
|
import { revalidatePath } from "next/cache";
|
|
import { sendCatalogUpdate } from "@/features/catalog/server/sync-status";
|
|
import { historyChanges, sameSnapshot } from "@/features/history/model";
|
|
import {
|
|
applyHistory,
|
|
historySnapshot,
|
|
readHistory,
|
|
} from "@/features/history/server";
|
|
import { requirePermission } from "@/lib/admin/guard";
|
|
import { db } from "@/lib/db";
|
|
import { logger } from "@/lib/logger";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
|
|
import { invalidateNewsCache } from "@/lib/services/news-cache";
|
|
|
|
async function authorized(id: number) {
|
|
await requirePermission(PERMS.LOGS_VIEW);
|
|
if (!Number.isSafeInteger(id) || id < 1) throw Error("unavailable");
|
|
const entry = await db.transaction((tx) => readHistory(tx, id));
|
|
const staff = await requirePermission(
|
|
entry.kind === "news" ? PERMS.NEWS_EDIT : PERMS.CATALOG_EDIT,
|
|
);
|
|
return { entry, staff };
|
|
}
|
|
export async function previewHistoryRestore(id: number) {
|
|
const { entry } = await authorized(id);
|
|
return db.transaction(async (tx) => {
|
|
const current = await historySnapshot(tx, entry.kind, entry.targetId);
|
|
return {
|
|
canRestore: sameSnapshot(current, entry.after),
|
|
changes: historyChanges(current, entry.before),
|
|
};
|
|
});
|
|
}
|
|
export async function restoreHistory(id: number) {
|
|
const { entry, staff } = await authorized(id);
|
|
try {
|
|
const restore = () =>
|
|
db.transaction((tx) => applyHistory(tx, entry, staff.id));
|
|
if (entry.kind === "news") await restore();
|
|
else
|
|
await withCatalogExport(async () => {
|
|
await restore();
|
|
return { ok: true as const, data: {} };
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message === "conflict")
|
|
return { ok: false as const, code: "conflict" as const };
|
|
logger.error("History restore failed", { module: "history", error });
|
|
return { ok: false as const, code: "failed" as const };
|
|
}
|
|
try {
|
|
if (entry.kind === "news") {
|
|
await invalidateNewsCache();
|
|
revalidatePath("/news", "layout");
|
|
revalidatePath("/admin/articles", "layout");
|
|
revalidatePath("/");
|
|
revalidatePath("/me");
|
|
} else {
|
|
await sendCatalogUpdate();
|
|
revalidatePath("/admin/catalog", "layout");
|
|
}
|
|
} catch (error) {
|
|
logger.error("History refresh failed", { module: "history", error });
|
|
}
|
|
revalidatePath("/admin/logs/audit");
|
|
return { ok: true as const };
|
|
}
|