From 84150df4d5908f05d69638918b3a6e8b9caa7c90 Mon Sep 17 00:00:00 2001 From: orion Date: Fri, 3 Apr 2026 13:58:15 +0000 Subject: [PATCH] fix: align cli routes with backend routers --- internal/commands/essential.go | 12 ++-- internal/commands/monitor.go | 101 ++++++++++++++++++++++----------- internal/commands/propose.go | 64 +++++++++++++++------ 3 files changed, 124 insertions(+), 53 deletions(-) diff --git a/internal/commands/essential.go b/internal/commands/essential.go index a7edc85..f1f6c27 100644 --- a/internal/commands/essential.go +++ b/internal/commands/essential.go @@ -49,7 +49,8 @@ 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") + project := resolveProposalProject(c, proposalCode) + data, err := c.Get("/projects/" + project + "/proposals/" + proposalCode + "/essentials") if err != nil { output.Errorf("failed to list essentials: %v", err) } @@ -146,7 +147,8 @@ 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)) + project := resolveProposalProject(c, proposalCode) + data, err := c.Post("/projects/"+project+"/proposals/"+proposalCode+"/essentials", bytes.NewReader(body)) if err != nil { output.Errorf("failed to create essential: %v", err) } @@ -229,7 +231,8 @@ 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)) + project := resolveProposalProject(c, proposalCode) + _, err = c.Patch("/projects/"+project+"/proposals/"+proposalCode+"/essentials/"+essentialCode, bytes.NewReader(body)) if err != nil { output.Errorf("failed to update essential: %v", err) } @@ -266,7 +269,8 @@ 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) + project := resolveProposalProject(c, proposalCode) + _, err = c.Delete("/projects/" + project + "/proposals/" + proposalCode + "/essentials/" + essentialCode) if err != nil { output.Errorf("failed to delete essential: %v", err) } diff --git a/internal/commands/monitor.go b/internal/commands/monitor.go index ffe1a40..5a7b38f 100644 --- a/internal/commands/monitor.go +++ b/internal/commands/monitor.go @@ -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) } diff --git a/internal/commands/propose.go b/internal/commands/propose.go index 24bc065..d572aea 100644 --- a/internal/commands/propose.go +++ b/internal/commands/propose.go @@ -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,35 @@ 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 { + output.Errorf("failed to list projects for proposal lookup: %v", err) + } + var projects []projectLookup + if err := json.Unmarshal(data, &projects); err != nil { + output.Errorf("cannot parse project list for proposal lookup: %v", err) + } + for _, p := range projects { + if _, err := c.Get("/projects/" + p.ProjectCode + "/proposals/" + proposalCode); err == nil { + return p.ProjectCode + } + } + output.Errorf("proposal not found: %s", proposalCode) + return "" +} + // RunProposeList implements `hf propose list --project `. 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 +60,35 @@ 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]) } } + if project == "" { + output.Error("usage: hf propose list --project [--status ] [--order-by ]") + } 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 encoded := query.Encode(); encoded != "" { + path += "?" + encoded } data, err := c.Get(path) if err != nil { @@ -105,7 +133,8 @@ func RunProposeGet(proposeCode, tokenFlag string) { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) - data, err := c.Get("/proposes/" + proposeCode) + project := resolveProposalProject(c, proposeCode) + data, err := c.Get("/projects/" + project + "/proposals/" + proposeCode) if err != nil { output.Errorf("failed to get proposal: %v", err) } @@ -178,9 +207,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 +221,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 +281,8 @@ 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)) + project := resolveProposalProject(c, proposeCode) + _, err = c.Patch("/projects/"+project+"/proposals/"+proposeCode, bytes.NewReader(body)) if err != nil { output.Errorf("failed to update proposal: %v", err) } @@ -311,7 +340,8 @@ 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)) + project := resolveProposalProject(c, proposeCode) + data, err := c.Post("/projects/"+project+"/proposals/"+proposeCode+"/accept", bytes.NewReader(body)) if err != nil { output.Errorf("failed to accept proposal: %v", err) } @@ -380,7 +410,8 @@ 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) + project := resolveProposalProject(c, proposeCode) + _, err = c.Post("/projects/"+project+"/proposals/"+proposeCode+"/reject", body) if err != nil { output.Errorf("failed to reject proposal: %v", err) } @@ -397,7 +428,8 @@ func RunProposeReopen(proposeCode, tokenFlag string) { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) - _, err = c.Post("/proposes/"+proposeCode+"/reopen", nil) + project := resolveProposalProject(c, proposeCode) + _, err = c.Post("/projects/"+project+"/proposals/"+proposeCode+"/reopen", nil) if err != nil { output.Errorf("failed to reopen proposal: %v", err) }