docs: audit housekeeping hotel workflows
This commit is contained in:
1 parent
4d9845b52f
commit
949e14c109
2 files changed
+779
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { ownedLegacyPages } from "./discover-legacy-pages";
|
||||
import { hotelMigrationEntries } from "./hotel";
|
||||
import { validateMigrationEntries } from "./validate-matrix";
|
||||
|
||||
const HOTEL_PREFIXES = [
|
||||
"/admin/rooms",
|
||||
"/admin/navigation",
|
||||
"/admin/radio",
|
||||
"/admin/studio",
|
||||
] as const;
|
||||
|
||||
describe("hotelMigrationEntries", () => {
|
||||
it("maps all hotel tools below the hotel target root", () => {
|
||||
expect(
|
||||
validateMigrationEntries(
|
||||
ownedLegacyPages(HOTEL_PREFIXES),
|
||||
hotelMigrationEntries,
|
||||
),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
hotelMigrationEntries.every(
|
||||
(row) =>
|
||||
row.targetPath === null || row.targetPath.startsWith("/admin/hotel/"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves room ids while merging only the detail aliases", () => {
|
||||
expect(
|
||||
hotelMigrationEntries
|
||||
.filter((row) => row.legacyPath.startsWith("/admin/rooms"))
|
||||
.map(({ legacyPath, targetPath, decision }) => ({
|
||||
legacyPath,
|
||||
targetPath,
|
||||
decision,
|
||||
})),
|
||||
).toEqual([
|
||||
{
|
||||
legacyPath: "/admin/rooms",
|
||||
targetPath: "/admin/hotel/rooms",
|
||||
decision: "REBUILD",
|
||||
},
|
||||
{
|
||||
legacyPath: "/admin/rooms/:id",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
},
|
||||
{
|
||||
legacyPath: "/admin/rooms/:id/furni",
|
||||
targetPath: "/admin/hotel/rooms/:id/furni",
|
||||
decision: "REBUILD",
|
||||
},
|
||||
{
|
||||
legacyPath: "/admin/rooms/edit/:id",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
},
|
||||
{
|
||||
legacyPath: "/admin/rooms/show/:id",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps every audited workflow planned without parity claims", () => {
|
||||
expect(
|
||||
hotelMigrationEntries.every(
|
||||
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("requires privileged audit and an explicit capability for every mutation", () => {
|
||||
const mutationRows = hotelMigrationEntries.filter(
|
||||
(row) => row.dependencies.mutations.length > 0,
|
||||
);
|
||||
|
||||
expect(mutationRows.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
mutationRows.every(
|
||||
(row) =>
|
||||
row.capabilities.mutate.length > 0 &&
|
||||
row.auditRequirement === "PRIVILEGED_MUTATION",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("records the actual permissions for room, radio, and Studio mutations", () => {
|
||||
expect(hotelMigrationEntries).toContainEqual(
|
||||
expect.objectContaining({
|
||||
legacyPath: "/admin/rooms/edit/:id",
|
||||
capabilities: {
|
||||
read: [PERMS.ROOMS_EDIT],
|
||||
mutate: [PERMS.ROOMS_EDIT, PERMS.ROOMS_DELETE],
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(hotelMigrationEntries).toContainEqual(
|
||||
expect.objectContaining({
|
||||
legacyPath: "/admin/radio/api-keys",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(hotelMigrationEntries).toContainEqual(
|
||||
expect.objectContaining({
|
||||
legacyPath: "/admin/studio/sync",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,659 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import type { MigrationEntry } from "./types";
|
||||
|
||||
type PlannedHotelEntry = Omit<
|
||||
MigrationEntry,
|
||||
"targetDomain" | "requiredTests" | "parityEvidence" | "status" | "notes"
|
||||
> & {
|
||||
requiredTests?: MigrationEntry["requiredTests"];
|
||||
notes?: MigrationEntry["notes"];
|
||||
};
|
||||
|
||||
function plannedHotelEntry(entry: PlannedHotelEntry): MigrationEntry {
|
||||
return {
|
||||
...entry,
|
||||
targetDomain: "hotel",
|
||||
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
|
||||
parityEvidence: [],
|
||||
status: "PLANNED",
|
||||
notes: entry.notes ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
export const hotelMigrationEntries: readonly MigrationEntry[] = [
|
||||
// Batch 1: room directory, consolidated room detail, furniture, and navigator.
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/rooms",
|
||||
sourceFile: "src/app/admin/rooms/page.tsx",
|
||||
targetPath: "/admin/hotel/rooms",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.ROOMS_VIEW], mutate: [] },
|
||||
dependencies: {
|
||||
queries: ["fetchAdminList", "Rooms", "GET /api/admin/export?type=rooms"],
|
||||
mutations: [],
|
||||
},
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/rooms/:id",
|
||||
sourceFile: "src/app/admin/rooms/[id]/page.tsx",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
capabilities: { read: [PERMS.ROOMS_VIEW], mutate: [] },
|
||||
dependencies: { queries: [], mutations: [] },
|
||||
auditRequirement: "NONE",
|
||||
localization: "COMPLETE",
|
||||
accessibility: "COMPLETE",
|
||||
requiredTests: ["e2e"],
|
||||
notes: ["Redirect-only alias for the room detail workflow"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/rooms/:id/furni",
|
||||
sourceFile: "src/app/admin/rooms/[id]/furni/page.tsx",
|
||||
targetPath: "/admin/hotel/rooms/:id/furni",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ROOMS_VIEW],
|
||||
mutate: [PERMS.ROOMS_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["Items", "ItemsBase", "Rooms", "User", "loadRoom"],
|
||||
mutations: [
|
||||
"bulkDeleteRoomItems",
|
||||
"deleteRoomItem",
|
||||
"RCON kickall",
|
||||
"RCON reloadroom",
|
||||
"roomRconAction",
|
||||
"updateRoomItem",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Preserve the furniture suffix and room id in the target route"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/rooms/edit/:id",
|
||||
sourceFile: "src/app/admin/rooms/edit/[id]/page.tsx",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
capabilities: {
|
||||
read: [PERMS.ROOMS_EDIT],
|
||||
mutate: [PERMS.ROOMS_EDIT, PERMS.ROOMS_DELETE],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["Rooms", "User", "loadRoom"],
|
||||
mutations: [
|
||||
"deleteRoom",
|
||||
"RCON kickall",
|
||||
"RCON reloadroom",
|
||||
"RCON updateroom",
|
||||
"roomRconAction",
|
||||
"updateRoom",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Merge editing and RCON controls into the room detail workflow"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/rooms/show/:id",
|
||||
sourceFile: "src/app/admin/rooms/show/[id]/page.tsx",
|
||||
targetPath: "/admin/hotel/rooms/:id",
|
||||
decision: "MERGE",
|
||||
capabilities: { read: [PERMS.ROOMS_VIEW], mutate: [] },
|
||||
dependencies: { queries: ["Rooms", "User", "loadRoom"], mutations: [] },
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Merge the canonical read-only detail alias without changing its id",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/navigation",
|
||||
sourceFile: "src/app/admin/navigation/page.tsx",
|
||||
targetPath: "/admin/hotel/navigation",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.PAGES_VIEW], mutate: [] },
|
||||
dependencies: {
|
||||
queries: [
|
||||
"NavigatorFlatcats",
|
||||
"NavigatorPubliccats",
|
||||
"navigator_publics raw SQL",
|
||||
"Rooms",
|
||||
],
|
||||
mutations: [],
|
||||
},
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
|
||||
// Batch 2: radio overview, monitoring, moderation, and runtime configuration.
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio",
|
||||
sourceFile: "src/app/admin/radio/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/overview",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioApplications", "RadioSchedules", "RadioShouts"],
|
||||
mutations: ["deleteShout"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/api-keys",
|
||||
sourceFile: "src/app/admin/radio/api-keys/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/api-keys",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioApiKeys"],
|
||||
mutations: ["createApiKey", "deleteApiKey", "toggleApiKey"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"API-key generation and lifecycle changes are credential mutations",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/autodj",
|
||||
sourceFile: "src/app/admin/radio/autodj/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/autodj",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioAutoDjPlaylist"],
|
||||
mutations: ["createTrack", "deleteTrack", "toggleTrack"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/banners",
|
||||
sourceFile: "src/app/admin/radio/banners/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/banners",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioBanners"],
|
||||
mutations: [
|
||||
"createRadioBanner",
|
||||
"deleteRadioBanner",
|
||||
"updateRadioBanner",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/embed",
|
||||
sourceFile: "src/app/admin/radio/embed/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/embed",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.RADIO_VIEW], mutate: [] },
|
||||
dependencies: {
|
||||
queries: ["resolveHotelName", "siteSettings.get"],
|
||||
mutations: [],
|
||||
},
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/history",
|
||||
sourceFile: "src/app/admin/radio/history/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/history",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.RADIO_VIEW], mutate: [] },
|
||||
dependencies: { queries: ["RadioHistory", "User"], mutations: [] },
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/moderation",
|
||||
sourceFile: "src/app/admin/radio/moderation/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/moderation",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioShouts"],
|
||||
mutations: ["deleteShout"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Shout deletion is a radio moderation mutation"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/monitoring",
|
||||
sourceFile: "src/app/admin/radio/monitoring/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/monitoring",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.RADIO_VIEW], mutate: [] },
|
||||
dependencies: {
|
||||
queries: [
|
||||
"fetch radio_listeners_api_url",
|
||||
"fetch radio_now_playing_api_url",
|
||||
"siteSettings.get",
|
||||
],
|
||||
mutations: [],
|
||||
},
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Monitoring probes operator-configured external HTTP endpoints"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/points",
|
||||
sourceFile: "src/app/admin/radio/points/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/points",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["siteSettings.get", "WebsiteSetting"],
|
||||
mutations: ["savePoints", "siteSettings.reload"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/ranks",
|
||||
sourceFile: "src/app/admin/radio/ranks/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/ranks",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["RadioRanks"],
|
||||
mutations: ["createRadioRank", "deleteRadioRank", "updateRadioRank"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/radio/settings",
|
||||
sourceFile: "src/app/admin/radio/settings/page.tsx",
|
||||
targetPath: "/admin/hotel/radio/settings",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.RADIO_VIEW],
|
||||
mutate: [PERMS.RADIO_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["WebsiteSetting"],
|
||||
mutations: [
|
||||
"saveRadioSetting",
|
||||
"saveRadioSettings",
|
||||
"siteSettings.reload",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Settings include external-service credentials, runtime flags, webhooks, and custom code",
|
||||
],
|
||||
}),
|
||||
|
||||
// Batch 3: Studio audit and asset import workflows.
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio",
|
||||
sourceFile: "src/app/admin/studio/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/furni",
|
||||
decision: "MERGE",
|
||||
capabilities: { read: [PERMS.ASSETS_IMPORT], mutate: [] },
|
||||
dependencies: { queries: [], mutations: [] },
|
||||
auditRequirement: "NONE",
|
||||
localization: "COMPLETE",
|
||||
accessibility: "COMPLETE",
|
||||
requiredTests: ["e2e"],
|
||||
notes: ["Redirect-only entry into the Studio furniture workflow"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/audit",
|
||||
sourceFile: "src/app/admin/studio/audit/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/audit",
|
||||
decision: "REBUILD",
|
||||
capabilities: { read: [PERMS.ASSETS_IMPORT], mutate: [] },
|
||||
dependencies: {
|
||||
queries: ["POST /api/admin/import/audit", "runCatalogAudit"],
|
||||
mutations: [],
|
||||
},
|
||||
auditRequirement: "NONE",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"The POST endpoint performs a read-only database and filesystem audit",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/badges",
|
||||
sourceFile: "src/app/admin/studio/badges/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/badges",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT, PERMS.CATALOG_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [
|
||||
"GET /api/admin/import/badges",
|
||||
"getBadgeData",
|
||||
"searchBadges",
|
||||
"WebsiteBadges",
|
||||
],
|
||||
mutations: [
|
||||
"importBadgeSynced",
|
||||
"POST /api/admin/import/badges",
|
||||
"updateBadge",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Imports external badge assets into the configured gamedata filesystem",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/clone",
|
||||
sourceFile: "src/app/admin/studio/clone/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/clone",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [
|
||||
"GET /api/admin/import/clone",
|
||||
"GET /api/admin/import/clone/icon",
|
||||
"getCloneList",
|
||||
"listSources",
|
||||
],
|
||||
mutations: [
|
||||
"DELETE /api/admin/import/clone",
|
||||
"POST /api/admin/import/clone",
|
||||
"POST /api/admin/import/clone/batch",
|
||||
"cloneSingleFurni",
|
||||
"rcon.updateCatalog",
|
||||
"rcon.updateItems",
|
||||
"syncAssetsToGamedataBundle",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Clone imports combine external HTTP, database, filesystem, and RCON mutations",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/clothing",
|
||||
sourceFile: "src/app/admin/studio/clothing/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/clothing",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [
|
||||
"GET /api/admin/import/clothing",
|
||||
"GET /api/admin/import/clothing/sets",
|
||||
"getClothingSetList",
|
||||
"getFigureList",
|
||||
],
|
||||
mutations: [
|
||||
"DELETE /api/admin/import/clothing",
|
||||
"POST /api/admin/import/clothing/batch",
|
||||
"POST /api/admin/import/clothing/sets/batch",
|
||||
"importClothingSet",
|
||||
"importSingleFigure",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Clothing imports download and delete asset files"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/effects",
|
||||
sourceFile: "src/app/admin/studio/effects/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/effects",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["GET /api/admin/import/effects", "getEffectList"],
|
||||
mutations: [
|
||||
"DELETE /api/admin/import/effects",
|
||||
"importSingleEffect",
|
||||
"POST /api/admin/import/effects/batch",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Effect imports download and delete asset files"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/furni",
|
||||
sourceFile: "src/app/admin/studio/furni/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/furni",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [
|
||||
"GET /api/admin/import/clone",
|
||||
"GET /api/admin/import/furni",
|
||||
"getHabboGamedataHotel",
|
||||
"getTreeFlat",
|
||||
"previewAutoCatalog",
|
||||
"siteSettings.getBool",
|
||||
],
|
||||
mutations: [
|
||||
"deleteImportedFurni",
|
||||
"PATCH /api/admin/import/furni",
|
||||
"POST /api/admin/import/furni",
|
||||
"POST /api/admin/import/furni/batch",
|
||||
"PUT /api/admin/import/furni/nitro-editor",
|
||||
"rcon.updateCatalog",
|
||||
"rcon.updateItems",
|
||||
"setFurnidataTranslateEnabled",
|
||||
"syncAssetsToGamedataBundle",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Furniture imports combine official Habbo HTTP sources, catalog and item tables, asset files, and RCON reloads",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/maintenance",
|
||||
sourceFile: "src/app/admin/studio/maintenance/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/maintenance",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.CATALOG_EDIT],
|
||||
mutate: [PERMS.CATALOG_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["getFurniHealthAction", "previewAlignIdsAction"],
|
||||
mutations: [
|
||||
"applyAlignIdsAction",
|
||||
"fixCatalogOffersAction",
|
||||
"fixEverythingAction",
|
||||
"fixSpriteIdsAction",
|
||||
"reconcileIdsAction",
|
||||
"removeDuplicateItemsBaseAction",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["The current page relies on action-level catalog-edit enforcement"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/pets",
|
||||
sourceFile: "src/app/admin/studio/pets/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/pets",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [
|
||||
"GET /api/admin/import/pets",
|
||||
"GET /api/admin/import/pets/icon",
|
||||
"getPetList",
|
||||
],
|
||||
mutations: [
|
||||
"DELETE /api/admin/import/pets",
|
||||
"importSinglePet",
|
||||
"POST /api/admin/import/pets/batch",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Pet imports download and delete asset files"],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/repair-icons",
|
||||
sourceFile: "src/app/admin/studio/repair-icons/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/repair-icons",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [],
|
||||
mutations: [
|
||||
"POST /api/admin/import/furni/repair-icons",
|
||||
"repairMissingIcons",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Repairs icon files from local bundles or configured external sources",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/sync",
|
||||
sourceFile: "src/app/admin/studio/sync/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/sync",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["ItemsBase", "listSources"],
|
||||
mutations: [
|
||||
"appendFurniEntriesBatch",
|
||||
"cloneSingleFurni",
|
||||
"POST /api/admin/import/clone/sync-all",
|
||||
],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Full source sync mutates item data and asset files from external clone sources",
|
||||
],
|
||||
}),
|
||||
plannedHotelEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/studio/upload",
|
||||
sourceFile: "src/app/admin/studio/upload/page.tsx",
|
||||
targetPath: "/admin/hotel/studio/upload",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.ASSETS_IMPORT],
|
||||
mutate: [PERMS.ASSETS_IMPORT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: [],
|
||||
mutations: ["POST /api/admin/import/furni/upload", "uploadSingleFurni"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: ["Uploads mutate database, catalog, FurnitureData, and asset files"],
|
||||
}),
|
||||
];
|
||||
Reference in new issue
Block a user