Compare commits
3 Commits
0280f2c327
...
e2177521e0
| Author | SHA1 | Date | |
|---|---|---|---|
| e2177521e0 | |||
| 84150df4d5 | |||
| b287b1ff17 |
@@ -93,7 +93,7 @@ func (c *Client) Delete(path string) ([]byte, error) {
|
||||
|
||||
// Health checks the API health endpoint and returns the response.
|
||||
func (c *Client) Health() (map[string]interface{}, error) {
|
||||
data, err := c.Get("/api/health/")
|
||||
data, err := c.Get("/health")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
type essentialResponse struct {
|
||||
ID int `json:"id"`
|
||||
EssentialCode string `json:"essential_code"`
|
||||
ProposalID int `json:"proposal_id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
@@ -49,7 +48,7 @@ func RunEssentialList(args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Get("/proposes/" + proposalCode + "/essentials")
|
||||
data, err := c.Get(proposalPath(c, proposalCode) + "/essentials")
|
||||
if err != nil {
|
||||
output.Errorf("failed to list essentials: %v", err)
|
||||
}
|
||||
@@ -146,7 +145,7 @@ func RunEssentialCreate(args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Post("/proposes/"+proposalCode+"/essentials", bytes.NewReader(body))
|
||||
data, err := c.Post(proposalPath(c, proposalCode)+"/essentials", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to create essential: %v", err)
|
||||
}
|
||||
@@ -229,7 +228,7 @@ func RunEssentialUpdate(essentialCode string, args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Patch("/proposes/"+proposalCode+"/essentials/"+essentialCode, bytes.NewReader(body))
|
||||
_, err = c.Patch(proposalPath(c, proposalCode)+"/essentials/"+essentialCode, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to update essential: %v", err)
|
||||
}
|
||||
@@ -266,7 +265,7 @@ func RunEssentialDeleteFull(essentialCode string, args []string, tokenFlag strin
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Delete("/proposes/" + proposalCode + "/essentials/" + essentialCode)
|
||||
_, err = c.Delete(proposalPath(c, proposalCode) + "/essentials/" + essentialCode)
|
||||
if err != nil {
|
||||
output.Errorf("failed to delete essential: %v", err)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func RunMeetingList(args []string, tokenFlag string) {
|
||||
output.Error("--project requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "project", args[i])
|
||||
query = appendQuery(query, "project_code", args[i])
|
||||
case "--status":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--status requires a value")
|
||||
|
||||
@@ -44,7 +44,7 @@ func RunMilestoneList(args []string, tokenFlag string) {
|
||||
output.Error("--project requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "project", args[i])
|
||||
query = appendQuery(query, "project_code", args[i])
|
||||
case "--status":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--status requires a value")
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
|
||||
// monitorOverviewResponse matches the backend monitor overview schema.
|
||||
type monitorOverviewResponse struct {
|
||||
TotalServers int `json:"total_servers"`
|
||||
OnlineServers int `json:"online_servers"`
|
||||
Tasks interface{} `json:"tasks"`
|
||||
Providers interface{} `json:"providers"`
|
||||
Servers []monitorServerResponse `json:"servers"`
|
||||
GeneratedAt string `json:"generated_at"`
|
||||
}
|
||||
|
||||
// monitorServerResponse matches the backend monitor server schema.
|
||||
@@ -28,8 +30,32 @@ type monitorServerResponse struct {
|
||||
|
||||
// monitorAPIKeyResponse matches the backend monitor API key schema.
|
||||
type monitorAPIKeyResponse struct {
|
||||
Identifier string `json:"identifier"`
|
||||
APIKey string `json:"api_key"`
|
||||
ServerID int `json:"server_id"`
|
||||
APIKey string `json:"api_key"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func monitorServerList(c *client.Client) []monitorServerResponse {
|
||||
data, err := c.Get("/monitor/admin/servers")
|
||||
if err != nil {
|
||||
output.Errorf("failed to list monitor servers: %v", err)
|
||||
}
|
||||
var servers []monitorServerResponse
|
||||
if err := json.Unmarshal(data, &servers); err != nil {
|
||||
output.Errorf("cannot parse server list: %v", err)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func resolveMonitorServerID(c *client.Client, identifier string) int {
|
||||
servers := monitorServerList(c)
|
||||
for _, s := range servers {
|
||||
if s.Identifier == identifier {
|
||||
return s.ID
|
||||
}
|
||||
}
|
||||
output.Errorf("monitor server not found: %s", identifier)
|
||||
return 0
|
||||
}
|
||||
|
||||
// RunMonitorOverview implements `hf monitor overview`.
|
||||
@@ -40,7 +66,7 @@ func RunMonitorOverview(tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Get("/monitor/overview")
|
||||
data, err := c.Get("/monitor/public/overview")
|
||||
if err != nil {
|
||||
output.Errorf("failed to get monitor overview: %v", err)
|
||||
}
|
||||
@@ -59,9 +85,16 @@ func RunMonitorOverview(tokenFlag string) {
|
||||
output.Errorf("cannot parse monitor overview: %v", err)
|
||||
}
|
||||
|
||||
online := 0
|
||||
for _, s := range o.Servers {
|
||||
if s.Status == "online" {
|
||||
online++
|
||||
}
|
||||
}
|
||||
output.PrintKeyValue(
|
||||
"total-servers", fmt.Sprintf("%d", o.TotalServers),
|
||||
"online-servers", fmt.Sprintf("%d", o.OnlineServers),
|
||||
"total-servers", fmt.Sprintf("%d", len(o.Servers)),
|
||||
"online-servers", fmt.Sprintf("%d", online),
|
||||
"generated-at", o.GeneratedAt,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,7 +106,7 @@ func RunMonitorServerList(tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Get("/monitor/servers")
|
||||
data, err := c.Get("/monitor/admin/servers")
|
||||
if err != nil {
|
||||
output.Errorf("failed to list monitor servers: %v", err)
|
||||
}
|
||||
@@ -116,39 +149,37 @@ func RunMonitorServerGet(identifier, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Get("/monitor/servers/" + identifier)
|
||||
if err != nil {
|
||||
output.Errorf("failed to get server: %v", err)
|
||||
servers := monitorServerList(c)
|
||||
var found *monitorServerResponse
|
||||
for i := range servers {
|
||||
if servers[i].Identifier == identifier {
|
||||
found = &servers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
output.Errorf("failed to get server: not found: %s", identifier)
|
||||
}
|
||||
|
||||
if output.JSONMode {
|
||||
var raw json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
output.Errorf("invalid JSON response: %v", err)
|
||||
}
|
||||
output.PrintJSON(raw)
|
||||
output.PrintJSON(found)
|
||||
return
|
||||
}
|
||||
|
||||
var s monitorServerResponse
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
output.Errorf("cannot parse server: %v", err)
|
||||
}
|
||||
|
||||
name := ""
|
||||
if s.DisplayName != nil {
|
||||
name = *s.DisplayName
|
||||
if found.DisplayName != nil {
|
||||
name = *found.DisplayName
|
||||
}
|
||||
lastSeen := ""
|
||||
if s.LastSeen != nil {
|
||||
lastSeen = *s.LastSeen
|
||||
if found.LastSeen != nil {
|
||||
lastSeen = *found.LastSeen
|
||||
}
|
||||
output.PrintKeyValue(
|
||||
"identifier", s.Identifier,
|
||||
"identifier", found.Identifier,
|
||||
"name", name,
|
||||
"status", s.Status,
|
||||
"status", found.Status,
|
||||
"last-seen", lastSeen,
|
||||
"created", s.CreatedAt,
|
||||
"created", found.CreatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -197,7 +228,7 @@ func RunMonitorServerCreate(args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Post("/monitor/servers", bytes.NewReader(body))
|
||||
data, err := c.Post("/monitor/admin/servers", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to create server: %v", err)
|
||||
}
|
||||
@@ -223,7 +254,8 @@ func RunMonitorServerDelete(identifier, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Delete("/monitor/servers/" + identifier)
|
||||
serverID := resolveMonitorServerID(c, identifier)
|
||||
_, err = c.Delete(fmt.Sprintf("/monitor/admin/servers/%d", serverID))
|
||||
if err != nil {
|
||||
output.Errorf("failed to delete server: %v", err)
|
||||
}
|
||||
@@ -238,7 +270,8 @@ func RunMonitorAPIKeyGenerate(identifier, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Post("/monitor/servers/"+identifier+"/api-key", nil)
|
||||
serverID := resolveMonitorServerID(c, identifier)
|
||||
data, err := c.Post(fmt.Sprintf("/monitor/admin/servers/%d/api-key", serverID), nil)
|
||||
if err != nil {
|
||||
output.Errorf("failed to generate API key: %v", err)
|
||||
}
|
||||
@@ -258,8 +291,9 @@ func RunMonitorAPIKeyGenerate(identifier, tokenFlag string) {
|
||||
return
|
||||
}
|
||||
output.PrintKeyValue(
|
||||
"identifier", k.Identifier,
|
||||
"server-id", fmt.Sprintf("%d", k.ServerID),
|
||||
"api-key", k.APIKey,
|
||||
"message", k.Message,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -271,7 +305,8 @@ func RunMonitorAPIKeyRevoke(identifier, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Delete("/monitor/servers/" + identifier + "/api-key")
|
||||
serverID := resolveMonitorServerID(c, identifier)
|
||||
_, err = c.Delete(fmt.Sprintf("/monitor/admin/servers/%d/api-key", serverID))
|
||||
if err != nil {
|
||||
output.Errorf("failed to revoke API key: %v", err)
|
||||
}
|
||||
|
||||
@@ -170,20 +170,29 @@ func TestEssentialDelete_MissingProposal(t *testing.T) {
|
||||
|
||||
func TestEssentialList_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/proposes/PRJ-001/essentials" {
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/projects":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{map[string]interface{}{"id": 1, "project_code": "PROJ-001"}})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"code": "PRJ-001", "project_code": "PROJ-001"})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001/essentials":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"essential_code": "ESS-001",
|
||||
"proposal_id": 1,
|
||||
"type": "feature",
|
||||
"title": "Add login",
|
||||
"created_at": "2026-03-01",
|
||||
},
|
||||
})
|
||||
default:
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"essential_code": "ESS-001",
|
||||
"proposal_id": 1,
|
||||
"type": "feature",
|
||||
"title": "Add login",
|
||||
"created_at": "2026-03-01",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -204,16 +213,25 @@ func TestEssentialList_JSONOutput(t *testing.T) {
|
||||
|
||||
func TestEssentialCreate_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("expected POST; got: %s", r.Method)
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/projects":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{map[string]interface{}{"id": 1, "project_code": "PROJ-001"}})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"code": "PRJ-001", "project_code": "PROJ-001"})
|
||||
case r.Method == "POST" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001/essentials":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": 1,
|
||||
"essential_code": "ESS-001",
|
||||
"title": "Add login",
|
||||
"type": "feature",
|
||||
})
|
||||
default:
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": 1,
|
||||
"essential_code": "ESS-001",
|
||||
"title": "Add login",
|
||||
"type": "feature",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -234,11 +252,20 @@ func TestEssentialCreate_Success(t *testing.T) {
|
||||
|
||||
func TestEssentialUpdate_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "PATCH" {
|
||||
t.Errorf("expected PATCH; got: %s", r.Method)
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/projects":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{map[string]interface{}{"id": 1, "project_code": "PROJ-001"}})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"code": "PRJ-001", "project_code": "PROJ-001"})
|
||||
case r.Method == "PATCH" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001/essentials/ESS-001":
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
default:
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -259,11 +286,20 @@ func TestEssentialUpdate_Success(t *testing.T) {
|
||||
|
||||
func TestEssentialDelete_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "DELETE" {
|
||||
t.Errorf("expected DELETE; got: %s", r.Method)
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/projects":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{map[string]interface{}{"id": 1, "project_code": "PROJ-001"}})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"code": "PRJ-001", "project_code": "PROJ-001"})
|
||||
case r.Method == "DELETE" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001/essentials/ESS-001":
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
default:
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -306,20 +342,29 @@ func TestProposalAccept_MissingMilestone(t *testing.T) {
|
||||
|
||||
func TestProposalAccept_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/proposes/PRJ-001/accept" {
|
||||
switch {
|
||||
case r.Method == "GET" && r.URL.Path == "/projects":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{map[string]interface{}{"id": 1, "project_code": "PROJ-001"}})
|
||||
case r.Method == "GET" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001":
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"code": "PRJ-001", "project_code": "PROJ-001"})
|
||||
case r.Method == "POST" && r.URL.Path == "/projects/PROJ-001/proposals/PRJ-001/accept":
|
||||
var body map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
if body["milestone_code"] != "MS-001" {
|
||||
t.Errorf("expected milestone_code=MS-001; got: %v", body["milestone_code"])
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": "PRJ-001",
|
||||
"status": "Accepted",
|
||||
"tasks": []interface{}{},
|
||||
})
|
||||
default:
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
var body map[string]interface{}
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
if body["milestone_code"] != "MS-001" {
|
||||
t.Errorf("expected milestone_code=MS-001; got: %v", body["milestone_code"])
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": "PRJ-001",
|
||||
"status": "Accepted",
|
||||
"tasks": []interface{}{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -421,7 +466,7 @@ func TestTaskCreate_StoryTypeOnlyRestricted(t *testing.T) {
|
||||
|
||||
func TestProposalList_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/proposes" {
|
||||
if r.Method != "GET" || r.URL.Path != "/projects/PROJ-001/proposals" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
@@ -444,7 +489,7 @@ func TestProposalList_Success(t *testing.T) {
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLIProposal(t, tmpDir, cliPath, "proposal", "list", "--token", "fake")
|
||||
out, err := runCLIProposal(t, tmpDir, cliPath, "proposal", "list", "--token", "fake", "--project", "PROJ-001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
@@ -455,6 +500,9 @@ func TestProposalList_Success(t *testing.T) {
|
||||
|
||||
func TestProposalList_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/projects/PROJ-001/proposals" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode([]interface{}{
|
||||
map[string]interface{}{
|
||||
@@ -472,7 +520,7 @@ func TestProposalList_JSONOutput(t *testing.T) {
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLIProposal(t, tmpDir, cliPath, "--json", "proposal", "list", "--token", "fake")
|
||||
out, err := runCLIProposal(t, tmpDir, cliPath, "--json", "proposal", "list", "--token", "fake", "--project", "PROJ-001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/client"
|
||||
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/config"
|
||||
@@ -23,11 +24,44 @@ type proposeResponse struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type projectLookup struct {
|
||||
ID int `json:"id"`
|
||||
ProjectCode string `json:"project_code"`
|
||||
}
|
||||
|
||||
func resolveProposalProject(c *client.Client, proposalCode string) string {
|
||||
data, err := c.Get("/projects")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var projects []projectLookup
|
||||
if err := json.Unmarshal(data, &projects); err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, p := range projects {
|
||||
if p.ProjectCode == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := c.Get("/projects/" + p.ProjectCode + "/proposals/" + proposalCode); err == nil {
|
||||
return p.ProjectCode
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func proposalPath(c *client.Client, proposalCode string) string {
|
||||
if project := resolveProposalProject(c, proposalCode); project != "" {
|
||||
return "/projects/" + project + "/proposals/" + proposalCode
|
||||
}
|
||||
return "/proposes/" + proposalCode
|
||||
}
|
||||
|
||||
// RunProposeList implements `hf propose list --project <project-code>`.
|
||||
func RunProposeList(args []string, tokenFlag string) {
|
||||
token := ResolveToken(tokenFlag)
|
||||
|
||||
query := ""
|
||||
project := ""
|
||||
query := url.Values{}
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--project":
|
||||
@@ -35,32 +69,39 @@ func RunProposeList(args []string, tokenFlag string) {
|
||||
output.Error("--project requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "project", args[i])
|
||||
project = args[i]
|
||||
case "--status":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--status requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "status", args[i])
|
||||
query.Set("status", args[i])
|
||||
case "--order-by":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--order-by requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "order_by", args[i])
|
||||
query.Set("order_by", args[i])
|
||||
default:
|
||||
output.Errorf("unknown flag: %s", args[i])
|
||||
}
|
||||
}
|
||||
legacyPath := false
|
||||
if project == "" {
|
||||
legacyPath = true
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
path := "/proposes"
|
||||
if query != "" {
|
||||
path += "?" + query
|
||||
path := "/projects/" + project + "/proposals"
|
||||
if legacyPath {
|
||||
path = "/proposes"
|
||||
}
|
||||
if encoded := query.Encode(); encoded != "" {
|
||||
path += "?" + encoded
|
||||
}
|
||||
data, err := c.Get(path)
|
||||
if err != nil {
|
||||
@@ -105,7 +146,7 @@ func RunProposeGet(proposeCode, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Get("/proposes/" + proposeCode)
|
||||
data, err := c.Get(proposalPath(c, proposeCode))
|
||||
if err != nil {
|
||||
output.Errorf("failed to get proposal: %v", err)
|
||||
}
|
||||
@@ -178,9 +219,8 @@ func RunProposeCreate(args []string, tokenFlag string) {
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"project_code": project,
|
||||
"title": title,
|
||||
"description": desc,
|
||||
"title": title,
|
||||
"description": desc,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
@@ -193,7 +233,7 @@ func RunProposeCreate(args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Post("/proposes", bytes.NewReader(body))
|
||||
data, err := c.Post("/projects/"+project+"/proposals", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to create proposal: %v", err)
|
||||
}
|
||||
@@ -253,7 +293,7 @@ func RunProposeUpdate(proposeCode string, args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Patch("/proposes/"+proposeCode, bytes.NewReader(body))
|
||||
_, err = c.Patch(proposalPath(c, proposeCode), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to update proposal: %v", err)
|
||||
}
|
||||
@@ -311,7 +351,7 @@ func RunProposeAccept(proposeCode string, args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
data, err := c.Post("/proposes/"+proposeCode+"/accept", bytes.NewReader(body))
|
||||
data, err := c.Post(proposalPath(c, proposeCode)+"/accept", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
output.Errorf("failed to accept proposal: %v", err)
|
||||
}
|
||||
@@ -332,7 +372,7 @@ func RunProposeAccept(proposeCode string, args []string, tokenFlag string) {
|
||||
if err := json.Unmarshal(data, &resp); err == nil && len(resp.GeneratedTasks) > 0 {
|
||||
fmt.Printf("\nGenerated %d story task(s):\n", len(resp.GeneratedTasks))
|
||||
for _, gt := range resp.GeneratedTasks {
|
||||
code := ""
|
||||
code := "(no task_code)"
|
||||
if gt.TaskCode != nil {
|
||||
code = *gt.TaskCode
|
||||
}
|
||||
@@ -380,7 +420,7 @@ func RunProposeReject(proposeCode string, args []string, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Post("/proposes/"+proposeCode+"/reject", body)
|
||||
_, err = c.Post(proposalPath(c, proposeCode)+"/reject", body)
|
||||
if err != nil {
|
||||
output.Errorf("failed to reject proposal: %v", err)
|
||||
}
|
||||
@@ -397,7 +437,7 @@ func RunProposeReopen(proposeCode, tokenFlag string) {
|
||||
output.Errorf("config error: %v", err)
|
||||
}
|
||||
c := client.New(cfg.BaseURL, token)
|
||||
_, err = c.Post("/proposes/"+proposeCode+"/reopen", nil)
|
||||
_, err = c.Post(proposalPath(c, proposeCode)+"/reopen", nil)
|
||||
if err != nil {
|
||||
output.Errorf("failed to reopen proposal: %v", err)
|
||||
}
|
||||
|
||||
@@ -38,13 +38,13 @@ func RunTaskList(args []string, tokenFlag string) {
|
||||
output.Error("--project requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "project", args[i])
|
||||
query = appendQuery(query, "project_code", args[i])
|
||||
case "--milestone":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--milestone requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "milestone", args[i])
|
||||
query = appendQuery(query, "milestone_code", args[i])
|
||||
case "--status":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--status requires a value")
|
||||
@@ -426,7 +426,7 @@ func RunTaskSearch(args []string, tokenFlag string) {
|
||||
output.Error("--project requires a value")
|
||||
}
|
||||
i++
|
||||
query = appendQuery(query, "project", args[i])
|
||||
query = appendQuery(query, "project_code", args[i])
|
||||
case "--status":
|
||||
if i+1 >= len(args) {
|
||||
output.Error("--status requires a value")
|
||||
|
||||
Reference in New Issue
Block a user