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"], }), ];