Fix: persist project folder hierarchy

This commit is contained in:
MangoPig
2026-06-22 14:14:13 +01:00
parent 2ff7fbd9e7
commit 618e3e84be
7 changed files with 780 additions and 70 deletions
@@ -0,0 +1,118 @@
package httpx
import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
bootstrapservice "moku-backend/internal/bootstrap"
"github.com/go-chi/chi/v5"
)
type createProjectFolderRequest struct {
Name string `json:"name"`
ParentFolderID string `json:"parentFolderId"`
}
func (routes apiRoutes) handleProjectFolders(w http.ResponseWriter, r *http.Request) {
projectID := strings.TrimSpace(chi.URLParam(r, "projectId"))
if projectID == "" {
WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_request", "Project ID is required.")
return
}
folders, err := routes.bootstrapService().GetProjectHierarchyFolders(r.Context(), projectID)
if err != nil {
routes.writeProjectFolderError(w, r, err)
return
}
WriteJSON(w, http.StatusOK, map[string]any{
"data": map[string]any{
"projectId": projectID,
"folders": folders,
},
"meta": map[string]any{
"resource": "project-folders",
},
})
}
func (routes apiRoutes) handleCreateProjectFolder(w http.ResponseWriter, r *http.Request) {
projectID := strings.TrimSpace(chi.URLParam(r, "projectId"))
if projectID == "" {
WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_request", "Project ID is required.")
return
}
payload, ok := decodeProjectFolderRequest(w, r)
if !ok {
return
}
payload.Name = strings.TrimSpace(payload.Name)
payload.ParentFolderID = strings.TrimSpace(payload.ParentFolderID)
if payload.Name == "" {
WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_request", "Folder name is required.")
return
}
result, err := routes.bootstrapService().CreateProjectFolder(r.Context(), bootstrapservice.CreateProjectFolderInput{
ProjectID: projectID,
ParentFolderID: payload.ParentFolderID,
Name: payload.Name,
})
if err != nil {
routes.writeProjectFolderError(w, r, err)
return
}
WriteJSON(w, http.StatusCreated, map[string]any{
"data": result,
"meta": map[string]any{
"resource": "project-folder-create",
"persisted": true,
},
})
}
func (routes apiRoutes) writeProjectFolderError(w http.ResponseWriter, r *http.Request, err error) {
switch {
case errors.Is(err, bootstrapservice.ErrProjectNotFound), errors.Is(err, bootstrapservice.ErrProjectFolderNotFound):
WriteError(w, http.StatusNotFound, RequestIDFromContext(r.Context()), "not_found", err.Error())
default:
routes.cfg.Logger.Error("persist project folder", "error", err, "path", r.URL.Path)
message := "Failed to persist project folder."
if routes.cfg.Config.IsDevelopment() {
message = message + " " + err.Error()
}
WriteError(w, http.StatusInternalServerError, RequestIDFromContext(r.Context()), "project_folder_persist_failed", message)
}
}
func decodeProjectFolderRequest(w http.ResponseWriter, r *http.Request) (createProjectFolderRequest, bool) {
var payload createProjectFolderRequest
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&payload); 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 payload, false
}
WriteError(w, http.StatusBadRequest, RequestIDFromContext(r.Context()), "invalid_json", "The request body must be valid JSON.")
return payload, 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 payload, false
}
return payload, true
}
+4
View File
@@ -33,6 +33,10 @@ func (routes apiRoutes) Register(router chi.Router) {
apiRouter.Get("/app-shell", routes.handleAppShellState)
apiRouter.Get("/organizations", routes.handleOrganizations)
apiRouter.Get("/workspaces", routes.handleWorkspaces)
apiRouter.Route("/projects/{projectId}", func(projectRouter chi.Router) {
projectRouter.Get("/folders", routes.handleProjectFolders)
projectRouter.Post("/folders", routes.handleCreateProjectFolder)
})
if routes.cfg.Config.IsDevelopment() {
apiRouter.Post("/dev/bootstrap/reset", routes.handleDevelopmentBootstrapReset)