Files
EpicNext-Cms/src/actions/furni-maintenance.ts
T
openhands 3e69b72513
CI / check (push) Failing after 26s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
feat(catalog): prevent and repair Arcturus emulator data errors
Block invalid catalog_items writes at the API level (points currency
allowlist, non-negative prices, positive amount, limited stack >= sold
count, unique sibling order numbers) and auto-assign unique order numbers
on bulk create. Add a catalog-maintenance scan + transactional repair that
fixes pre-existing rows: resets unsupported points_type, clamps negative
costs, sets amount to 1, raises limited_stack, renumbers duplicate orders
and deletes offers with missing page/item references. Surface the issue
count and a fix button in the admin maintenance panel.

Also: add enabled/retired flag to clone sources, classify poster and
currency furniture in item-kind, and remove the obsolete update-Nitrov3.sh.
2026-09-10 11:29:28 +02:00

139 lines
3.0 KiB
TypeScript

"use server";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import {
type ActionResult,
actionOk,
handleActionError,
} from "@/lib/safe-action-shared";
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
import type {
AlignResult,
DedupResult,
FixEmulatorResult,
FixOfferResult,
FixSpriteResult,
FurniHealth,
ReconcileResult,
} from "@/lib/services/furni-maintenance";
import * as maintenance from "@/lib/services/furni-maintenance";
async function guard() {
await requirePermission(PERMS.CATALOG_EDIT);
}
export async function getFurniHealthAction(): Promise<
ActionResult<{ health: FurniHealth }>
> {
try {
await guard();
const health = await maintenance.getFurniHealth();
return actionOk({ health });
} catch (e) {
return handleActionError(e);
}
}
export async function fixSpriteIdsAction(): Promise<
ActionResult<FixSpriteResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.fixSpriteIds());
});
} catch (e) {
return handleActionError(e);
}
}
export async function fixCatalogOffersAction(): Promise<
ActionResult<FixOfferResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.fixCatalogOffers());
});
} catch (e) {
return handleActionError(e);
}
}
export async function fixEmulatorIssuesAction(): Promise<
ActionResult<FixEmulatorResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.fixEmulatorIssues());
});
} catch (e) {
return handleActionError(e);
}
}
export async function reconcileIdsAction(): Promise<
ActionResult<ReconcileResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.reconcileIds());
});
} catch (e) {
return handleActionError(e);
}
}
export async function removeDuplicateItemsBaseAction(): Promise<
ActionResult<DedupResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.removeDuplicates());
});
} catch (e) {
return handleActionError(e);
}
}
export async function previewAlignIdsAction(): Promise<
ActionResult<AlignResult>
> {
try {
await guard();
return actionOk(await maintenance.forceItemsBaseIdsToFurnidata(false));
} catch (e) {
return handleActionError(e);
}
}
export async function applyAlignIdsAction(): Promise<
ActionResult<AlignResult>
> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.forceItemsBaseIdsToFurnidata(true));
});
} catch (e) {
return handleActionError(e);
}
}
export async function fixEverythingAction(input?: {
dedupePages?: boolean;
}): Promise<ActionResult<maintenance.FixAllResult>> {
try {
await guard();
return await withCatalogExport(async () => {
return actionOk(await maintenance.fixEverything(input ?? {}));
});
} catch (e) {
return handleActionError(e);
}
}