package management import ( "encoding/json" "fmt" "net/http" "os" "strings" "github.com/gin-gonic/gin" ) // genericUpdateField handles PUT requests for a single field. func genericUpdateField[T any](h *Handler, c *gin.Context, set func(T)) { var body struct { Value *T `json:"value"` } if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"}) return } set(*body.Value) h.persist(c) } // genericListFiles handles listing files from a directory. func genericListFiles[T any]( c *gin.Context, dir string, filterMap func(os.DirEntry) (T, bool), // returns parsed item and true if it should be included sortFunc func([]T), // optional sort function ) { entries, err := os.ReadDir(dir) if err != nil { if os.IsNotExist(err) { c.JSON(http.StatusOK, gin.H{"files": []T{}}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to read dir: %v", err)}) return } files := make([]T, 0, len(entries)) for _, e := range entries { if item, ok := filterMap(e); ok { files = append(files, item) } } if sortFunc != nil { sortFunc(files) } c.JSON(http.StatusOK, gin.H{"files": files}) } // genericPutList handles PUT requests for a list of items. func genericPutList[T any](h *Handler, c *gin.Context, setList func([]T), normalize func(*T), sanitize func()) { data, err := c.GetRawData() if err != nil { c.JSON(400, gin.H{"error": "failed to read body"}) return } var arr []T if err = json.Unmarshal(data, &arr); err != nil { var obj struct { Items []T `json:"items"` } if err2 := json.Unmarshal(data, &obj); err2 != nil || len(obj.Items) == 0 { c.JSON(400, gin.H{"error": "invalid body"}) return } arr = obj.Items } if normalize != nil { for i := range arr { normalize(&arr[i]) } } setList(arr) if sanitize != nil { sanitize() } h.persist(c) } // genericPatchList handles PATCH requests for a list item. // T is the item type (e.g. config.GeminiKey). // P is the patch struct type (e.g. geminiKeyPatch). func genericPatchList[T any, P any]( h *Handler, c *gin.Context, getList func() []T, setList func([]T), matchFunc func(T, string) bool, // returns true if item matches the match string updateFunc func(*T, P) bool, // applies patch P to item T, returns false if item should be removed normalize func(*T), sanitize func(), ) { var body struct { Index *int `json:"index"` Match *string `json:"match"` Value *P `json:"value"` } if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { c.JSON(400, gin.H{"error": "invalid body"}) return } list := getList() targetIndex := -1 if body.Index != nil && *body.Index >= 0 && *body.Index < len(list) { targetIndex = *body.Index } if targetIndex == -1 && body.Match != nil { match := strings.TrimSpace(*body.Match) if match != "" && matchFunc != nil { for i := range list { if matchFunc(list[i], match) { targetIndex = i break } } } } if targetIndex == -1 { c.JSON(404, gin.H{"error": "item not found"}) return } // Work on a copy of the list to be safe, but we need to modify the list in place or reassign. // Since getList returns a slice, modifications to elements reflect in the underlying array usually, // but appending/slicing does not. // We will create a new slice for assignment. // Use a copy of the slice to avoid modifying the config in place before persisting (though persist calls save, config is in memory). // Actually we want to modify h.cfg in place then persist. entry := list[targetIndex] keep := updateFunc(&entry, *body.Value) if !keep { // Remove item list = append(list[:targetIndex], list[targetIndex+1:]...) } else { if normalize != nil { normalize(&entry) } list[targetIndex] = entry } setList(list) if sanitize != nil { sanitize() } h.persist(c) } // genericDeleteList handles DELETE requests for a list item. func genericDeleteList[T any]( h *Handler, c *gin.Context, getList func() []T, setList func([]T), shouldDelete func(T, string) bool, // returns true if item matches criteria and should be deleted queryParamName string, sanitize func(), ) { if val := strings.TrimSpace(c.Query(queryParamName)); val != "" { list := getList() out := make([]T, 0, len(list)) deleted := false for _, v := range list { if shouldDelete(v, val) { deleted = true continue } out = append(out, v) } if deleted { setList(out) if sanitize != nil { sanitize() } h.persist(c) } else { c.JSON(404, gin.H{"error": "item not found"}) } return } if idxStr := c.Query("index"); idxStr != "" { list := getList() var idx int if _, err := fmt.Sscanf(idxStr, "%d", &idx); err == nil && idx >= 0 && idx < len(list) { list = append(list[:idx], list[idx+1:]...) setList(list) if sanitize != nil { sanitize() } h.persist(c) return } } c.JSON(400, gin.H{"error": fmt.Sprintf("missing %s or index", queryParamName)}) }