From b888414c63c0e5fae0d33b3c5faea8a604d195fa Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Thu, 9 Jul 2026 17:12:03 -0400 Subject: feat(admin): multi-select and delete rooms --- internal/admin/api_rooms_bulk_delete.go | 132 ++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 internal/admin/api_rooms_bulk_delete.go (limited to 'internal/admin/api_rooms_bulk_delete.go') diff --git a/internal/admin/api_rooms_bulk_delete.go b/internal/admin/api_rooms_bulk_delete.go new file mode 100644 index 0000000..6aa0181 --- /dev/null +++ b/internal/admin/api_rooms_bulk_delete.go @@ -0,0 +1,132 @@ +package admin + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "thehouseoficarus/internal/world" +) + +type bulkDeleteRequest struct { + IDs []int `json:"ids"` +} + +func (s *AdminServer) handleBulkDelete(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + body, readErr := io.ReadAll(r.Body) + r.Body.Close() + if readErr != nil { + writeJSON(w, map[string]any{"error": fmt.Sprintf("read body: %v", readErr)}) + return + } + + var req bulkDeleteRequest + if err := json.Unmarshal(body, &req); err != nil { + writeJSON(w, map[string]any{"error": fmt.Sprintf("invalid json: %v", err)}) + return + } + + if len(req.IDs) == 0 { + writeJSON(w, map[string]any{"error": "no room ids"}) + return + } + + type roomSnapshot struct { + id int + path string + oldContent []byte + } + + snapshots := make([]roomSnapshot, 0, len(req.IDs)) + for _, id := range req.IDs { + path, ok := s.world.GetRoomPath(id) + if !ok { + writeJSON(w, map[string]any{"error": fmt.Sprintf("room %d not found", id)}) + return + } + oldContent, snapErr := snapshotFile(path) + if snapErr != nil { + writeJSON(w, map[string]any{"error": fmt.Sprintf("snapshot room %d: %v", id, snapErr)}) + return + } + snapshots = append(snapshots, roomSnapshot{id: id, path: path, oldContent: oldContent}) + } + + deletedSet := make(map[int]bool, len(req.IDs)) + for _, snap := range snapshots { + s.detachRoomFromCourse(snap.id) + if err := os.Remove(snap.path); err != nil { + writeJSON(w, map[string]any{"error": fmt.Sprintf("delete room %d: %v", snap.id, err)}) + return + } + deletedSet[snap.id] = true + } + + s.world.RebuildRoomIndex(s.dataDir) + for _, snap := range snapshots { + s.world.ClearRoomState(snap.id) + } + + allIDs, _ := listRoomIDs(s.dataDir) + var extraFiles []ExtraFile + var exitCleaned int + for _, otherID := range allIDs { + if deletedSet[otherID] { + continue + } + otherRoom, loadErr := s.world.LoadRoom(otherID) + if loadErr != nil { + continue + } + changed := false + for _, dir := range world.ExitOrder { + exit, exists := otherRoom.Exits[dir] + if !exists || !deletedSet[exit.Room] { + continue + } + delete(otherRoom.Exits, dir) + changed = true + } + if changed { + otherPath, pathOk := s.world.GetRoomPath(otherID) + if pathOk { + oldExtra, _ := snapshotFile(otherPath) + newContent, writeErr := writeYAMLFile(otherPath, otherRoom) + if writeErr != nil { + continue + } + extraFiles = append(extraFiles, ExtraFile{ + FilePath: otherPath, + OldContent: oldExtra, + NewContent: newContent, + }) + exitCleaned++ + } + } + } + + for i := 1; i < len(snapshots); i++ { + extraFiles = append(extraFiles, ExtraFile{ + FilePath: snapshots[i].path, + OldContent: snapshots[i].oldContent, + IsDelete: true, + }) + } + + s.undoStack.Push(ChangeDesc{ + Description: fmt.Sprintf("delete %d rooms (cleaned %d exits)", len(req.IDs), exitCleaned), + FilePath: snapshots[0].path, + OldContent: snapshots[0].oldContent, + IsDelete: true, + ExtraFiles: extraFiles, + }) + + writeJSON(w, map[string]any{"ok": true, "count": len(req.IDs)}) +} -- cgit v1.2.3