1
0

service.go 10 KB

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