Enforce admin CSRF, harden catalog translate, use CMS hotel name for PayPal.
Local Build and Deploy / deploy (push) Successful in 1m38s

Mutating withAdmin routes now require a double-submit CSRF token; translate is capped at 500 items with audit logging; PayPal descriptions prefer siteSettings hotel_name.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-18 19:38:42 +02:00
1 parent 6b884ad25a
commit 2de3696993
18 files changed
+218 -62

No files matched your search

+24 -5
View File
@@ -5,9 +5,11 @@ import { Prisma } from "@/generated/prisma/client";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { logAudit } from "@/lib/services/audit";
import { allocateCatalogItemId } from "@/lib/services/furni-import";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
import { translateItemsSchema } from "@/lib/validators/catalog";
const CATALOG_ITEM_FIELDS = [
"pageId",
@@ -323,13 +325,19 @@ export async function updateCatalogItem({
return { ok: true as const, data: {} };
}
export async function translateCatalogItems({
items,
}: {
export async function translateCatalogItems(input: {
/** `id` is items_base.id (not catalog_items.id) */
items: Array<{ id: number; publicName: string; description: string }>;
items: Array<{ id: number; publicName: string; description?: string }>;
}) {
await requirePermission(PERMS.CATALOG_EDIT);
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const parsed = translateItemsSchema.safeParse(input);
if (!parsed.success) {
return {
ok: false as const,
error: parsed.error.issues[0]?.message ?? "Invalid translate payload",
};
}
const { items } = parsed.data;
const { invalidateFurniDataCache } = await import(
"@/lib/services/catalog-items-loader"
);
@@ -413,6 +421,17 @@ export async function translateCatalogItems({
}
await rcon.updateCatalog();
await logAudit({
userId: staff.id,
action: "items_base_translate",
target: "ItemsBase",
after: {
namesUpdated,
descriptionsUpdated,
furniDataUpdated: furniResult.updated > 0,
furniDataInserted: furniResult.inserted,
},
});
revalidatePath("/admin/catalog");
return {
ok: true as const,