From 5ac90408f331dbf6f9c6679cc9095b7c627c3dfc Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 20:16:59 +0000 Subject: [PATCH] feat: support discord id account updates --- cmd/hf/main.go | 56 ++++++++++++++++++++++++++++++++-- internal/commands/user.go | 63 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/cmd/hf/main.go b/cmd/hf/main.go index 7610a8f..cbea4e5 100644 --- a/cmd/hf/main.go +++ b/cmd/hf/main.go @@ -32,6 +32,28 @@ func main() { handleLeafOrRun("health", args[1:], commands.RunHealth) case "config": handleConfig(args[1:]) + case "update-discord-id": + tokenFlag := "" + var filtered []string + for i := 1; i < len(args); i++ { + switch args[i] { + case "--token": + if i+1 < len(args) { + i++ + tokenFlag = args[i] + } + default: + filtered = append(filtered, args[i]) + } + } + if len(filtered) < 1 { + output.Error("usage: hf update-discord-id [discord-id]") + } + discordID := "" + if len(filtered) >= 2 { + discordID = filtered[1] + } + commands.RunUserUpdateDiscordID(filtered[0], discordID, tokenFlag) default: if group, ok := findGroup(args[0]); ok { handleGroup(group, args[1:]) @@ -204,6 +226,31 @@ func handleGroup(group help.Group, args []string) { return } + if len(args) > 0 && args[0] == "update-discord-id" { + tokenFlag := "" + var filtered []string + for i := 1; i < len(args); i++ { + switch args[i] { + case "--token": + if i+1 < len(args) { + i++ + tokenFlag = args[i] + } + default: + filtered = append(filtered, args[i]) + } + } + if len(filtered) < 1 { + output.Error("usage: hf update-discord-id [discord-id]") + } + discordID := "" + if len(filtered) >= 2 { + discordID = filtered[1] + } + commands.RunUserUpdateDiscordID(filtered[0], discordID, tokenFlag) + return + } + output.Errorf("hf %s %s is recognized but not implemented yet", group.Name, sub.Name) } @@ -238,7 +285,7 @@ func handleUserCommand(subCmd string, args []string) { } commands.RunUserGet(filtered[0], tokenFlag) case "create": - username, password, email, fullName := "", "", "", "" + username, password, email, fullName, discordUserID := "", "", "", "", "" for i := 0; i < len(filtered); i++ { switch filtered[i] { case "--user": @@ -261,6 +308,11 @@ func handleUserCommand(subCmd string, args []string) { i++ fullName = filtered[i] } + case "--discord-user-id": + if i+1 < len(filtered) { + i++ + discordUserID = filtered[i] + } default: output.Errorf("unknown flag: %s", filtered[i]) } @@ -268,7 +320,7 @@ func handleUserCommand(subCmd string, args []string) { if username == "" { output.Error("usage: hf user create --user ") } - commands.RunUserCreate(username, password, email, fullName, accMgrTokenFlag) + commands.RunUserCreate(username, password, email, fullName, discordUserID, accMgrTokenFlag) case "update": if len(filtered) < 1 { output.Error("usage: hf user update [--email ...] [--full-name ...] [--pass ...] [--active ...]") diff --git a/internal/commands/user.go b/internal/commands/user.go index bdbd133..8381a5d 100644 --- a/internal/commands/user.go +++ b/internal/commands/user.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "fmt" + "os" + "os/exec" "strings" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/client" @@ -23,6 +25,7 @@ type userResponse struct { IsAdmin bool `json:"is_admin"` RoleID *int `json:"role_id"` RoleName *string `json:"role_name"` + DiscordUserID *string `json:"discord_user_id"` CreatedAt string `json:"created_at"` } @@ -137,10 +140,41 @@ type userCreatePayload struct { Email string `json:"email"` FullName *string `json:"full_name,omitempty"` Password *string `json:"password,omitempty"` + DiscordUserID *string `json:"discord_user_id,omitempty"` +} + +func maybeResolveDiscordUserID(explicit string, requireEnv bool) (string, bool, error) { + if strings.TrimSpace(explicit) != "" { + return strings.TrimSpace(explicit), true, nil + } + agentID := strings.TrimSpace(os.Getenv("AGENT_ID")) + agentVerify := strings.TrimSpace(os.Getenv("AGENT_VERIFY")) + if agentID == "" || agentVerify == "" { + if requireEnv { + return "", false, fmt.Errorf("discord id not provided and AGENT_ID/AGENT_VERIFY are missing") + } + return "", false, nil + } + cmd := exec.Command("ego-mgr", "get", "discord-id") + out, err := cmd.Output() + if err != nil { + if requireEnv { + return "", false, fmt.Errorf("failed to resolve discord id from ego-mgr: %w", err) + } + return "", false, nil + } + value := strings.TrimSpace(string(out)) + if value == "" { + if requireEnv { + return "", false, fmt.Errorf("ego-mgr returned empty discord id") + } + return "", false, nil + } + return value, true, nil } // RunUserCreate implements `hf user create`. -func RunUserCreate(username, password, email, fullName, accMgrTokenFlag string) { +func RunUserCreate(username, password, email, fullName, discordUserID, accMgrTokenFlag string) { // Resolve account-manager token var accMgrToken string if mode.IsPaddedCell() { @@ -181,6 +215,11 @@ func RunUserCreate(username, password, email, fullName, accMgrTokenFlag string) Email: email, Password: &password, } + if resolvedDiscordID, ok, err := maybeResolveDiscordUserID(discordUserID, false); err != nil { + output.Errorf("failed to resolve discord user id: %v", err) + } else if ok { + payload.DiscordUserID = &resolvedDiscordID + } if fullName != "" { payload.FullName = &fullName } @@ -216,6 +255,28 @@ func RunUserCreate(username, password, email, fullName, accMgrTokenFlag string) fmt.Printf("user created: %s\n", u.Username) } +// RunUserUpdateDiscordID updates a user's discord_user_id field. +func RunUserUpdateDiscordID(username, discordUserID, tokenFlag string) { + token := ResolveToken(tokenFlag) + resolvedDiscordID, _, err := maybeResolveDiscordUserID(discordUserID, true) + if err != nil { + output.Errorf("failed to resolve discord user id: %v", err) + } + body, err := json.Marshal(map[string]interface{}{"discord_user_id": resolvedDiscordID}) + 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) + if _, err := c.Patch("/users/"+username, bytes.NewReader(body)); err != nil { + output.Errorf("failed to update discord id: %v", err) + } + fmt.Printf("discord id updated: %s\n", username) +} + // RunUserUpdate implements `hf user update `. func RunUserUpdate(username string, args []string, tokenFlag string) { token := ResolveToken(tokenFlag)