diff options
| author | historia <[not public]> | 2026-09-16 21:05:39 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-16 21:05:39 -0400 |
| commit | 35ddd0ad2215760bb1d3dc7724480f179ea33fa6 (patch) | |
| tree | 3e9acf5e8a690544ecee1ec03af888ee28dd05b8 /internal/admin/api_rooms.go | |
| parent | 6ef40a5739f1723ec26cc32dfc48fe764f776dca (diff) | |
| download | thehouseoficarus-35ddd0ad2215760bb1d3dc7724480f179ea33fa6.tar.gz | |
fix: login account race, casino chip double-refund, room-create rollback, rename collision, fatal transport errors
Diffstat (limited to 'internal/admin/api_rooms.go')
| -rw-r--r-- | internal/admin/api_rooms.go | 47 |
1 files changed, 33 insertions, 14 deletions
diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go index 16fc7a0..f9508f6 100644 --- a/internal/admin/api_rooms.go +++ b/internal/admin/api_rooms.go @@ -144,11 +144,38 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { var extraFiles []ExtraFile + // Prepare the reciprocal exit in the destination map BEFORE the + // destination file is committed, so the written file is complete. + if linkFrom != nil && linkDir != nil && *linkDir != "" { + if dir := world.ExitDir(strings.ToLower(*linkDir)); dir != "" { + if opp, ok := world.OppositeExit[dir]; ok && !linkOneWay { + exits[string(opp)] = map[string]any{"room": *linkFrom} + } + } + } + + // The destination room is committed FIRST: if it cannot be written, + // nothing else has been mutated yet. The old order wrote the + // source room's new exit first, so a failed destination write left + // a dangling exit pointing at a room that never existed, with no + // undo entry to repair it. + path := filepath.Join(subdir, strconv.Itoa(id)+".yaml") + newContent, writeErr := writeMapAsYAML(path, m) + if writeErr != nil { + failJSON(w, fmt.Sprintf("write room: %v", writeErr)) + return + } + if linkFrom != nil && linkDir != nil && *linkDir != "" { dir := world.ExitDir(strings.ToLower(*linkDir)) - if opp, ok := world.OppositeExit[dir]; ok { + if _, ok := world.OppositeExit[dir]; ok { srcRoom, loadErr := s.world.LoadRoom(*linkFrom) if loadErr != nil { + // Roll back the destination so a failed create leaves + // no half-created room behind. + if rmErr := os.Remove(path); rmErr != nil { + log.Printf("room create rollback: remove %s: %v", path, rmErr) + } failJSON(w, fmt.Sprintf("load link_from room %d: %v", *linkFrom, loadErr)) return } @@ -160,31 +187,23 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { srcPath, srcOk := s.world.GetRoomPath(*linkFrom) if srcOk { oldContent, _ := snapshotFile(srcPath) - newContent, writeErr := writeYAMLFile(srcPath, srcRoom) + srcContent, writeErr := writeYAMLFile(srcPath, srcRoom) if writeErr != nil { + if rmErr := os.Remove(path); rmErr != nil { + log.Printf("room create rollback: remove %s: %v", path, rmErr) + } failJSON(w, fmt.Sprintf("write source room: %v", writeErr)) return } extraFiles = append(extraFiles, ExtraFile{ FilePath: srcPath, OldContent: oldContent, - NewContent: newContent, + NewContent: srcContent, }) } - - if !linkOneWay { - exits[string(opp)] = map[string]any{"room": *linkFrom} - } } } - path := filepath.Join(subdir, strconv.Itoa(id)+".yaml") - newContent, writeErr := writeMapAsYAML(path, m) - if writeErr != nil { - failJSON(w, fmt.Sprintf("write room: %v", writeErr)) - return - } - s.world.AddRoomPath(id, path) s.undoStack.Push(ChangeDesc{ Description: fmt.Sprintf("create room %d", id), |
