Files
Epicnabbo-Catalogus-Updated…/src/features/housekeeping/migration/hotel.ts
T

695 lines
19 KiB
TypeScript

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: [PERMS.ASSETS_IMPORT],
},
dependencies: {
queries: ["runCatalogAudit"],
mutations: [
"POST /api/admin/import/audit",
"applyCatalogSql",
"ensureDirectories",
"repairDuplicateClassnames",
"repairFurniData",
"repairMissingIcons",
"repairMissingNitros",
"repairOrphanedCatalog",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: [
"Audit repairs can mutate asset files, catalog data, FurnitureData, and database structure",
],
}),
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/nitro-cleanup",
sourceFile: "src/app/admin/studio/nitro-cleanup/page.tsx",
targetPath: "/admin/hotel/studio/nitro-cleanup",
decision: "REBUILD",
capabilities: {
read: [PERMS.ASSETS_IMPORT],
mutate: [PERMS.ASSETS_IMPORT],
},
dependencies: {
queries: ["GET /api/admin/studio/nitro-cleanup", "scanFakeBrokenNitros"],
mutations: [
"DELETE /api/admin/studio/nitro-cleanup",
"clearFurniImportCache",
"deleteNitroCleanupFiles",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: ["Deletes orphaned or unparseable nitro bundles from asset files"],
}),
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"],
}),
];