// Path: Backend/internal/httpx/api_project_decode.go package httpx import ( "encoding/json" "errors" "io" "net/http" "strings" ) type createProjectFolderRequest struct { Name string `json:"name"` ParentFolderPath string `json:"parentFolderId"` } type renameProjectFolderRequest struct { FolderPath string `json:"folderId"` Name string `json:"name"` } type deleteProjectFolderRequest struct { FolderPath string `json:"folderId"` } // Keep the existing JSON contract for the frontend, but use clearer path-vs-stable-ID // names internally so the move flow is easier to reason about. type moveProjectFolderRequest struct { FolderPath string `json:"folderId"` FolderStableID string `json:"folderNodeId"` ParentFolderPath string `json:"parentFolderId"` ParentStableID string `json:"parentNodeId"` TargetIndex int `json:"targetIndex"` } type createProjectItemRequest struct { Name string `json:"name"` ParentFolderPath string `json:"parentFolderId"` ItemType string `json:"itemType"` } type deleteProjectItemRequest struct { ItemPath string `json:"itemId"` } type moveProjectItemRequest struct { ItemPath string `json:"itemId"` ItemStableID string `json:"itemNodeId"` ParentFolderPath string `json:"parentFolderId"` ParentStableID string `json:"parentNodeId"` TargetIndex int `json:"targetIndex"` } func decodeMoveProjectFolderRequest(w http.ResponseWriter, r *http.Request) (moveProjectFolderRequest, bool) { var payload moveProjectFolderRequest if !decodeJSONObjectBody(w, r, &payload) { return payload, false } return payload, true } func decodeDeleteProjectFolderRequest(r *http.Request) deleteProjectFolderRequest { return deleteProjectFolderRequest{ FolderPath: strings.TrimSpace(r.URL.Query().Get("folderId")), } } func decodeDeleteProjectItemRequest(r *http.Request) deleteProjectItemRequest { return deleteProjectItemRequest{ ItemPath: strings.TrimSpace(r.URL.Query().Get("itemId")), } } func decodeRenameProjectFolderRequest(w http.ResponseWriter, r *http.Request) (renameProjectFolderRequest, bool) { var payload renameProjectFolderRequest if !decodeJSONObjectBody(w, r, &payload) { return payload, false } return payload, true } func decodeProjectFolderRequest(w http.ResponseWriter, r *http.Request) (createProjectFolderRequest, bool) { var payload createProjectFolderRequest if !decodeJSONObjectBody(w, r, &payload) { return payload, false } return payload, true } func decodeProjectItemRequest(w http.ResponseWriter, r *http.Request) (createProjectItemRequest, bool) { var payload createProjectItemRequest if !decodeJSONObjectBody(w, r, &payload) { return payload, false } return payload, true } func decodeMoveProjectItemRequest(w http.ResponseWriter, r *http.Request) (moveProjectItemRequest, bool) { var payload moveProjectItemRequest if !decodeJSONObjectBody(w, r, &payload) { return payload, false } return payload, true } func decodeJSONObjectBody(w http.ResponseWriter, r *http.Request, target any) bool { decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { if errors.Is(err, io.EOF) { WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_json", "The request body is required and must be valid JSON.") return false } WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_json", "The request body must be valid JSON.") return false } if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_json", "The request body must contain a single JSON object.") return false } return true }