API / internal /api /handlers /management /generics.go
sshinmen's picture
Refactor auth handlers and executors
9b1c663
Raw
History Blame Contribute Delete
5.07 kB
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)})
}