user.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package cmd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "github.com/AlecAivazis/survey/v2"
  9. "github.com/olekukonko/tablewriter"
  10. "github.com/urfave/cli/v3"
  11. )
  12. // UserCommand returns the user command
  13. func UserCommand() *cli.Command {
  14. return &cli.Command{
  15. Name: "user",
  16. Usage: "Manage users",
  17. Description: `Manage ARP users. Users are accounts that can authenticate and interact with the system.
  18. Users can have roles assigned to them which grant permissions. Use this command to
  19. create, list, update, and delete users.`,
  20. Commands: []*cli.Command{
  21. {
  22. Name: "list",
  23. Aliases: []string{"ls"},
  24. Usage: "List all users",
  25. Flags: []cli.Flag{
  26. &cli.BoolFlag{
  27. Name: "json",
  28. Aliases: []string{"j"},
  29. Usage: "Output as JSON",
  30. },
  31. },
  32. Action: userList,
  33. },
  34. {
  35. Name: "get",
  36. Usage: "Get a user by ID",
  37. Flags: []cli.Flag{
  38. &cli.StringFlag{
  39. Name: "id",
  40. Aliases: []string{"i"},
  41. Usage: "User ID",
  42. Required: true,
  43. },
  44. &cli.BoolFlag{
  45. Name: "json",
  46. Aliases: []string{"j"},
  47. Usage: "Output as JSON",
  48. },
  49. },
  50. Action: userGet,
  51. },
  52. {
  53. Name: "create",
  54. Usage: "Create a new user",
  55. Action: userCreate,
  56. Flags: []cli.Flag{
  57. &cli.StringFlag{
  58. Name: "email",
  59. Aliases: []string{"e"},
  60. Usage: "User email",
  61. },
  62. &cli.StringFlag{
  63. Name: "password",
  64. Aliases: []string{"p"},
  65. Usage: "User password",
  66. },
  67. &cli.StringSliceFlag{
  68. Name: "roles",
  69. Aliases: []string{"r"},
  70. Usage: "Role IDs to assign",
  71. },
  72. },
  73. },
  74. {
  75. Name: "update",
  76. Usage: "Update a user",
  77. Action: userUpdate,
  78. Flags: []cli.Flag{
  79. &cli.StringFlag{
  80. Name: "id",
  81. Aliases: []string{"i"},
  82. Usage: "User ID",
  83. Required: true,
  84. },
  85. &cli.StringFlag{
  86. Name: "email",
  87. Aliases: []string{"e"},
  88. Usage: "User email",
  89. },
  90. &cli.StringFlag{
  91. Name: "password",
  92. Aliases: []string{"p"},
  93. Usage: "User password",
  94. },
  95. &cli.StringSliceFlag{
  96. Name: "roles",
  97. Aliases: []string{"r"},
  98. Usage: "Role IDs to assign",
  99. },
  100. },
  101. },
  102. {
  103. Name: "delete",
  104. Usage: "Delete a user",
  105. Action: userDelete,
  106. Flags: []cli.Flag{
  107. &cli.StringFlag{
  108. Name: "id",
  109. Aliases: []string{"i"},
  110. Usage: "User ID",
  111. Required: true,
  112. },
  113. &cli.BoolFlag{
  114. Name: "yes",
  115. Aliases: []string{"y"},
  116. Usage: "Skip confirmation",
  117. },
  118. },
  119. },
  120. },
  121. }
  122. }
  123. type User struct {
  124. ID string `json:"id"`
  125. Email string `json:"email"`
  126. Roles []Role `json:"roles"`
  127. CreatedAt string `json:"createdAt"`
  128. UpdatedAt string `json:"updatedAt"`
  129. }
  130. type Role struct {
  131. ID string `json:"id"`
  132. Name string `json:"name"`
  133. Description string `json:"description"`
  134. }
  135. func userList(ctx context.Context, cmd *cli.Command) error {
  136. c, cfg, err := GetClient(ctx, cmd)
  137. if err != nil {
  138. return err
  139. }
  140. if err := RequireAuth(cfg); err != nil {
  141. return err
  142. }
  143. query := "query Users { users { id email roles { id name } createdAt updatedAt } }"
  144. resp, err := c.Query(query, nil)
  145. if err != nil {
  146. return err
  147. }
  148. var result struct {
  149. Users []User `json:"users"`
  150. }
  151. if err := json.Unmarshal(resp.Data, &result); err != nil {
  152. return err
  153. }
  154. if cmd.Bool("json") {
  155. enc := json.NewEncoder(os.Stdout)
  156. enc.SetIndent("", " ")
  157. return enc.Encode(result.Users)
  158. }
  159. if len(result.Users) == 0 {
  160. fmt.Println("No users found.")
  161. return nil
  162. }
  163. table := tablewriter.NewWriter(os.Stdout)
  164. table.Header([]string{"ID", "Email", "Roles", "Created At"})
  165. for _, u := range result.Users {
  166. roles := make([]string, len(u.Roles))
  167. for i, r := range u.Roles {
  168. roles[i] = r.Name
  169. }
  170. table.Append([]string{u.ID, u.Email, strings.Join(roles, ", "), u.CreatedAt})
  171. }
  172. table.Render()
  173. return nil
  174. }
  175. func userGet(ctx context.Context, cmd *cli.Command) error {
  176. c, cfg, err := GetClient(ctx, cmd)
  177. if err != nil {
  178. return err
  179. }
  180. if err := RequireAuth(cfg); err != nil {
  181. return err
  182. }
  183. id := cmd.String("id")
  184. query := "query User($id: ID!) { user(id: $id) { id email roles { id name description } createdAt updatedAt } }"
  185. resp, err := c.Query(query, map[string]interface{}{"id": id})
  186. if err != nil {
  187. return err
  188. }
  189. var result struct {
  190. User *User `json:"user"`
  191. }
  192. if err := json.Unmarshal(resp.Data, &result); err != nil {
  193. return err
  194. }
  195. if result.User == nil {
  196. return fmt.Errorf("user not found")
  197. }
  198. if cmd.Bool("json") {
  199. enc := json.NewEncoder(os.Stdout)
  200. enc.SetIndent("", " ")
  201. return enc.Encode(result.User)
  202. }
  203. u := result.User
  204. fmt.Printf("ID: %s\n", u.ID)
  205. fmt.Printf("Email: %s\n", u.Email)
  206. fmt.Printf("Roles:\n")
  207. for _, r := range u.Roles {
  208. fmt.Printf(" - %s (%s): %s\n", r.Name, r.ID, r.Description)
  209. }
  210. fmt.Printf("Created At: %s\n", u.CreatedAt)
  211. fmt.Printf("Updated At: %s\n", u.UpdatedAt)
  212. return nil
  213. }
  214. func userCreate(ctx context.Context, cmd *cli.Command) error {
  215. c, cfg, err := GetClient(ctx, cmd)
  216. if err != nil {
  217. return err
  218. }
  219. if err := RequireAuth(cfg); err != nil {
  220. return err
  221. }
  222. email := cmd.String("email")
  223. password := cmd.String("password")
  224. roles := cmd.StringSlice("roles")
  225. if email == "" {
  226. prompt := &survey.Input{Message: "Email:"}
  227. if err := survey.AskOne(prompt, &email, survey.WithValidator(survey.Required)); err != nil {
  228. return err
  229. }
  230. }
  231. if password == "" {
  232. prompt := &survey.Password{Message: "Password:"}
  233. if err := survey.AskOne(prompt, &password, survey.WithValidator(survey.Required)); err != nil {
  234. return err
  235. }
  236. }
  237. if len(roles) == 0 {
  238. var rolesStr string
  239. prompt := &survey.Input{Message: "Role IDs (comma-separated):"}
  240. if err := survey.AskOne(prompt, &rolesStr, survey.WithValidator(survey.Required)); err != nil {
  241. return err
  242. }
  243. for _, r := range strings.Split(rolesStr, ",") {
  244. roles = append(roles, strings.TrimSpace(r))
  245. }
  246. }
  247. mutation := `mutation CreateUser($input: NewUser!) { createUser(input: $input) { id email roles { id name } createdAt updatedAt } }`
  248. input := map[string]interface{}{
  249. "email": email,
  250. "password": password,
  251. "roles": roles,
  252. }
  253. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  254. if err != nil {
  255. return err
  256. }
  257. var result struct {
  258. CreateUser *User `json:"createUser"`
  259. }
  260. if err := json.Unmarshal(resp.Data, &result); err != nil {
  261. return err
  262. }
  263. if result.CreateUser == nil {
  264. return fmt.Errorf("failed to create user")
  265. }
  266. fmt.Printf("User created successfully!\n")
  267. fmt.Printf("ID: %s\n", result.CreateUser.ID)
  268. fmt.Printf("Email: %s\n", result.CreateUser.Email)
  269. return nil
  270. }
  271. func userUpdate(ctx context.Context, cmd *cli.Command) error {
  272. c, cfg, err := GetClient(ctx, cmd)
  273. if err != nil {
  274. return err
  275. }
  276. if err := RequireAuth(cfg); err != nil {
  277. return err
  278. }
  279. id := cmd.String("id")
  280. email := cmd.String("email")
  281. password := cmd.String("password")
  282. roles := cmd.StringSlice("roles")
  283. if email == "" && password == "" && len(roles) == 0 {
  284. fmt.Println("No updates provided. Use flags to specify what to update.")
  285. return nil
  286. }
  287. input := make(map[string]interface{})
  288. if email != "" {
  289. input["email"] = email
  290. }
  291. if password != "" {
  292. input["password"] = password
  293. }
  294. if len(roles) > 0 {
  295. input["roles"] = roles
  296. }
  297. mutation := `mutation UpdateUser($id: ID!, $input: UpdateUserInput!) { updateUser(id: $id, input: $input) { id email roles { id name } createdAt updatedAt } }`
  298. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  299. if err != nil {
  300. return err
  301. }
  302. var result struct {
  303. UpdateUser *User `json:"updateUser"`
  304. }
  305. if err := json.Unmarshal(resp.Data, &result); err != nil {
  306. return err
  307. }
  308. if result.UpdateUser == nil {
  309. return fmt.Errorf("user not found")
  310. }
  311. fmt.Printf("User updated successfully!\n")
  312. fmt.Printf("ID: %s\n", result.UpdateUser.ID)
  313. fmt.Printf("Email: %s\n", result.UpdateUser.Email)
  314. return nil
  315. }
  316. func userDelete(ctx context.Context, cmd *cli.Command) error {
  317. c, cfg, err := GetClient(ctx, cmd)
  318. if err != nil {
  319. return err
  320. }
  321. if err := RequireAuth(cfg); err != nil {
  322. return err
  323. }
  324. id := cmd.String("id")
  325. skipConfirm := cmd.Bool("yes")
  326. if !skipConfirm {
  327. confirm := false
  328. prompt := &survey.Confirm{
  329. Message: fmt.Sprintf("Are you sure you want to delete user %s?", id),
  330. Default: false,
  331. }
  332. if err := survey.AskOne(prompt, &confirm); err != nil {
  333. return err
  334. }
  335. if !confirm {
  336. fmt.Println("Deletion cancelled.")
  337. return nil
  338. }
  339. }
  340. mutation := `mutation DeleteUser($id: ID!) { deleteUser(id: $id) }`
  341. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  342. if err != nil {
  343. return err
  344. }
  345. var result struct {
  346. DeleteUser bool `json:"deleteUser"`
  347. }
  348. if err := json.Unmarshal(resp.Data, &result); err != nil {
  349. return err
  350. }
  351. if result.DeleteUser {
  352. fmt.Printf("User %s deleted successfully.\n", id)
  353. } else {
  354. fmt.Printf("Failed to delete user %s.\n", id)
  355. }
  356. return nil
  357. }