package cmd import ( "context" "encoding/json" "fmt" "os" "strings" "gogs.dmsc.dev/arp/arp_cli/client" "gogs.dmsc.dev/arp/arp_cli/config" "github.com/AlecAivazis/survey/v2" "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v3" ) // UserCommand returns the user command func UserCommand() *cli.Command { return &cli.Command{ Name: "user", Usage: "Manage users", Description: `Manage ARP users. Users are accounts that can authenticate and interact with the system. Users can have roles assigned to them which grant permissions. Use this command to create, list, update, and delete users.`, Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all users", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: userList, }, { Name: "get", Usage: "Get a user by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "User ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: userGet, }, { Name: "create", Usage: "Create a new user", Action: userCreate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "email", Aliases: []string{"e"}, Usage: "User email", }, &cli.StringFlag{ Name: "password", Aliases: []string{"p"}, Usage: "User password", }, &cli.StringSliceFlag{ Name: "roles", Aliases: []string{"r"}, Usage: "Role IDs to assign", }, }, }, { Name: "update", Usage: "Update a user", Action: userUpdate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "User ID", Required: true, }, &cli.StringFlag{ Name: "email", Aliases: []string{"e"}, Usage: "User email", }, &cli.StringFlag{ Name: "password", Aliases: []string{"p"}, Usage: "User password", }, &cli.StringSliceFlag{ Name: "roles", Aliases: []string{"r"}, Usage: "Role IDs to assign", }, }, }, { Name: "delete", Usage: "Delete a user", Action: userDelete, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "User ID", Required: true, }, &cli.BoolFlag{ Name: "yes", Aliases: []string{"y"}, Usage: "Skip confirmation", }, }, }, }, } } type User struct { ID string `json:"id"` Email string `json:"email"` Roles []Role `json:"roles"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type Role struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` } func userList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) query := "query Users { users { id email roles { id name } createdAt updatedAt } }" resp, err := c.Query(query, nil) if err != nil { return err } var result struct { Users []User `json:"users"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Users) } if len(result.Users) == 0 { fmt.Println("No users found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Email", "Roles", "Created At"}) for _, u := range result.Users { roles := make([]string, len(u.Roles)) for i, r := range u.Roles { roles[i] = r.Name } table.Append([]string{u.ID, u.Email, strings.Join(roles, ", "), u.CreatedAt}) } table.Render() return nil } func userGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := "query User($id: ID!) { user(id: $id) { id email roles { id name description } createdAt updatedAt } }" resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { User *User `json:"user"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.User == nil { return fmt.Errorf("user not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.User) } u := result.User fmt.Printf("ID: %s\n", u.ID) fmt.Printf("Email: %s\n", u.Email) fmt.Printf("Roles:\n") for _, r := range u.Roles { fmt.Printf(" - %s (%s): %s\n", r.Name, r.ID, r.Description) } fmt.Printf("Created At: %s\n", u.CreatedAt) fmt.Printf("Updated At: %s\n", u.UpdatedAt) return nil } func userCreate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } email := cmd.String("email") password := cmd.String("password") roles := cmd.StringSlice("roles") if email == "" { prompt := &survey.Input{Message: "Email:"} if err := survey.AskOne(prompt, &email, survey.WithValidator(survey.Required)); err != nil { return err } } if password == "" { prompt := &survey.Password{Message: "Password:"} if err := survey.AskOne(prompt, &password, survey.WithValidator(survey.Required)); err != nil { return err } } if len(roles) == 0 { var rolesStr string prompt := &survey.Input{Message: "Role IDs (comma-separated):"} if err := survey.AskOne(prompt, &rolesStr, survey.WithValidator(survey.Required)); err != nil { return err } for _, r := range strings.Split(rolesStr, ",") { roles = append(roles, strings.TrimSpace(r)) } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CreateUser($input: NewUser!) { createUser(input: $input) { id email roles { id name } createdAt updatedAt } }` input := map[string]interface{}{ "email": email, "password": password, "roles": roles, } resp, err := c.Mutation(mutation, map[string]interface{}{"input": input}) if err != nil { return err } var result struct { CreateUser *User `json:"createUser"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CreateUser == nil { return fmt.Errorf("failed to create user") } fmt.Printf("User created successfully!\n") fmt.Printf("ID: %s\n", result.CreateUser.ID) fmt.Printf("Email: %s\n", result.CreateUser.Email) return nil } func userUpdate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") email := cmd.String("email") password := cmd.String("password") roles := cmd.StringSlice("roles") if email == "" && password == "" && len(roles) == 0 { fmt.Println("No updates provided. Use flags to specify what to update.") return nil } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) input := make(map[string]interface{}) if email != "" { input["email"] = email } if password != "" { input["password"] = password } if len(roles) > 0 { input["roles"] = roles } mutation := `mutation UpdateUser($id: ID!, $input: UpdateUserInput!) { updateUser(id: $id, input: $input) { id email roles { id name } createdAt updatedAt } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input}) if err != nil { return err } var result struct { UpdateUser *User `json:"updateUser"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.UpdateUser == nil { return fmt.Errorf("user not found") } fmt.Printf("User updated successfully!\n") fmt.Printf("ID: %s\n", result.UpdateUser.ID) fmt.Printf("Email: %s\n", result.UpdateUser.Email) return nil } func userDelete(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") skipConfirm := cmd.Bool("yes") if !skipConfirm { confirm := false prompt := &survey.Confirm{ Message: fmt.Sprintf("Are you sure you want to delete user %s?", id), Default: false, } if err := survey.AskOne(prompt, &confirm); err != nil { return err } if !confirm { fmt.Println("Deletion cancelled.") return nil } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation DeleteUser($id: ID!) { deleteUser(id: $id) }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { DeleteUser bool `json:"deleteUser"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.DeleteUser { fmt.Printf("User %s deleted successfully.\n", id) } else { fmt.Printf("Failed to delete user %s.\n", id) } return nil }