Local Build and Deploy / deploy (push) Successful in 54s
Gate pages and mutations on module PERMS instead of dashboard-only staff checks, add radio view/edit slugs with migration 0015, and expand the operations contract tests. Co-authored-by: Cursor <[email protected]>
110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
import { requirePermission } from "@/lib/admin/guard";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { slugify } from "@/lib/format";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
async function uniqueSlug(title: string): Promise<string> {
|
|
const base = slugify(title);
|
|
let slug = base;
|
|
let n = 2;
|
|
while (
|
|
await prisma.websiteArticles.findUnique({
|
|
where: { slug },
|
|
select: { id: true },
|
|
})
|
|
) {
|
|
slug = `${base}-${n++}`;
|
|
}
|
|
return slug;
|
|
}
|
|
|
|
export async function createArticle(formData: FormData): Promise<void> {
|
|
const staff = await requirePermission(PERMS.NEWS_EDIT);
|
|
const title = String(formData.get("title") ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
const shortStory = String(formData.get("shortStory") ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
const fullStory = String(formData.get("fullStory") ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
const image = String(formData.get("image") ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
if (!title) return;
|
|
|
|
try {
|
|
const now = new Date();
|
|
await prisma.websiteArticles.create({
|
|
data: {
|
|
slug: await uniqueSlug(title),
|
|
title: title.slice(0, 255),
|
|
shortStory: shortStory.slice(0, 255),
|
|
fullStory,
|
|
image: image.slice(0, 255),
|
|
userId: staff.id,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
},
|
|
});
|
|
} catch {
|
|
// Database error — re-render unchanged with error.
|
|
redirect(
|
|
"/admin/articles/new?error=Database error while creating article. Please try again.",
|
|
);
|
|
}
|
|
redirect("/admin/articles");
|
|
}
|
|
|
|
export async function updateArticle(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.NEWS_EDIT);
|
|
const id = BigInt(String(formData.get("id")));
|
|
try {
|
|
await prisma.websiteArticles.update({
|
|
where: { id },
|
|
data: {
|
|
title: String(formData.get("title") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255),
|
|
shortStory: String(formData.get("shortStory") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255),
|
|
fullStory: String(formData.get("fullStory") ?? "")
|
|
.normalize("NFC")
|
|
.trim(),
|
|
image: String(formData.get("image") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255),
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
} catch {
|
|
redirect("/admin/articles?error=Update failed");
|
|
}
|
|
revalidatePath(`/admin/articles/${id}`);
|
|
redirect("/admin/articles");
|
|
}
|
|
|
|
export async function deleteArticle(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.NEWS_EDIT);
|
|
const id = BigInt(String(formData.get("id")));
|
|
try {
|
|
await prisma.$transaction([
|
|
prisma.websiteArticleReactions.deleteMany({ where: { articleId: id } }),
|
|
prisma.websiteArticleComments.deleteMany({ where: { articleId: id } }),
|
|
prisma.websiteArticles.delete({ where: { id } }),
|
|
]);
|
|
} catch {
|
|
redirect("/admin/articles?error=Delete failed");
|
|
}
|
|
redirect("/admin/articles");
|
|
}
|