Files
HarborForge.Cli/internal/commands/user.go

332 lines
8.2 KiB
Go

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/mode"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/passmgr"
)
// userResponse matches the backend UserResponse schema.
type userResponse struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FullName *string `json:"full_name"`
IsActive bool `json:"is_active"`
IsAdmin bool `json:"is_admin"`
RoleID *int `json:"role_id"`
RoleName *string `json:"role_name"`
CreatedAt string `json:"created_at"`
}
// RunUserList implements `hf user list`.
func RunUserList(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("/users")
if err != nil {
output.Errorf("failed to list users: %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 users []userResponse
if err := json.Unmarshal(data, &users); err != nil {
output.Errorf("cannot parse user list: %v", err)
}
headers := []string{"USERNAME", "EMAIL", "FULL NAME", "ROLE", "ACTIVE", "ADMIN"}
var rows [][]string
for _, u := range users {
fullName := ""
if u.FullName != nil {
fullName = *u.FullName
}
roleName := ""
if u.RoleName != nil {
roleName = *u.RoleName
}
active := "yes"
if !u.IsActive {
active = "no"
}
admin := ""
if u.IsAdmin {
admin = "yes"
}
rows = append(rows, []string{u.Username, u.Email, fullName, roleName, active, admin})
}
output.PrintTable(headers, rows)
}
// RunUserGet implements `hf user get <username>`.
func RunUserGet(username, 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("/users/" + username)
if err != nil {
output.Errorf("failed to get user: %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 u userResponse
if err := json.Unmarshal(data, &u); err != nil {
output.Errorf("cannot parse user: %v", err)
}
fullName := ""
if u.FullName != nil {
fullName = *u.FullName
}
roleName := ""
if u.RoleName != nil {
roleName = *u.RoleName
}
active := "yes"
if !u.IsActive {
active = "no"
}
admin := ""
if u.IsAdmin {
admin = "yes"
}
output.PrintKeyValue(
"username", u.Username,
"email", u.Email,
"full-name", fullName,
"role", roleName,
"active", active,
"admin", admin,
"created", u.CreatedAt,
)
}
// userCreatePayload is the JSON body for POST /users.
type userCreatePayload struct {
Username string `json:"username"`
Email string `json:"email"`
FullName *string `json:"full_name,omitempty"`
Password *string `json:"password,omitempty"`
}
// RunUserCreate implements `hf user create`.
func RunUserCreate(username, password, email, fullName, accMgrTokenFlag string) {
// Resolve account-manager token
var accMgrToken string
if mode.IsPaddedCell() {
if accMgrTokenFlag != "" {
output.Error("padded-cell installed, --acc-mgr-token flag disabled, use command directly")
}
tok, err := passmgr.GetAccountManagerToken()
if err != nil {
output.Error("--acc-mgr-token <token> required or execute with pcexec")
}
accMgrToken = tok
} else {
if accMgrTokenFlag == "" {
output.Error("--acc-mgr-token <token> required or execute with pcexec")
}
accMgrToken = accMgrTokenFlag
}
// Resolve password
if password == "" && mode.IsPaddedCell() {
pw, err := passmgr.GeneratePassword("hf", username)
if err != nil {
output.Error("--pass <password> required or execute with pcexec")
}
password = pw
}
if password == "" && !mode.IsPaddedCell() {
output.Error("--pass <password> required or execute with pcexec")
}
// Resolve email (default to username@harborforge.local if not provided)
if email == "" {
email = username + "@harborforge.local"
}
payload := userCreatePayload{
Username: username,
Email: email,
Password: &password,
}
if fullName != "" {
payload.FullName = &fullName
}
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.NewWithAPIKey(cfg.BaseURL, accMgrToken)
data, err := c.Post("/users", bytes.NewReader(body))
if err != nil {
output.Errorf("failed to create user: %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 u userResponse
if err := json.Unmarshal(data, &u); err != nil {
output.Errorf("cannot parse response: %v", err)
}
fmt.Printf("user created: %s\n", u.Username)
}
// RunUserUpdate implements `hf user update <username>`.
func RunUserUpdate(username string, args []string, tokenFlag string) {
token := ResolveToken(tokenFlag)
payload := make(map[string]interface{})
for i := 0; i < len(args); i++ {
switch args[i] {
case "--email":
if i+1 >= len(args) {
output.Error("--email requires a value")
}
i++
payload["email"] = args[i]
case "--full-name":
if i+1 >= len(args) {
output.Error("--full-name requires a value")
}
i++
payload["full_name"] = args[i]
case "--pass":
if i+1 >= len(args) {
output.Error("--pass requires a value")
}
i++
payload["password"] = args[i]
case "--active":
if i+1 >= len(args) {
output.Error("--active requires true or false")
}
i++
payload["is_active"] = strings.ToLower(args[i]) == "true"
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)
data, err := c.Patch("/users/"+username, bytes.NewReader(body))
if err != nil {
output.Errorf("failed to update user: %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("user updated: %s\n", username)
}
// RunUserActivate implements `hf user activate <username>`.
func RunUserActivate(username, tokenFlag string) {
token := ResolveToken(tokenFlag)
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
body, _ := json.Marshal(map[string]interface{}{"is_active": true})
c := client.New(cfg.BaseURL, token)
_, err = c.Patch("/users/"+username, bytes.NewReader(body))
if err != nil {
output.Errorf("failed to activate user: %v", err)
}
fmt.Printf("user activated: %s\n", username)
}
// RunUserDeactivate implements `hf user deactivate <username>`.
func RunUserDeactivate(username, tokenFlag string) {
token := ResolveToken(tokenFlag)
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
body, _ := json.Marshal(map[string]interface{}{"is_active": false})
c := client.New(cfg.BaseURL, token)
_, err = c.Patch("/users/"+username, bytes.NewReader(body))
if err != nil {
output.Errorf("failed to deactivate user: %v", err)
}
fmt.Printf("user deactivated: %s\n", username)
}
// RunUserDelete implements `hf user delete <username>`.
func RunUserDelete(username, 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("/users/" + username)
if err != nil {
output.Errorf("failed to delete user: %v", err)
}
fmt.Printf("user deleted: %s\n", username)
}