feat(catalog): add reviewed bulk edits and complete category duplication
CI / check (push) Successful in 1m14s
CI / deploy (push) Successful in 1m11s

This commit is contained in:
Simo committed 2026-09-06 20:25:19 +02:00
1 parent 0bedc04692
commit 55a47949f1
26 files changed
+2504 -85

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
"use server";
import { revalidatePath } from "next/cache";
import type { BulkOfferInput } from "@/features/catalog/domain/bulk-offers";
import {
applyBulkOffersCommand,
listBulkOfferDestinationsCommand,
previewBulkOffersCommand,
} from "@/features/catalog/server/bulk-offers";
import { catalogFailure } from "@/features/catalog/server/errors";
import { sendCatalogUpdate } from "@/features/catalog/server/sync-status";
import { requirePermission } from "@/lib/admin/guard";
import { logger } from "@/lib/logger";
import { PERMS } from "@/lib/permissions";
import { logAudit } from "@/lib/services/audit";
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
export async function previewBulkOffers(input: BulkOfferInput) {
await requirePermission(PERMS.CATALOG_VIEW);
try {
return { ok: true as const, data: await previewBulkOffersCommand(input) };
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
}
export async function applyBulkOffers(
input: BulkOfferInput,
fingerprint: string,
) {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
try {
return await withCatalogExport(async () => {
const data = await applyBulkOffersCommand(input, fingerprint);
if (data.changedCount > 0) {
await logAudit({
userId: staff.id,
action: "catalog_offers_bulk_update",
target: "catalog",
after: {
ids: input.ids,
changes: input.changes,
changedCount: data.changedCount,
},
}).catch((error) =>
logger.error("Catalog bulk update committed; audit write failed", {
module: "catalog",
error,
}),
);
await sendCatalogUpdate();
revalidatePath("/admin/catalog", "layout");
}
return { ok: true as const, data };
});
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
}
export async function getBulkOfferDestinations() {
await requirePermission(PERMS.CATALOG_VIEW);
try {
return {
ok: true as const,
data: await listBulkOfferDestinationsCommand(),
};
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
}
+66
View File
@@ -0,0 +1,66 @@
"use server";
import { revalidatePath } from "next/cache";
import type {
DuplicateInput,
DuplicatePreview,
} from "@/features/catalog/domain/duplicate";
import {
duplicateCategoryCommand,
duplicateDestinationsCommand,
previewDuplicateCommand,
} from "@/features/catalog/server/duplicate-commands";
import { catalogFailure } from "@/features/catalog/server/errors";
import { sendCatalogUpdate } from "@/features/catalog/server/sync-status";
import { requirePermission } from "@/lib/admin/guard";
import { logger } from "@/lib/logger";
import { PERMS } from "@/lib/permissions";
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
import { logStaffActivity } from "@/lib/services/staff-activity";
export async function previewCatalogDuplicate(input: DuplicateInput) {
await requirePermission(PERMS.CATALOG_VIEW);
try {
return { ok: true as const, data: await previewDuplicateCommand(input) };
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
}
export async function applyCatalogDuplicate(input: DuplicatePreview) {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
return withCatalogExport(async () => {
let data: Awaited<ReturnType<typeof duplicateCategoryCommand>>;
try {
data = await duplicateCategoryCommand(input);
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
const hotel = await sendCatalogUpdate();
await logStaffActivity({
staffId: staff.id,
action: "catalog_page_create",
description: `Duplicated ${input.kind} category #${input.sourceId} to #${data.id}: ${data.pages} categories, ${data.offers} offers`,
targetType: "catalog_page",
targetId: data.id,
}).catch((error) => {
logger.error("Category duplicated but audit logging failed", {
module: "catalog",
error,
});
});
revalidatePath("/admin/catalog");
return { ok: true as const, data: { ...data, hotel } };
});
}
export async function catalogDuplicateDestinations(
kind: DuplicateInput["kind"],
) {
await requirePermission(PERMS.CATALOG_VIEW);
try {
return {
ok: true as const,
data: await duplicateDestinationsCommand(kind),
};
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
}