121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
"use server";
|
|
|
|
import { eq } from "drizzle-orm";
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
import { requirePermission } from "@/lib/admin/guard";
|
|
import {
|
|
db,
|
|
WebsiteArticleComments,
|
|
WebsiteArticleReactions,
|
|
WebsiteArticles,
|
|
} from "@/lib/db";
|
|
import { slugify } from "@/lib/format";
|
|
import { PERMS } from "@/lib/permissions";
|
|
|
|
async function uniqueSlug(title: string): Promise<string> {
|
|
const base = slugify(title);
|
|
let slug = base;
|
|
let n = 2;
|
|
for (;;) {
|
|
const [existing] = await db
|
|
.select({ id: WebsiteArticles.id })
|
|
.from(WebsiteArticles)
|
|
.where(eq(WebsiteArticles.slug, slug))
|
|
.limit(1);
|
|
if (!existing) return slug;
|
|
slug = `${base}-${n++}`;
|
|
}
|
|
}
|
|
|
|
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 db.insert(WebsiteArticles).values({
|
|
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 db
|
|
.update(WebsiteArticles)
|
|
.set({
|
|
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(),
|
|
})
|
|
.where(eq(WebsiteArticles.id, id));
|
|
} 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 db.transaction(async (tx) => {
|
|
await tx
|
|
.delete(WebsiteArticleReactions)
|
|
.where(eq(WebsiteArticleReactions.articleId, id));
|
|
await tx
|
|
.delete(WebsiteArticleComments)
|
|
.where(eq(WebsiteArticleComments.articleId, id));
|
|
await tx.delete(WebsiteArticles).where(eq(WebsiteArticles.id, id));
|
|
});
|
|
} catch {
|
|
redirect("/admin/articles?error=Delete failed");
|
|
}
|
|
redirect("/admin/articles");
|
|
}
|