feat : add audit logs
This commit is contained in:
+51
-34
@@ -9,6 +9,7 @@ import {
|
||||
deleteGlossaryAlias,
|
||||
} from "@/lib/glossary-repository";
|
||||
import { requireAdmin } from "@/lib/auth/server";
|
||||
import { auditActor } from "@/lib/audit-log";
|
||||
import { publicGuideSections } from "@/lib/guides/public-sections";
|
||||
import { publicationIssues } from "@/lib/guides/publication";
|
||||
import { getAdminGuideContentById } from "@/lib/guides/queries";
|
||||
@@ -47,10 +48,11 @@ type GuideActionResult =
|
||||
type CreateGuideActionResult = Exclude<GuideActionResult, { status: "conflict" }>;
|
||||
type SaveIntent = { overwrite?: boolean };
|
||||
|
||||
function overwriteDetails(admin: Awaited<ReturnType<typeof requireAdmin>>, intent?: SaveIntent) {
|
||||
return intent?.overwrite
|
||||
? { operation: "overwrite" as const, actorName: (admin.user.name?.trim() || admin.user.email).slice(0, 100) }
|
||||
: undefined;
|
||||
function mutationContext(
|
||||
admin: Awaited<ReturnType<typeof requireAdmin>>,
|
||||
intent?: SaveIntent,
|
||||
) {
|
||||
return { actor: auditActor(admin.user), overwrite: intent?.overwrite };
|
||||
}
|
||||
|
||||
async function runGuideAction(action: () => Promise<{ version?: number }>): Promise<GuideActionResult> {
|
||||
@@ -69,9 +71,9 @@ async function runGuideAction(action: () => Promise<{ version?: number }>): Prom
|
||||
}
|
||||
|
||||
export async function createStructuredGuide(input: unknown): Promise<CreateGuideActionResult> {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
const guide = await createGuide(input);
|
||||
const guide = await createGuide(input, mutationContext(admin));
|
||||
return { status: "saved", version: guide.version, location: `/admin/${guide.characterKey}` };
|
||||
} catch (cause) {
|
||||
return { status: "error", message: cause instanceof Error ? cause.message : "สร้าง Guide ไม่สำเร็จ" };
|
||||
@@ -80,27 +82,27 @@ export async function createStructuredGuide(input: unknown): Promise<CreateGuide
|
||||
|
||||
export async function saveWeaponSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveWeapons(guideId, input, overwriteDetails(admin, intent)));
|
||||
return runGuideAction(() => saveWeapons(guideId, input, mutationContext(admin, intent)));
|
||||
}
|
||||
|
||||
export async function saveGuideOverview(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => updateGuideBasics(guideId, input, overwriteDetails(admin, intent)));
|
||||
return runGuideAction(() => updateGuideBasics(guideId, input, mutationContext(admin, intent)));
|
||||
}
|
||||
|
||||
export async function saveArtifactSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveArtifacts(guideId, input, overwriteDetails(admin, intent)));
|
||||
return runGuideAction(() => saveArtifacts(guideId, input, mutationContext(admin, intent)));
|
||||
}
|
||||
|
||||
export async function saveConstellationSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveConstellations(guideId, input, overwriteDetails(admin, intent)));
|
||||
return runGuideAction(() => saveConstellations(guideId, input, mutationContext(admin, intent)));
|
||||
}
|
||||
|
||||
export async function saveTeamSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveTeams(guideId, input, overwriteDetails(admin, intent)));
|
||||
return runGuideAction(() => saveTeams(guideId, input, mutationContext(admin, intent)));
|
||||
}
|
||||
|
||||
export async function listTeamImportGuides(currentGuideId: string) {
|
||||
@@ -129,9 +131,13 @@ export async function loadTeamImportGuide(guideId: string) {
|
||||
}
|
||||
|
||||
export async function createGuideExtra(guideId: string, input: unknown) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
const section = await createExtraSection(z.string().uuid().parse(guideId), input);
|
||||
const section = await createExtraSection(
|
||||
z.string().uuid().parse(guideId),
|
||||
input,
|
||||
mutationContext(admin),
|
||||
);
|
||||
updateTag(pageCacheTag(guideId));
|
||||
return {
|
||||
status: "saved" as const,
|
||||
@@ -160,18 +166,19 @@ export async function saveGuideExtra(
|
||||
const parsedGuideId = z.string().uuid().parse(guideId);
|
||||
const parsedSectionId = z.string().uuid().parse(sectionId);
|
||||
const result = await runGuideAction(() =>
|
||||
saveExtraSection(parsedGuideId, parsedSectionId, input, overwriteDetails(admin, intent)),
|
||||
saveExtraSection(parsedGuideId, parsedSectionId, input, mutationContext(admin, intent)),
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteGuideExtra(guideId: string, sectionId: string) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
const result = await runGuideAction(() =>
|
||||
deleteExtraSection(
|
||||
z.string().uuid().parse(guideId),
|
||||
z.string().uuid().parse(sectionId),
|
||||
mutationContext(admin),
|
||||
),
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
@@ -179,36 +186,40 @@ export async function deleteGuideExtra(guideId: string, sectionId: string) {
|
||||
}
|
||||
|
||||
export async function reorderGuideExtras(guideId: string, input: unknown) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
const result = await runGuideAction(() =>
|
||||
reorderExtraSections(z.string().uuid().parse(guideId), input),
|
||||
reorderExtraSections(
|
||||
z.string().uuid().parse(guideId),
|
||||
input,
|
||||
mutationContext(admin),
|
||||
),
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function moveGuideToTrash(guideId: string) {
|
||||
await requireAdmin();
|
||||
return runGuideAction(() => trashGuide(guideId));
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => trashGuide(guideId, mutationContext(admin)));
|
||||
}
|
||||
|
||||
export async function restoreStructuredGuide(guideId: string) {
|
||||
await requireAdmin();
|
||||
return runGuideAction(() => restoreGuide(guideId));
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => restoreGuide(guideId, mutationContext(admin)));
|
||||
}
|
||||
|
||||
export async function setStructuredGuideVisibility(
|
||||
guideId: string,
|
||||
input: unknown,
|
||||
) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
const parsed = z.strictObject({
|
||||
isPublic: z.boolean(),
|
||||
expectedVersion: z.number().int().positive(),
|
||||
}).safeParse(input);
|
||||
if (!parsed.success) return { status: "error" as const, message: "ข้อมูลการเผยแพร่ไม่ถูกต้อง" };
|
||||
if (!parsed.data.isPublic) {
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data));
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)));
|
||||
}
|
||||
|
||||
const guide = await getAdminGuideContentById(guideId);
|
||||
@@ -223,18 +234,21 @@ export async function setStructuredGuideVisibility(
|
||||
if (issues.length) {
|
||||
return { status: "error" as const, message: issues.join(" • "), issues };
|
||||
}
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data));
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)));
|
||||
}
|
||||
|
||||
export async function deleteStructuredGuide(guideId: string) {
|
||||
await requireAdmin();
|
||||
return runGuideAction(() => permanentlyDeleteGuide(guideId));
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => permanentlyDeleteGuide(guideId, mutationContext(admin)));
|
||||
}
|
||||
|
||||
export async function startCatalogSync() {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
return { status: "started" as const, job: await startCatalogSyncJob() };
|
||||
return {
|
||||
status: "started" as const,
|
||||
job: await startCatalogSyncJob(auditActor(admin.user)),
|
||||
};
|
||||
} catch (cause) {
|
||||
return { status: "error" as const, message: cause instanceof Error ? cause.message : "เริ่ม Sync ไม่สำเร็จ" };
|
||||
}
|
||||
@@ -246,14 +260,14 @@ export async function getCatalogSyncProgress(jobId: string) {
|
||||
}
|
||||
|
||||
export async function cancelCatalogSync(jobId: string) {
|
||||
await requireAdmin();
|
||||
return cancelCatalogSyncJob(jobId);
|
||||
const admin = await requireAdmin();
|
||||
return cancelCatalogSyncJob(jobId, auditActor(admin.user));
|
||||
}
|
||||
|
||||
export async function addGlossaryAlias(input: unknown) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
const alias = await createGlossaryAlias(input);
|
||||
const alias = await createGlossaryAlias(input, auditActor(admin.user));
|
||||
updateTag(GLOSSARY_CACHE_TAG);
|
||||
return { status: "saved" as const, alias };
|
||||
} catch (cause) {
|
||||
@@ -269,9 +283,12 @@ export async function addGlossaryAlias(input: unknown) {
|
||||
}
|
||||
|
||||
export async function removeGlossaryAlias(id: string) {
|
||||
await requireAdmin();
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
await deleteGlossaryAlias(z.string().uuid().parse(id));
|
||||
await deleteGlossaryAlias(
|
||||
z.string().uuid().parse(id),
|
||||
auditActor(admin.user),
|
||||
);
|
||||
updateTag(GLOSSARY_CACHE_TAG);
|
||||
return { status: "saved" as const };
|
||||
} catch (cause) {
|
||||
|
||||
Reference in New Issue
Block a user