Files
EpicNext-Cms/src/actions/admin-radio-points.ts
T

95 lines
3.1 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requirePermission } from "@/lib/admin/guard";
import { db, WebsiteSetting } from "@/lib/db";
import { PERMS } from "@/lib/permissions";
import { siteSettings } from "@/lib/services/site-settings";
import { logStaffActivity } from "@/lib/services/staff-activity";
// Radio listener-points settings (website_settings radio_points_* keys).
// Mirrors AtomCMS's RadioPoints Filament page: key/value rows in
// website_settings that reward listeners for time spent on the radio. Booleans
// use the string '0' / '1'. Busts the siteSettings cache so the public radio
// pages pick the change up immediately.
const POINTS_KEYS = [
"radio_points_enabled",
"radio_points_per_minute",
"radio_points_currency",
"radio_points_max_per_day",
"radio_points_min_listeners",
] as const;
const ALLOWED_CURRENCIES = new Set([
"credits",
"duckets",
"diamonds",
"points",
]);
function str(raw: FormDataEntryValue | null): string {
return typeof raw === "string" ? raw : "";
}
/** Checkbox/select truthiness → '1' / '0'. */
function boolStr(raw: FormDataEntryValue | null): "0" | "1" {
const v = str(raw).trim().toLowerCase();
return v === "1" || v === "true" || v === "on" ? "1" : "0";
}
/** Clamp a form value to a non-negative integer string (defaulting to 0). */
function intStr(raw: FormDataEntryValue | null): string {
const n = Number(str(raw).trim());
if (!Number.isFinite(n) || n < 0) return "0";
return String(Math.floor(n));
}
export async function savePoints(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.RADIO_EDIT);
const currencyRaw = str(formData.get("radio_points_currency"))
.trim()
.toLowerCase();
const currency = ALLOWED_CURRENCIES.has(currencyRaw)
? currencyRaw
: "credits";
const values: Record<(typeof POINTS_KEYS)[number], string> = {
radio_points_enabled: boolStr(formData.get("radio_points_enabled")),
radio_points_per_minute: intStr(formData.get("radio_points_per_minute")),
radio_points_currency: currency,
radio_points_max_per_day: intStr(formData.get("radio_points_max_per_day")),
radio_points_min_listeners: intStr(
formData.get("radio_points_min_listeners"),
),
};
try {
await Promise.all(
POINTS_KEYS.map((key) =>
db
.insert(WebsiteSetting)
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const
.values({ key, value: values[key], comment: "Radio points" })
.onDuplicateKeyUpdate({
// eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const
set: { value: values[key] },
}),
),
);
siteSettings.reload();
await logStaffActivity({
staffId: staff.id,
action: "radio_points_update",
description: `Updated radio listener-points settings (enabled=${values.radio_points_enabled}, ${values.radio_points_per_minute}/min ${currency})`,
});
} catch {
// DB unavailable — fail soft so the action does not throw.
}
revalidatePath("/admin/radio/points");
redirect("/admin/radio/points?saved=1");
}