diff --git a/api/handlers.go b/api/handlers.go index 7d5dccc..79333a5 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -1933,6 +1933,63 @@ func (h *Handlers) ListRequests(w http.ResponseWriter, r *http.Request) { // ImportRequests handles POST /api/projects/{projectID}/requests/import // Accepts multipart form with CSV/XLSX file, mode (add/replace), section_filter, create_workstreams +// CreateRequestList creates a new empty request list for a project. +func (h *Handlers) CreateRequestList(w http.ResponseWriter, r *http.Request) { + actorID := UserIDFromContext(r.Context()) + projectID := chi.URLParam(r, "projectID") + + if err := lib.CheckAccessWrite(h.DB, actorID, projectID, ""); err != nil { + ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied") + return + } + + var body struct { + Name string `json:"name"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil || strings.TrimSpace(body.Name) == "" { + ErrorResponse(w, http.StatusBadRequest, "invalid_body", "name is required") + 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().UTC().Format(time.RFC3339) + listName := strings.TrimSpace(body.Name) + requestListID := uuid.New().String() + + 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, 0, + nil, nil, rlSummaryPacked, rlDataPacked, lib.StagePreDataroom, + "", "", "", + 1, nil, nil, 1, + now, now, actorID, + ) + if err != nil { + ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create request list") + return + } + + JSONResponse(w, http.StatusOK, map[string]any{ + "entry_id": requestListID, + "name": listName, + }) +} + func (h *Handlers) ImportRequests(w http.ResponseWriter, r *http.Request) { actorID := UserIDFromContext(r.Context()) projectID := chi.URLParam(r, "projectID") diff --git a/api/routes.go b/api/routes.go index 1e05c51..2a63df8 100644 --- a/api/routes.go +++ b/api/routes.go @@ -70,6 +70,7 @@ func NewRouter(db *lib.DB, cfg *lib.Config, store lib.ObjectStore, websiteFS fs. // Requests (list, tree, and import) r.Get("/projects/{projectID}/requests", h.ListRequests) r.Get("/projects/{projectID}/requests/tree", h.ListRequestTree) + r.Post("/projects/{projectID}/requests", h.CreateRequestList) r.Post("/projects/{projectID}/requests/import", h.ImportRequests) // Request detail diff --git a/dealspace b/dealspace index e4cb705..8bb2e9e 100755 Binary files a/dealspace and b/dealspace differ diff --git a/portal/templates/app/project.html b/portal/templates/app/project.html index 5bfecab..b3db51b 100644 --- a/portal/templates/app/project.html +++ b/portal/templates/app/project.html @@ -23,8 +23,9 @@