Revert "Merge pull request 'Complete Housekeeping migration and /ase cutover' (#52) from codex/housekeeping-complete into main"

This reverts commit 488b6e57c4, reversing
changes made to b506b4499a.
This commit is contained in:
Simo committed 2026-08-30 21:31:34 +02:00
1 parent 488b6e57c4
commit b1ddda66ff
802 files changed
+61370 -76659

No files matched your search

+195 -80
View File
@@ -1,119 +1,234 @@
"use server";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { executeLegacyHotelMutation } from "@/features/housekeeping/domains/hotel/services/mutations";
import { requirePermission } from "@/lib/admin/guard";
import { db, RadioBanners, RadioRanks, WebsiteSetting } from "@/lib/db";
import { logger } from "@/lib/logger";
import { PERMS } from "@/lib/permissions";
import { siteSettings } from "@/lib/services/site-settings";
function text(formData: FormData, key: string): string {
return String(formData.get(key) ?? "")
.normalize("NFC")
.trim();
// ── 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 enabled(formData: FormData, key: string): boolean {
return ["1", "true", "on"].includes(text(formData, key).toLowerCase());
function str(raw: FormDataEntryValue | null): string {
return typeof raw === "string" ? raw : "";
}
async function actor() {
return requirePermission(PERMS.RADIO_EDIT);
/** 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<void> {
const staff = await actor();
await executeLegacyHotelMutation(staff, "radio.settings.save-one", {
key: text(formData, "key"),
value: String(formData.get("value") ?? ""),
comment: text(formData, "comment") || undefined,
});
siteSettings.reload();
revalidatePath("/ase/hotel/radio/settings");
await requirePermission(PERMS.RADIO_EDIT);
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 db
.insert(WebsiteSetting)
.values({ key, value, comment: comment || null })
.onDuplicateKeyUpdate({ set: { value } });
siteSettings.reload();
} catch (err) {
logger.error("Failed to save radio setting", { err, key });
}
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<void> {
const staff = await actor();
const entries = text(formData, "__keys")
await requirePermission(PERMS.RADIO_EDIT);
const keysRaw = str(formData.get("__keys"));
const keys = keysRaw
.split(",")
.map((key) => key.trim())
.filter(Boolean)
.map((key) => ({ key, value: String(formData.get(key) ?? "") }));
await executeLegacyHotelMutation(staff, "radio.settings.save-many", {
entries,
});
siteSettings.reload();
revalidatePath("/ase/hotel/radio/settings");
.map((k) => k.trim())
.filter((k) => k.startsWith("radio_") || k.startsWith("auto_dj_"));
if (keys.length === 0) return;
try {
await Promise.all(
keys.map((key) => {
const value = str(formData.get(key));
return db
.insert(WebsiteSetting)
.values({ key, value, comment: null })
.onDuplicateKeyUpdate({ set: { value } });
}),
);
siteSettings.reload();
} catch (err) {
logger.error("Failed to bulk-save radio settings", { err, keys });
}
revalidatePath("/admin/radio/settings");
}
function bannerInput(formData: FormData) {
return {
imagePath: text(formData, "imagePath"),
title: text(formData, "title") || undefined,
description: text(formData, "description") || undefined,
sortOrder: Number(text(formData, "sortOrder") || 0),
isActive: enabled(formData, "isActive"),
};
}
// ── Radio banners CRUD (radio_banners) ─────────────────────────────────────
export async function createRadioBanner(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(
staff,
"radio.banner.create",
bannerInput(formData),
);
revalidatePath("/ase/hotel/radio/banners");
const staff = await requirePermission(PERMS.RADIO_EDIT);
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 db.insert(RadioBanners).values({
userId: BigInt(staff.id),
imagePath,
title: title || null,
description: description || null,
sortOrder,
isActive,
createdAt: now,
updatedAt: now,
});
} catch (err) {
logger.error("Failed to create radio banner", { err, imagePath });
}
revalidatePath("/admin/radio/banners");
}
export async function updateRadioBanner(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(staff, "radio.banner.update", {
id: text(formData, "id"),
...bannerInput(formData),
});
revalidatePath("/ase/hotel/radio/banners");
await requirePermission(PERMS.RADIO_EDIT);
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 db
.update(RadioBanners)
.set({
imagePath,
title: title || null,
description: description || null,
sortOrder,
isActive,
updatedAt: new Date(),
})
.where(eq(RadioBanners.id, id));
} catch (err) {
logger.error("Failed to update radio banner", { err, id: String(id) });
}
revalidatePath("/admin/radio/banners");
}
export async function deleteRadioBanner(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(staff, "radio.banner.delete", {
id: text(formData, "id"),
});
revalidatePath("/ase/hotel/radio/banners");
await requirePermission(PERMS.RADIO_EDIT);
const id = parseId(formData.get("id"));
if (id === null) return;
try {
await db.delete(RadioBanners).where(eq(RadioBanners.id, id));
} catch (err) {
logger.error("Failed to delete radio banner", { err, id: String(id) });
}
revalidatePath("/admin/radio/banners");
}
function rankInput(formData: FormData) {
return {
name: text(formData, "name"),
description: text(formData, "description") || undefined,
badgeCode: text(formData, "badgeCode") || undefined,
isActive: enabled(formData, "isActive"),
};
}
// ── Radio ranks CRUD (radio_ranks) ─────────────────────────────────────────
export async function createRadioRank(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(
staff,
"radio.rank.create",
rankInput(formData),
);
revalidatePath("/ase/hotel/radio/ranks");
await requirePermission(PERMS.RADIO_EDIT);
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 db.insert(RadioRanks).values({
name,
description: description || null,
badgeCode: badgeCode || null,
isActive,
createdAt: now,
updatedAt: now,
});
} catch (err) {
logger.error("Failed to create radio rank", { err, name });
}
revalidatePath("/admin/radio/ranks");
}
export async function updateRadioRank(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(staff, "radio.rank.update", {
id: text(formData, "id"),
...rankInput(formData),
});
revalidatePath("/ase/hotel/radio/ranks");
await requirePermission(PERMS.RADIO_EDIT);
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 db
.update(RadioRanks)
.set({
name,
description: description || null,
badgeCode: badgeCode || null,
isActive,
updatedAt: new Date(),
})
.where(eq(RadioRanks.id, id));
} catch (err) {
logger.error("Failed to update radio rank", { err, id: String(id) });
}
revalidatePath("/admin/radio/ranks");
}
export async function deleteRadioRank(formData: FormData): Promise<void> {
const staff = await actor();
await executeLegacyHotelMutation(staff, "radio.rank.delete", {
id: text(formData, "id"),
});
revalidatePath("/ase/hotel/radio/ranks");
await requirePermission(PERMS.RADIO_EDIT);
const id = parseId(formData.get("id"));
if (id === null) return;
try {
await db.delete(RadioRanks).where(eq(RadioRanks.id, id));
} catch (err) {
logger.error("Failed to delete radio rank", { err, id: String(id) });
}
revalidatePath("/admin/radio/ranks");
}