service.go 10 KB

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