From ec0b60d44c15231cdc2e58047f9961824766bc01 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 17 Mar 2026 13:21:49 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20CreateSection/CreateRequest/CreateReques?= =?UTF-8?q?tList=20now=20use=20lib.EntryWrite=20=E2=80=94=20no=20more=20ra?= =?UTF-8?q?w=20DB=20calls=20in=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/handlers.go | 145 +++++++++++++++++++----------------------------- 1 file changed, 58 insertions(+), 87 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 2495559..4ac6ae0 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -2057,35 +2057,28 @@ func (h *Handlers) CreateSection(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || strings.TrimSpace(body.Name) == "" || body.ParentID == "" { ErrorResponse(w, http.StatusBadRequest, "invalid_body", "name and parent_id required"); return } - projectKey, err := lib.DeriveProjectKey(h.Cfg.MasterKey, projectID) - if err != nil { ErrorResponse(w, http.StatusInternalServerError, "internal", "key error"); return } - - var parentDepth int - h.DB.Conn.QueryRow(`SELECT depth FROM entries WHERE entry_id=?`, body.ParentID).Scan(&parentDepth) - var maxSort int - h.DB.Conn.QueryRow( - `SELECT COALESCE(MAX(sort_order),0) FROM entries WHERE parent_id=? AND deleted_at IS NULL`, body.ParentID, - ).Scan(&maxSort) - - now := time.Now().UnixMilli() - secID := uuid.New().String() + parent, err := lib.EntryByID(h.DB, h.Cfg, body.ParentID) + if err != nil || parent == nil { + ErrorResponse(w, http.StatusBadRequest, "invalid_parent", "Parent entry not found"); return + } + siblings, _ := lib.EntriesByParent(h.DB, h.Cfg, body.ParentID) name := strings.TrimSpace(body.Name) secData := lib.SectionData{Name: name} secDataJSON, _ := json.Marshal(secData) - sumPacked, _ := lib.Pack(projectKey, name) - dataPacked, _ := lib.Pack(projectKey, string(secDataJSON)) - - _, err = h.DB.Conn.Exec( - `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, sort_order, - search_key, search_key2, summary, data, stage, - assignee_id, return_to_id, origin_id, version, deleted_at, deleted_by, key_version, created_at, updated_at, created_by) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, - secID, projectID, body.ParentID, lib.TypeSection, parentDepth+1, maxSort+1000, - nil, nil, sumPacked, dataPacked, lib.StagePreDataroom, - "", "", "", 1, nil, nil, 1, now, now, actorID, - ) - if err != nil { ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create section"); return } - JSONResponse(w, http.StatusOK, map[string]any{"entry_id": secID, "name": name}) + entry := &lib.Entry{ + ProjectID: projectID, + ParentID: body.ParentID, + Type: lib.TypeSection, + Depth: parent.Depth + 1, + SortOrder: (len(siblings) + 1) * 1000, + SummaryText: name, + DataText: string(secDataJSON), + Stage: lib.StagePreDataroom, + } + if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil { + ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create section"); return + } + JSONResponse(w, http.StatusOK, map[string]any{"entry_id": entry.EntryID, "name": name}) } // CreateRequest creates a new blank request under a section or request_list. @@ -2102,36 +2095,28 @@ func (h *Handlers) CreateRequest(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&body); err != nil || strings.TrimSpace(body.Title) == "" || body.ParentID == "" { ErrorResponse(w, http.StatusBadRequest, "invalid_body", "title and parent_id required"); return } - projectKey, err := lib.DeriveProjectKey(h.Cfg.MasterKey, projectID) - if err != nil { ErrorResponse(w, http.StatusInternalServerError, "internal", "key error"); return } - - // Determine depth from parent - var parentDepth int - h.DB.Conn.QueryRow(`SELECT depth FROM entries WHERE entry_id=?`, body.ParentID).Scan(&parentDepth) - var maxSort int - h.DB.Conn.QueryRow( - `SELECT COALESCE(MAX(sort_order),0) FROM entries WHERE parent_id=? AND deleted_at IS NULL`, body.ParentID, - ).Scan(&maxSort) - - now := time.Now().UnixMilli() - reqID := uuid.New().String() + parent, err := lib.EntryByID(h.DB, h.Cfg, body.ParentID) + if err != nil || parent == nil { + ErrorResponse(w, http.StatusBadRequest, "invalid_parent", "Parent entry not found"); return + } + siblings, _ := lib.EntriesByParent(h.DB, h.Cfg, body.ParentID) title := strings.TrimSpace(body.Title) reqData := lib.RequestData{Title: title, Status: "open", Priority: "medium"} reqDataJSON, _ := json.Marshal(reqData) - sumPacked, _ := lib.Pack(projectKey, title) - dataPacked, _ := lib.Pack(projectKey, string(reqDataJSON)) - - _, err = h.DB.Conn.Exec( - `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, sort_order, - search_key, search_key2, summary, data, stage, - assignee_id, return_to_id, origin_id, version, deleted_at, deleted_by, key_version, created_at, updated_at, created_by) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, - reqID, projectID, body.ParentID, lib.TypeRequest, parentDepth+1, maxSort+100, - nil, nil, sumPacked, dataPacked, lib.StagePreDataroom, - "", "", "", 1, nil, nil, 1, now, now, actorID, - ) - if err != nil { ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create request"); return } - JSONResponse(w, http.StatusOK, map[string]any{"entry_id": reqID, "title": title}) + entry := &lib.Entry{ + ProjectID: projectID, + ParentID: body.ParentID, + Type: lib.TypeRequest, + Depth: parent.Depth + 1, + SortOrder: (len(siblings) + 1) * 100, + SummaryText: title, + DataText: string(reqDataJSON), + Stage: lib.StagePreDataroom, + } + if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil { + ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create request"); return + } + JSONResponse(w, http.StatusOK, map[string]any{"entry_id": entry.EntryID, "title": title}) } // CreateRequestList creates a new empty request list for a project. @@ -2152,48 +2137,34 @@ func (h *Handlers) CreateRequestList(w http.ResponseWriter, r *http.Request) { return } - projectKey, err := lib.DeriveProjectKey(h.Cfg.MasterKey, projectID) - if err != nil { - ErrorResponse(w, http.StatusInternalServerError, "internal", "Could not derive project key") - return - } - - now := time.Now().UnixMilli() listName := strings.TrimSpace(body.Name) - requestListID := uuid.New().String() - - // Compute sort_order: place after last existing list - var maxSort int - h.DB.Conn.QueryRow( - `SELECT COALESCE(MAX(sort_order),0) FROM entries WHERE project_id=? AND type='request_list' AND deleted_at IS NULL`, projectID, - ).Scan(&maxSort) - sortOrder := maxSort + 1000 - + // Sort after existing lists: use sibling count * 1000 + siblings, _ := lib.EntriesByParent(h.DB, h.Cfg, projectID) + listCount := 0 + for _, s := range siblings { + if s.Type == lib.TypeRequestList { + listCount++ + } + } rlData := lib.RequestListData{Name: listName} rlDataJSON, _ := json.Marshal(rlData) - rlSummaryPacked, _ := lib.Pack(projectKey, listName) - rlDataPacked, _ := lib.Pack(projectKey, string(rlDataJSON)) - - _, err = h.DB.Conn.Exec( - `INSERT INTO entries (entry_id, project_id, parent_id, type, depth, sort_order, - search_key, search_key2, summary, data, stage, - assignee_id, return_to_id, origin_id, - version, deleted_at, deleted_by, key_version, - created_at, updated_at, created_by) - VALUES (?,?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?, ?,?,?)`, - requestListID, projectID, projectID, lib.TypeRequestList, 1, sortOrder, - nil, nil, rlSummaryPacked, rlDataPacked, lib.StagePreDataroom, - "", "", "", - 1, nil, nil, 1, - now, now, actorID, - ) - if err != nil { + entry := &lib.Entry{ + ProjectID: projectID, + ParentID: projectID, + Type: lib.TypeRequestList, + Depth: 1, + SortOrder: (listCount + 1) * 1000, + SummaryText: listName, + DataText: string(rlDataJSON), + Stage: lib.StagePreDataroom, + } + if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil { ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create request list") return } JSONResponse(w, http.StatusOK, map[string]any{ - "entry_id": requestListID, + "entry_id": entry.EntryID, "name": listName, }) }