'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'); }