package admin import ( "fmt" "net/http" "os" "path/filepath" "strings" ) func (s *AdminServer) handleItems(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: tree, err := listYAMLTree(s.dataDir, "items") if err != nil { http.Error(w, `{"error":"failed to list items"}`, http.StatusInternalServerError) return } if tree == nil { tree = &YAMLTree{Root: []string{}, Dirs: map[string][]string{}} } writeJSON(w, tree) case http.MethodPost: s.createItem(w, r) default: http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed) } } func (s *AdminServer) handleItemByID(w http.ResponseWriter, r *http.Request) { id := strings.TrimPrefix(r.URL.Path, "/api/items/") if id == "" { http.Error(w, `{"error":"missing item id"}`, http.StatusBadRequest) return } switch r.Method { case http.MethodGet: s.getItem(w, r, id) case http.MethodPut: s.updateItem(w, r, id) case http.MethodDelete: s.deleteItem(w, r, id) default: http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed) } } func (s *AdminServer) findItemFile(id string) ([]byte, string, error) { data, path, err := readYAMLFile(s.dataDir, "items", id) if err == nil { return data, path, nil } return findYAMLFileInSubdirs(s.dataDir, "items", id) } func (s *AdminServer) getItem(w http.ResponseWriter, r *http.Request, id string) { data, path, err := s.findItemFile(id) if err != nil { http.Error(w, `{"error":"item not found"}`, http.StatusNotFound) return } m, err := yamlToMap(data) if err != nil { http.Error(w, `{"error":"failed to parse item yaml"}`, http.StatusInternalServerError) return } m["_path"] = path m["_raw"] = string(data) writeJSON(w, m) } func (s *AdminServer) updateItem(w http.ResponseWriter, r *http.Request, id string) { _, path, err := s.findItemFile(id) if err != nil { http.Error(w, `{"error":"item not found"}`, http.StatusNotFound) return } var m map[string]any if err := readJSON(r, &m); err != nil { http.Error(w, `{"error":"invalid json body"}`, http.StatusBadRequest) return } m["id"] = id oldContent, err := snapshotFile(path) if err != nil { http.Error(w, `{"error":"failed to read existing item"}`, http.StatusInternalServerError) return } newContent, err := writeMapAsYAML(path, m) if err != nil { http.Error(w, `{"error":"failed to write item"}`, http.StatusInternalServerError) return } m["_raw"] = string(newContent) s.undoStack.Push(ChangeDesc{ Description: fmt.Sprintf("Update item %s", id), FilePath: path, OldContent: oldContent, NewContent: newContent, }) writeJSON(w, m) } func (s *AdminServer) deleteItem(w http.ResponseWriter, r *http.Request, id string) { _, path, err := s.findItemFile(id) if err != nil { http.Error(w, `{"error":"item not found"}`, http.StatusNotFound) return } oldContent := backupFile(path) if err := os.Remove(path); err != nil { http.Error(w, `{"error":"failed to delete item"}`, http.StatusInternalServerError) return } s.undoStack.Push(ChangeDesc{ Description: fmt.Sprintf("Delete item %s", id), FilePath: path, OldContent: oldContent, IsDelete: true, }) writeJSON(w, map[string]any{"deleted": id}) } func (s *AdminServer) createItem(w http.ResponseWriter, r *http.Request) { var m map[string]any if err := readJSON(r, &m); err != nil { http.Error(w, `{"error":"invalid json body"}`, http.StatusBadRequest) return } id, _ := m["id"].(string) if strings.TrimSpace(id) == "" { http.Error(w, `{"error":"missing item id"}`, http.StatusBadRequest) return } if _, _, err := s.findItemFile(id); err == nil { http.Error(w, `{"error":"item already exists"}`, http.StatusConflict) return } path := filepath.Join(s.dataDir, "items", id+".yaml") newContent, err := writeMapAsYAML(path, m) if err != nil { http.Error(w, `{"error":"failed to write item"}`, http.StatusInternalServerError) return } s.undoStack.Push(ChangeDesc{ Description: fmt.Sprintf("Create item %s", id), FilePath: path, NewContent: newContent, IsCreate: true, }) writeJSON(w, m) }