"use server"; import { revalidatePath } from "next/cache"; import { requireStaff } from "@/lib/admin/guard"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; // ── Helpers ──────────────────────────────────────────────────────────────── /** Parse a FormData field into a positive BigInt id, or null when invalid. */ function parseId(raw: FormDataEntryValue | null): bigint | null { if (typeof raw !== "string" || raw.trim() === "") return null; try { const id = BigInt(raw.trim()); return id > 0n ? id : null; } catch { return null; } } function str(raw: FormDataEntryValue | null): string { return typeof raw === "string" ? raw : ""; } /** Checkbox/select truthiness: '1', 'true', 'on' → true. */ function bool(raw: FormDataEntryValue | null): boolean { const v = str(raw).trim().toLowerCase(); return v === "1" || v === "true" || v === "on"; } // ── Radio settings (website_settings radio_* keys) ───────────────────────── /** * Upsert one radio_* website_settings key. Mirrors AtomCMS's * RadioSettings Filament page (key/value rows in website_settings). Busts the * siteSettings cache so the public radio pages pick the change up immediately. */ export async function saveRadioSetting(formData: FormData): Promise { await requireStaff(); const key = str(formData.get("key")).trim().slice(0, 255); const value = str(formData.get("value")); const comment = str(formData.get("comment")).trim().slice(0, 255); if (!key) return; try { await prisma.websiteSetting.upsert({ where: { key }, update: { value }, create: { key, value, comment: comment || null }, }); siteSettings.reload(); } catch { // DB unavailable — fail soft so the action does not throw. } revalidatePath("/admin/radio/settings"); } /** * Bulk-save every radio_* field submitted by the settings form in one pass. * The form posts a hidden `__keys` field listing the keys it rendered so we * only touch those (and never wipe unrelated settings). */ export async function saveRadioSettings(formData: FormData): Promise { await requireStaff(); const keysRaw = str(formData.get("__keys")); const keys = keysRaw .split(",") .map((k) => k.trim()) .filter((k) => k.startsWith("radio_") || k.startsWith("auto_dj_")); if (keys.length === 0) return; try { await prisma.$transaction( keys.map((key) => { const value = str(formData.get(key)); return prisma.websiteSetting.upsert({ where: { key }, update: { value }, create: { key, value, comment: null }, }); }), ); siteSettings.reload(); } catch { // Fail soft. } revalidatePath("/admin/radio/settings"); } // ── Radio banners CRUD (radio_banners) ───────────────────────────────────── export async function createRadioBanner(formData: FormData): Promise { const staff = await requireStaff(); const imagePath = str(formData.get("imagePath")).trim().slice(0, 255); if (!imagePath) return; const title = str(formData.get("title")).trim().slice(0, 255); const description = str(formData.get("description")).trim(); const sortOrderNum = Number(str(formData.get("sortOrder"))); const sortOrder = Number.isFinite(sortOrderNum) ? Math.trunc(sortOrderNum) : 0; const isActive = bool(formData.get("isActive")); const now = new Date(); try { await prisma.radioBanners.create({ data: { userId: BigInt(staff.id), imagePath, title: title || null, description: description || null, sortOrder, isActive, createdAt: now, updatedAt: now, }, }); } catch { // Fail soft. } revalidatePath("/admin/radio/banners"); } export async function updateRadioBanner(formData: FormData): Promise { await requireStaff(); const id = parseId(formData.get("id")); if (id === null) return; const imagePath = str(formData.get("imagePath")).trim().slice(0, 255); const title = str(formData.get("title")).trim().slice(0, 255); const description = str(formData.get("description")).trim(); const sortOrderNum = Number(str(formData.get("sortOrder"))); const sortOrder = Number.isFinite(sortOrderNum) ? Math.trunc(sortOrderNum) : 0; const isActive = bool(formData.get("isActive")); if (!imagePath) return; try { await prisma.radioBanners.update({ where: { id }, data: { imagePath, title: title || null, description: description || null, sortOrder, isActive, updatedAt: new Date(), }, }); } catch { // Row may be gone; ignore. } revalidatePath("/admin/radio/banners"); } export async function deleteRadioBanner(formData: FormData): Promise { await requireStaff(); const id = parseId(formData.get("id")); if (id === null) return; try { await prisma.radioBanners.delete({ where: { id } }); } catch { // Already deleted; ignore. } revalidatePath("/admin/radio/banners"); } // ── Radio ranks CRUD (radio_ranks) ───────────────────────────────────────── export async function createRadioRank(formData: FormData): Promise { await requireStaff(); const name = str(formData.get("name")).trim().slice(0, 255); if (!name) return; const description = str(formData.get("description")).trim().slice(0, 255); const badgeCode = str(formData.get("badgeCode")).trim().slice(0, 255); const isActive = bool(formData.get("isActive")); const now = new Date(); try { await prisma.radioRanks.create({ data: { name, description: description || null, badgeCode: badgeCode || null, isActive, createdAt: now, updatedAt: now, }, }); } catch { // Fail soft. } revalidatePath("/admin/radio/ranks"); } export async function updateRadioRank(formData: FormData): Promise { await requireStaff(); const id = parseId(formData.get("id")); if (id === null) return; const name = str(formData.get("name")).trim().slice(0, 255); const description = str(formData.get("description")).trim().slice(0, 255); const badgeCode = str(formData.get("badgeCode")).trim().slice(0, 255); const isActive = bool(formData.get("isActive")); if (!name) return; try { await prisma.radioRanks.update({ where: { id }, data: { name, description: description || null, badgeCode: badgeCode || null, isActive, updatedAt: new Date(), }, }); } catch { // Row may be gone; ignore. } revalidatePath("/admin/radio/ranks"); } export async function deleteRadioRank(formData: FormData): Promise { await requireStaff(); const id = parseId(formData.get("id")); if (id === null) return; try { await prisma.radioRanks.delete({ where: { id } }); } catch { // Already deleted; ignore. } revalidatePath("/admin/radio/ranks"); }