diff --git a/client/src/components/DashboardActions.vue b/client/src/components/DashboardActions.vue index 2a6bbf7..4eb9bb5 100644 --- a/client/src/components/DashboardActions.vue +++ b/client/src/components/DashboardActions.vue @@ -6,12 +6,23 @@
diff --git a/server/src/adminApi.ts b/server/src/adminApi.ts index ec1bb5b..e4ab28f 100644 --- a/server/src/adminApi.ts +++ b/server/src/adminApi.ts @@ -959,4 +959,42 @@ async function sendPlayersActionsUpdate(client?: Response) { } } +// NameManager export endpoint +adminRouter.get("/admin/namemanager/export", async (req: Request, res: Response) => { + try { + const nameManager = NameManager.getInstance(); + const state = nameManager.exportState(); + res.json(state); + } catch (error) { + console.error('[AdminAPI] Error exporting nameManager state:', error); + res.status(500).json({ error: 'Failed to export nameManager state' }); + } +}); + +// NameManager import endpoint +adminRouter.post("/admin/namemanager/import", async (req: Request, res: Response) => { + try { + const nameManager = NameManager.getInstance(); + const state = req.body; + + if (!state || !state.data) { + return res.status(400).json({ error: 'Invalid state format' }); + } + + nameManager.importState(state); + + // Broadcast update to SSE clients after importing + await sendUuidsUpdate(); + await sendPlayersActionsUpdate(); + + res.json({ + success: true, + message: `NameManager state imported successfully. Version: ${state.version || 'unknown'}` + }); + } catch (error) { + console.error('[AdminAPI] Error importing nameManager state:', error); + res.status(500).json({ error: 'Failed to import nameManager state', details: error.message }); + } +}); + export { adminRouter, broadcastDashboardUpdate }; diff --git a/server/src/utils/nameManager.ts b/server/src/utils/nameManager.ts index aad017b..346992b 100644 --- a/server/src/utils/nameManager.ts +++ b/server/src/utils/nameManager.ts @@ -208,4 +208,101 @@ export class NameManager { clearSystemHistory(uuid: string): void { this.uuidToSystemHistory.delete(uuid); } + + // Export/Import methods for backup/restore functionality + exportState(): any { + return { + version: "1.0", + timestamp: Date.now(), + data: { + uuidToName: Object.fromEntries(this.uuidToName), + uuidToColor: Object.fromEntries(this.uuidToColor), + uuidToShame: Object.fromEntries(this.uuidToShame), + uuidToSystemHistory: Object.fromEntries(this.uuidToSystemHistory), + uuidToCurrentRoom: Object.fromEntries(this.uuidToCurrentRoom), + uuidToReconnectToken: Object.fromEntries(this.uuidToReconnectToken), + roomAssignments: Object.fromEntries(this.roomAssignments), + shuffleInProgress: this.shuffleInProgress + } + }; + } + + importState(state: any): void { + if (!state || !state.data) { + throw new Error('Invalid state format'); + } + + const { data } = state; + + // Clear current state + this.uuidToName.clear(); + this.uuidToColor.clear(); + this.uuidToShame.clear(); + this.uuidToSystemHistory.clear(); + this.uuidToCurrentRoom.clear(); + this.uuidToReconnectToken.clear(); + this.roomAssignments.clear(); + + // Import data + if (data.uuidToName) { + for (const [uuid, name] of Object.entries(data.uuidToName)) { + if (typeof name === 'string') { + this.uuidToName.set(uuid, name); + } + } + } + + if (data.uuidToColor) { + for (const [uuid, color] of Object.entries(data.uuidToColor)) { + if (typeof color === 'string') { + this.uuidToColor.set(uuid, color); + } + } + } + + if (data.uuidToShame) { + for (const [uuid, shame] of Object.entries(data.uuidToShame)) { + if (typeof shame === 'number') { + this.uuidToShame.set(uuid, shame); + } + } + } + + if (data.uuidToSystemHistory) { + for (const [uuid, history] of Object.entries(data.uuidToSystemHistory)) { + if (Array.isArray(history)) { + this.uuidToSystemHistory.set(uuid, history); + } + } + } + + if (data.uuidToCurrentRoom) { + for (const [uuid, roomId] of Object.entries(data.uuidToCurrentRoom)) { + if (typeof roomId === 'string') { + this.uuidToCurrentRoom.set(uuid, roomId); + } + } + } + + if (data.uuidToReconnectToken) { + for (const [uuid, token] of Object.entries(data.uuidToReconnectToken)) { + if (typeof token === 'string') { + this.uuidToReconnectToken.set(uuid, token); + } + } + } + + if (data.roomAssignments) { + for (const [uuid, assignment] of Object.entries(data.roomAssignments)) { + if (assignment && typeof assignment === 'object' && + 'roomId' in assignment && 'role' in assignment) { + this.roomAssignments.set(uuid, assignment as { roomId: string; role: 'P1' | 'P2' }); + } + } + } + + if (typeof data.shuffleInProgress === 'boolean') { + this.shuffleInProgress = data.shuffleInProgress; + } + } }