Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
267 lines
8.7 KiB
TypeScript
267 lines
8.7 KiB
TypeScript
"use server";
|
|
|
|
import config from "@payload-config";
|
|
import { isPayloadUser } from "@/utils/access-control/isPayloadUser";
|
|
import { getPayload } from "payload";
|
|
import type { User, WikiPage } from "@/payload-types";
|
|
import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification";
|
|
import { emitGameEvent } from "@/utils/event-log/emit";
|
|
import { EventTypes } from "@/utils/event-log/eventTypes";
|
|
import {
|
|
createPage,
|
|
deletePage,
|
|
restoreRevision,
|
|
setPageLockdown as setPageLockdownService,
|
|
updatePage,
|
|
} from "@/lib/wiki/service";
|
|
|
|
interface ActionResult<T = undefined> {
|
|
success: boolean;
|
|
error?: string;
|
|
data?: T;
|
|
}
|
|
|
|
async function authenticate() {
|
|
const payloadConfig = await config;
|
|
const payload = await getPayload({ config: payloadConfig });
|
|
const { headers } = await import("next/headers");
|
|
const hdrs = await headers();
|
|
const { user } = await payload.auth({
|
|
headers: hdrs,
|
|
canSetHeaders: false,
|
|
});
|
|
|
|
if (!isPayloadUser(user)) {
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
return { payload, user };
|
|
}
|
|
|
|
async function fetchPage(
|
|
payload: Awaited<ReturnType<typeof getPayload>>,
|
|
id: number,
|
|
): Promise<WikiPage | null> {
|
|
try {
|
|
return (await payload.findByID({
|
|
collection: "wiki-pages",
|
|
id,
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})) as unknown as WikiPage;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function isWikiModerator(
|
|
payload: Awaited<ReturnType<typeof getPayload>>,
|
|
user: User,
|
|
): Promise<boolean> {
|
|
return hasIntelligenceQualification(payload, user);
|
|
}
|
|
|
|
export async function createWikiPage(input: {
|
|
title: string;
|
|
category: string;
|
|
tags?: string[];
|
|
body: string;
|
|
summary?: string;
|
|
}): Promise<ActionResult<{ id: number; slug: string }>> {
|
|
try {
|
|
const { payload, user } = await authenticate();
|
|
const page = await createPage(payload, user, input);
|
|
await emitGameEvent(payload, {
|
|
type: EventTypes.WikiPageCreate,
|
|
message: `${user.displayName ?? user.username} created wiki page "${input.title}"`,
|
|
actor: user.id,
|
|
targetCollection: "wiki-pages",
|
|
targetId: page.id,
|
|
data: { slug: page.slug },
|
|
});
|
|
return { success: true, data: { id: page.id, slug: page.slug } };
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
export async function updateWikiPage(input: {
|
|
id: number;
|
|
title?: string;
|
|
body?: string;
|
|
category?: string;
|
|
tags?: string[];
|
|
summary?: string;
|
|
}): Promise<ActionResult<{ id: number; slug: string }>> {
|
|
try {
|
|
const { payload, user } = await authenticate();
|
|
const page = await fetchPage(payload, input.id);
|
|
if (!page) return { success: false, error: "Wiki page not found." };
|
|
const updated = await updatePage(payload, user, page, input);
|
|
await emitGameEvent(payload, {
|
|
type: EventTypes.WikiPageEdit,
|
|
message: `${user.displayName ?? user.username} edited wiki page "${updated.title}"`,
|
|
actor: user.id,
|
|
targetCollection: "wiki-pages",
|
|
targetId: updated.id,
|
|
data: { slug: updated.slug },
|
|
});
|
|
return { success: true, data: { id: updated.id, slug: updated.slug } };
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
export async function restoreWikiRevision(input: {
|
|
id: number;
|
|
revisionNumber: number;
|
|
}): Promise<ActionResult<{ id: number; slug: string }>> {
|
|
try {
|
|
const { payload, user } = await authenticate();
|
|
if (!(await isWikiModerator(payload, user))) {
|
|
return {
|
|
success: false,
|
|
error: "You need intelligence division clearance to restore wiki pages.",
|
|
};
|
|
}
|
|
const page = await fetchPage(payload, input.id);
|
|
if (!page) return { success: false, error: "Wiki page not found." };
|
|
const updated = await restoreRevision(payload, user, page, input.revisionNumber);
|
|
await emitGameEvent(payload, {
|
|
type: EventTypes.WikiPageRestore,
|
|
message: `${user.displayName ?? user.username} restored wiki page "${updated.title}" to revision ${input.revisionNumber}`,
|
|
actor: user.id,
|
|
targetCollection: "wiki-pages",
|
|
targetId: updated.id,
|
|
data: { slug: updated.slug, revisionNumber: input.revisionNumber },
|
|
});
|
|
return { success: true, data: { id: updated.id, slug: updated.slug } };
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
export async function setPageLockdown(input: {
|
|
id: number;
|
|
lockdown: boolean;
|
|
}): Promise<ActionResult<{ id: number; slug: string; lockdown: boolean }>> {
|
|
try {
|
|
const { payload, user } = await authenticate();
|
|
if (!(await isWikiModerator(payload, user))) {
|
|
return {
|
|
success: false,
|
|
error: "You need intelligence division clearance to lock wiki pages.",
|
|
};
|
|
}
|
|
const page = await fetchPage(payload, input.id);
|
|
if (!page) return { success: false, error: "Wiki page not found." };
|
|
const updated = await setPageLockdownService(payload, user, page, input.lockdown);
|
|
await emitGameEvent(payload, {
|
|
type: input.lockdown ? EventTypes.WikiPageLock : EventTypes.WikiPageUnlock,
|
|
message: `${user.displayName ?? user.username} ${input.lockdown ? "locked" : "unlocked"} wiki page "${updated.title}"`,
|
|
actor: user.id,
|
|
targetCollection: "wiki-pages",
|
|
targetId: updated.id,
|
|
data: { slug: updated.slug, lockdown: input.lockdown },
|
|
});
|
|
return {
|
|
success: true,
|
|
data: { id: updated.id, slug: updated.slug, lockdown: input.lockdown },
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
export async function deleteWikiPage(input: { id: number }): Promise<ActionResult<{ ok: true }>> {
|
|
try {
|
|
const { payload, user } = await authenticate();
|
|
if (!(await isWikiModerator(payload, user))) {
|
|
return {
|
|
success: false,
|
|
error: "You need intelligence division clearance to delete wiki pages.",
|
|
};
|
|
}
|
|
const page = await fetchPage(payload, input.id);
|
|
if (!page) return { success: false, error: "Wiki page not found." };
|
|
await deletePage(payload, user, page);
|
|
await emitGameEvent(payload, {
|
|
type: EventTypes.WikiPageDelete,
|
|
message: `${user.displayName ?? user.username} deleted wiki page "${page.title}"`,
|
|
actor: user.id,
|
|
targetCollection: "wiki-pages",
|
|
targetId: page.id,
|
|
data: { slug: page.slug },
|
|
});
|
|
return { success: true, data: { ok: true } };
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|
|
|
|
export async function uploadWikiImage(
|
|
formData: FormData,
|
|
): Promise<ActionResult<{ url: string; filename: string; alt: string }>> {
|
|
try {
|
|
const { payload } = await authenticate();
|
|
const file = formData.get("file");
|
|
if (!(file instanceof File)) {
|
|
return { success: false, error: "No image file provided." };
|
|
}
|
|
if (!file.type.startsWith("image/")) {
|
|
return { success: false, error: "Only image files can be uploaded." };
|
|
}
|
|
if (file.size > 8 * 1024 * 1024) {
|
|
return { success: false, error: "Images must be 8 MB or smaller." };
|
|
}
|
|
const alt =
|
|
file.name
|
|
.replace(/\.[^/.]+$/, "")
|
|
.replace(/[^a-zA-Z0-9 _-]/g, "")
|
|
.trim() || "Image";
|
|
const doc = await payload.create({
|
|
collection: "media",
|
|
data: { alt },
|
|
file: {
|
|
data: Buffer.from(await file.arrayBuffer()),
|
|
mimetype: file.type,
|
|
name: file.name,
|
|
size: file.size,
|
|
},
|
|
overrideAccess: true,
|
|
});
|
|
if (!doc.filename) {
|
|
return { success: false, error: "Upload failed: no file was stored." };
|
|
}
|
|
return {
|
|
success: true,
|
|
data: { url: doc.url ?? `/api/media/file/${doc.filename}`, filename: doc.filename, alt },
|
|
};
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
if (message === "Unauthorized") {
|
|
return { success: false, error: "You must be logged in." };
|
|
}
|
|
return { success: false, error: message };
|
|
}
|
|
}
|