Files
EpicNext-Cms/src/actions/admin-articles.ts
T
openhands 17847545dd
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m52s
Improvements: remove dead config, fix ESM, add URL validation, unify types, add missing logging
- Remove .prettierrc (dead config, Biome replaces Prettier)
- Rename lighthouserc.json to lighthouserc.cjs with module.exports for ESM compat
- Add logger.warn to empty catch blocks in auth, register, site-settings, prisma-cache, redis, security, rate-limit
- Unify ActionResult type: action-helper.ts uses 'ok' consistent with safe-action-shared.ts
- Add noUnusedLocals + noUnusedParameters to tsconfig + fix 25 pre-existing unused vars
- Replace barrel export src/types/index.ts with direct @/types/common imports
- Make trustHost conditional (development only) in auth.ts
- Add pre-flight URL validation to update-Nitrov3.sh to catch image.library.url misconfigurations
- Improve NITRO_IMAGE_LIBRARY_URL content validation in pre-flight & post-compute checks
2026-07-26 20:28:11 +02:00

113 lines
3.1 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requirePermission } from "@/lib/admin/guard";
import { slugify } from "@/lib/format";
import { PERMS } from "@/lib/permissions";
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();
const rawSlug = String(formData.get("slug") ?? "").trim();
if (!title) return;
try {
const now = new Date();
await prisma.websiteArticles.create({
data: {
slug: rawSlug ? await uniqueSlug(rawSlug) : 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")));
const rawSlug = String(formData.get("slug") ?? "").trim();
try {
await prisma.websiteArticles.update({
where: { id },
data: {
title: String(formData.get("title") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255),
...(rawSlug ? { slug: await uniqueSlug(rawSlug) } : {}),
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");
}