package commands import ( "bytes" "encoding/json" "fmt" "strings" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/client" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/config" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/output" ) // roleResponse matches the backend RoleResponse schema. type roleResponse struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` IsGlobal bool `json:"is_global"` Permissions []string `json:"permissions"` CreatedAt string `json:"created_at"` } // permissionResponse matches the backend PermissionResponse schema. type permissionResponse struct { ID int `json:"id"` Codename string `json:"codename"` Description string `json:"description"` } // RunRoleList implements `hf role list`. func RunRoleList(tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Get("/roles") if err != nil { output.Errorf("failed to list roles: %v", err) } 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) return } var roles []roleResponse if err := json.Unmarshal(data, &roles); err != nil { output.Errorf("cannot parse role list: %v", err) } headers := []string{"NAME", "DESCRIPTION", "GLOBAL", "PERMISSIONS"} var rows [][]string for _, r := range roles { global := "" if r.IsGlobal { global = "yes" } perms := strings.Join(r.Permissions, ", ") if len(perms) > 60 { perms = perms[:57] + "..." } rows = append(rows, []string{r.Name, r.Description, global, perms}) } output.PrintTable(headers, rows) } // RunRoleGet implements `hf role get `. func RunRoleGet(roleName, tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Get("/roles/" + roleName) if err != nil { output.Errorf("failed to get role: %v", err) } 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) return } var r roleResponse if err := json.Unmarshal(data, &r); err != nil { output.Errorf("cannot parse role: %v", err) } global := "no" if r.IsGlobal { global = "yes" } perms := "(none)" if len(r.Permissions) > 0 { perms = strings.Join(r.Permissions, ", ") } output.PrintKeyValue( "name", r.Name, "description", r.Description, "global", global, "permissions", perms, "created", r.CreatedAt, ) } // RunRoleCreate implements `hf role create`. func RunRoleCreate(name, desc string, global bool, tokenFlag string) { token := ResolveToken(tokenFlag) if name == "" { output.Error("usage: hf role create --name ") } payload := map[string]interface{}{ "name": name, } if desc != "" { payload["description"] = desc } if global { payload["is_global"] = true } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Post("/roles", bytes.NewReader(body)) if err != nil { output.Errorf("failed to create role: %v", err) } 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) return } fmt.Printf("role created: %s\n", name) } // RunRoleUpdate implements `hf role update `. func RunRoleUpdate(roleName string, args []string, tokenFlag string) { token := ResolveToken(tokenFlag) payload := make(map[string]interface{}) for i := 0; i < len(args); i++ { switch args[i] { case "--desc": if i+1 >= len(args) { output.Error("--desc requires a value") } i++ payload["description"] = args[i] default: output.Errorf("unknown flag: %s", args[i]) } } if len(payload) == 0 { output.Error("nothing to update — provide at least one flag") } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Patch("/roles/"+roleName, bytes.NewReader(body)) if err != nil { output.Errorf("failed to update role: %v", err) } fmt.Printf("role updated: %s\n", roleName) } // RunRoleDelete implements `hf role delete `. func RunRoleDelete(roleName, tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Delete("/roles/" + roleName) if err != nil { output.Errorf("failed to delete role: %v", err) } fmt.Printf("role deleted: %s\n", roleName) } // RunRoleSetPermissions implements `hf role set-permissions --permission [...]`. func RunRoleSetPermissions(roleName string, permissions []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(permissions) == 0 { output.Error("usage: hf role set-permissions --permission [--permission ...]") } payload := map[string]interface{}{ "permissions": permissions, } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Put("/roles/"+roleName+"/permissions", bytes.NewReader(body)) if err != nil { output.Errorf("failed to set permissions: %v", err) } fmt.Printf("permissions set for role %s: %s\n", roleName, strings.Join(permissions, ", ")) } // RunRoleAddPermissions implements `hf role add-permissions --permission [...]`. func RunRoleAddPermissions(roleName string, permissions []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(permissions) == 0 { output.Error("usage: hf role add-permissions --permission [--permission ...]") } payload := map[string]interface{}{ "permissions": permissions, } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Post("/roles/"+roleName+"/permissions", bytes.NewReader(body)) if err != nil { output.Errorf("failed to add permissions: %v", err) } fmt.Printf("permissions added to role %s: %s\n", roleName, strings.Join(permissions, ", ")) } // RunRoleRemovePermissions implements `hf role remove-permissions --permission [...]`. func RunRoleRemovePermissions(roleName string, permissions []string, tokenFlag string) { token := ResolveToken(tokenFlag) if len(permissions) == 0 { output.Error("usage: hf role remove-permissions --permission [--permission ...]") } payload := map[string]interface{}{ "permissions": permissions, } body, err := json.Marshal(payload) if err != nil { output.Errorf("cannot marshal payload: %v", err) } cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) _, err = c.Do("DELETE", "/roles/"+roleName+"/permissions", bytes.NewReader(body)) if err != nil { output.Errorf("failed to remove permissions: %v", err) } fmt.Printf("permissions removed from role %s: %s\n", roleName, strings.Join(permissions, ", ")) } // RunPermissionList implements `hf permission list`. func RunPermissionList(tokenFlag string) { token := ResolveToken(tokenFlag) cfg, err := config.Load() if err != nil { output.Errorf("config error: %v", err) } c := client.New(cfg.BaseURL, token) data, err := c.Get("/permissions") if err != nil { output.Errorf("failed to list permissions: %v", err) } 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) return } var perms []permissionResponse if err := json.Unmarshal(data, &perms); err != nil { output.Errorf("cannot parse permission list: %v", err) } headers := []string{"CODENAME", "DESCRIPTION"} var rows [][]string for _, p := range perms { rows = append(rows, []string{p.Codename, p.Description}) } output.PrintTable(headers, rows) }