fix: CreateSection/CreateRequest/CreateRequestList now use lib.EntryWrite — no more raw DB calls in handlers

This commit is contained in:
James 2026-03-17 13:21:49 -04:00
parent eb37f682eb
commit ec0b60d44c
1 changed files with 58 additions and 87 deletions

View File

@ -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,
})
}