aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_rooms.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/api_rooms.go')
-rw-r--r--internal/admin/api_rooms.go47
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),