user.go 9.2 KB

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