message.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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. // MessageCommand returns the message command
  15. func MessageCommand() *cli.Command {
  16. return &cli.Command{
  17. Name: "message",
  18. Usage: "Manage messages",
  19. Description: `Manage ARP messages. Messages are sent between users/agents.
  20. Use this command to create, list, update, and delete messages. You can also watch for real-time message updates.`,
  21. Commands: []*cli.Command{
  22. {
  23. Name: "list",
  24. Aliases: []string{"ls"},
  25. Usage: "List all messages",
  26. Flags: []cli.Flag{
  27. &cli.BoolFlag{
  28. Name: "json",
  29. Aliases: []string{"j"},
  30. Usage: "Output as JSON",
  31. },
  32. },
  33. Action: messageList,
  34. },
  35. {
  36. Name: "get",
  37. Usage: "Get a message by ID",
  38. Flags: []cli.Flag{
  39. &cli.StringFlag{
  40. Name: "id",
  41. Aliases: []string{"i"},
  42. Usage: "Message ID",
  43. Required: true,
  44. },
  45. &cli.BoolFlag{
  46. Name: "json",
  47. Aliases: []string{"j"},
  48. Usage: "Output as JSON",
  49. },
  50. },
  51. Action: messageGet,
  52. },
  53. {
  54. Name: "create",
  55. Usage: "Create a new message",
  56. Action: messageCreate,
  57. Flags: []cli.Flag{
  58. &cli.StringSliceFlag{
  59. Name: "receivers",
  60. Aliases: []string{"r"},
  61. Usage: "Receiver user IDs (comma-separated or multiple flags)",
  62. },
  63. &cli.StringFlag{
  64. Name: "content",
  65. Aliases: []string{"m"},
  66. Usage: "Message content",
  67. },
  68. },
  69. },
  70. {
  71. Name: "update",
  72. Usage: "Update a message",
  73. Action: messageUpdate,
  74. Flags: []cli.Flag{
  75. &cli.StringFlag{
  76. Name: "id",
  77. Aliases: []string{"i"},
  78. Usage: "Message ID",
  79. Required: true,
  80. },
  81. &cli.StringFlag{
  82. Name: "content",
  83. Aliases: []string{"c"},
  84. Usage: "Message content",
  85. },
  86. &cli.StringSliceFlag{
  87. Name: "receivers",
  88. Aliases: []string{"r"},
  89. Usage: "Receiver user IDs (comma-separated or multiple flags)",
  90. },
  91. },
  92. },
  93. {
  94. Name: "delete",
  95. Usage: "Delete a message",
  96. Action: messageDelete,
  97. Flags: []cli.Flag{
  98. &cli.StringFlag{
  99. Name: "id",
  100. Aliases: []string{"i"},
  101. Usage: "Message ID",
  102. Required: true,
  103. },
  104. &cli.BoolFlag{
  105. Name: "yes",
  106. Aliases: []string{"y"},
  107. Usage: "Skip confirmation",
  108. },
  109. },
  110. },
  111. {
  112. Name: "watch",
  113. Usage: "Watch for real-time message updates",
  114. Action: messageWatch,
  115. },
  116. },
  117. }
  118. }
  119. type Message struct {
  120. ID string `json:"id"`
  121. SenderID string `json:"senderId"`
  122. Sender *User `json:"sender"`
  123. Content string `json:"content"`
  124. SentAt string `json:"sentAt"`
  125. Receivers []string `json:"receivers"`
  126. ReceiverObjects []*User `json:"receiverObjects"`
  127. CreatedAt string `json:"createdAt"`
  128. UpdatedAt string `json:"updatedAt"`
  129. }
  130. func messageList(ctx context.Context, cmd *cli.Command) error {
  131. cfg, err := config.Load()
  132. if err != nil {
  133. return err
  134. }
  135. if err := RequireAuth(cfg); err != nil {
  136. return err
  137. }
  138. c := client.New(cfg.ServerURL)
  139. c.SetToken(cfg.Token)
  140. query := "query Messages { messages { id sender { id email } content sentAt receivers receiverObjects { id email } createdAt } }"
  141. resp, err := c.Query(query, nil)
  142. if err != nil {
  143. return err
  144. }
  145. var result struct {
  146. Messages []Message `json:"messages"`
  147. }
  148. if err := json.Unmarshal(resp.Data, &result); err != nil {
  149. return err
  150. }
  151. if cmd.Bool("json") {
  152. enc := json.NewEncoder(os.Stdout)
  153. enc.SetIndent("", " ")
  154. return enc.Encode(result.Messages)
  155. }
  156. if len(result.Messages) == 0 {
  157. fmt.Println("No messages found.")
  158. return nil
  159. }
  160. table := tablewriter.NewWriter(os.Stdout)
  161. table.Header([]string{"ID", "Sender", "Content", "Receivers", "Sent At"})
  162. for _, m := range result.Messages {
  163. sender := ""
  164. if m.Sender != nil {
  165. sender = m.Sender.Email
  166. }
  167. content := m.Content
  168. if len(content) > 50 {
  169. content = content[:47] + "..."
  170. }
  171. // Build receiver emails string
  172. var receiverEmails []string
  173. if len(m.ReceiverObjects) > 0 {
  174. for _, receiver := range m.ReceiverObjects {
  175. if receiver != nil {
  176. receiverEmails = append(receiverEmails, receiver.Email)
  177. }
  178. }
  179. } else {
  180. // Fallback to IDs if objects aren't available
  181. receiverEmails = m.Receivers
  182. }
  183. receivers := strings.Join(receiverEmails, ", ")
  184. table.Append([]string{m.ID, sender, content, receivers, m.SentAt})
  185. }
  186. table.Render()
  187. return nil
  188. }
  189. func messageGet(ctx context.Context, cmd *cli.Command) error {
  190. cfg, err := config.Load()
  191. if err != nil {
  192. return err
  193. }
  194. if err := RequireAuth(cfg); err != nil {
  195. return err
  196. }
  197. c := client.New(cfg.ServerURL)
  198. c.SetToken(cfg.Token)
  199. id := cmd.String("id")
  200. query := "query Message($id: ID!) { message(id: $id) { id sender { id email } content sentAt receivers receiverObjects { id email } createdAt updatedAt } }"
  201. resp, err := c.Query(query, map[string]interface{}{"id": id})
  202. if err != nil {
  203. return err
  204. }
  205. var result struct {
  206. Message *Message `json:"message"`
  207. }
  208. if err := json.Unmarshal(resp.Data, &result); err != nil {
  209. return err
  210. }
  211. if result.Message == nil {
  212. return fmt.Errorf("message not found")
  213. }
  214. if cmd.Bool("json") {
  215. enc := json.NewEncoder(os.Stdout)
  216. enc.SetIndent("", " ")
  217. return enc.Encode(result.Message)
  218. }
  219. m := result.Message
  220. fmt.Printf("ID: %s\n", m.ID)
  221. if m.Sender != nil {
  222. fmt.Printf("Sender: %s (%s)\n", m.Sender.Email, m.SenderID)
  223. } else {
  224. fmt.Printf("Sender ID: %s\n", m.SenderID)
  225. }
  226. fmt.Printf("Content: %s\n", m.Content)
  227. // Display receiver emails
  228. var receiverEmails []string
  229. if len(m.ReceiverObjects) > 0 {
  230. for _, receiver := range m.ReceiverObjects {
  231. if receiver != nil {
  232. receiverEmails = append(receiverEmails, receiver.Email)
  233. }
  234. }
  235. } else {
  236. // Fallback to IDs if objects aren't available
  237. receiverEmails = m.Receivers
  238. }
  239. fmt.Printf("Receivers: %s\n", strings.Join(receiverEmails, ", "))
  240. fmt.Printf("Sent At: %s\n", m.SentAt)
  241. fmt.Printf("Created At: %s\n", m.CreatedAt)
  242. fmt.Printf("Updated At: %s\n", m.UpdatedAt)
  243. return nil
  244. }
  245. func messageCreate(ctx context.Context, cmd *cli.Command) error {
  246. cfg, err := config.Load()
  247. if err != nil {
  248. return err
  249. }
  250. if err := RequireAuth(cfg); err != nil {
  251. return err
  252. }
  253. receivers := cmd.StringSlice("receivers")
  254. content := cmd.String("content")
  255. if len(receivers) == 0 {
  256. var receiverInput string
  257. prompt := &survey.Input{Message: "Receiver user IDs (comma-separated):"}
  258. if err := survey.AskOne(prompt, &receiverInput, survey.WithValidator(survey.Required)); err != nil {
  259. return err
  260. }
  261. receivers = strings.Split(receiverInput, ",")
  262. for i, r := range receivers {
  263. receivers[i] = strings.TrimSpace(r)
  264. }
  265. }
  266. if content == "" {
  267. prompt := &survey.Multiline{Message: "Message content:"}
  268. if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil {
  269. return err
  270. }
  271. }
  272. c := client.New(cfg.ServerURL)
  273. c.SetToken(cfg.Token)
  274. mutation := `mutation CreateMessage($input: NewMessage!) { createMessage(input: $input) { id sender { id email } content sentAt receivers createdAt } }`
  275. input := map[string]interface{}{
  276. "content": content,
  277. "receivers": receivers,
  278. }
  279. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  280. if err != nil {
  281. return err
  282. }
  283. var result struct {
  284. CreateMessage *Message `json:"createMessage"`
  285. }
  286. if err := json.Unmarshal(resp.Data, &result); err != nil {
  287. return err
  288. }
  289. if result.CreateMessage == nil {
  290. return fmt.Errorf("failed to create message")
  291. }
  292. fmt.Printf("Message created successfully!\n")
  293. fmt.Printf("ID: %s\n", result.CreateMessage.ID)
  294. return nil
  295. }
  296. func messageUpdate(ctx context.Context, cmd *cli.Command) error {
  297. cfg, err := config.Load()
  298. if err != nil {
  299. return err
  300. }
  301. if err := RequireAuth(cfg); err != nil {
  302. return err
  303. }
  304. id := cmd.String("id")
  305. content := cmd.String("content")
  306. receivers := cmd.StringSlice("receivers")
  307. if content == "" && len(receivers) == 0 {
  308. prompt := &survey.Multiline{Message: "Message content:"}
  309. if err := survey.AskOne(prompt, &content); err != nil {
  310. return err
  311. }
  312. }
  313. c := client.New(cfg.ServerURL)
  314. c.SetToken(cfg.Token)
  315. mutation := `mutation UpdateMessage($id: ID!, $input: UpdateMessage!) { updateMessage(id: $id, input: $input) { id sender { id email } content sentAt receivers createdAt updatedAt } }`
  316. input := map[string]interface{}{}
  317. if content != "" {
  318. input["content"] = content
  319. }
  320. if len(receivers) > 0 {
  321. input["receivers"] = receivers
  322. }
  323. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  324. if err != nil {
  325. return err
  326. }
  327. var result struct {
  328. UpdateMessage *Message `json:"updateMessage"`
  329. }
  330. if err := json.Unmarshal(resp.Data, &result); err != nil {
  331. return err
  332. }
  333. if result.UpdateMessage == nil {
  334. return fmt.Errorf("failed to update message")
  335. }
  336. fmt.Printf("Message updated successfully!\n")
  337. fmt.Printf("ID: %s\n", result.UpdateMessage.ID)
  338. return nil
  339. }
  340. func messageDelete(ctx context.Context, cmd *cli.Command) error {
  341. cfg, err := config.Load()
  342. if err != nil {
  343. return err
  344. }
  345. if err := RequireAuth(cfg); err != nil {
  346. return err
  347. }
  348. id := cmd.String("id")
  349. if !cmd.Bool("yes") {
  350. confirm := false
  351. prompt := &survey.Confirm{Message: fmt.Sprintf("Are you sure you want to delete message %s?", id)}
  352. if err := survey.AskOne(prompt, &confirm); err != nil {
  353. return err
  354. }
  355. if !confirm {
  356. fmt.Println("Cancelled.")
  357. return nil
  358. }
  359. }
  360. c := client.New(cfg.ServerURL)
  361. c.SetToken(cfg.Token)
  362. mutation := `mutation DeleteMessage($id: ID!) { deleteMessage(id: $id) { id } }`
  363. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  364. if err != nil {
  365. return err
  366. }
  367. var result struct {
  368. DeleteMessage *Message `json:"deleteMessage"`
  369. }
  370. if err := json.Unmarshal(resp.Data, &result); err != nil {
  371. return err
  372. }
  373. if result.DeleteMessage == nil {
  374. return fmt.Errorf("failed to delete message")
  375. }
  376. fmt.Printf("Message %s deleted successfully!\n", result.DeleteMessage.ID)
  377. return nil
  378. }
  379. func messageWatch(ctx context.Context, cmd *cli.Command) error {
  380. cfg, err := config.Load()
  381. if err != nil {
  382. return err
  383. }
  384. if err := RequireAuth(cfg); err != nil {
  385. return err
  386. }
  387. wsClient := client.NewWebSocketClient(cfg.ServerURL, cfg.Token)
  388. if err := wsClient.Connect(); err != nil {
  389. return fmt.Errorf("failed to connect: %w", err)
  390. }
  391. defer wsClient.Close()
  392. subscription := `subscription MessageAdded { messageAdded { id sender { id email } content sentAt receivers receiverObjects { id email } createdAt } }`
  393. if err := wsClient.Subscribe("1", subscription, nil); err != nil {
  394. return fmt.Errorf("failed to subscribe: %w", err)
  395. }
  396. fmt.Println("Watching for new messages... (press Ctrl+C to stop)")
  397. for {
  398. select {
  399. case msg := <-wsClient.Messages():
  400. var result struct {
  401. MessageAdded *Message `json:"messageAdded"`
  402. }
  403. if err := json.Unmarshal(msg, &result); err != nil {
  404. fmt.Fprintf(os.Stderr, "Error parsing message: %v\n", err)
  405. continue
  406. }
  407. if result.MessageAdded != nil {
  408. m := result.MessageAdded
  409. sender := "Unknown"
  410. if m.Sender != nil {
  411. sender = m.Sender.Email
  412. }
  413. // Build receiver emails string
  414. var receiverEmails []string
  415. if len(m.ReceiverObjects) > 0 {
  416. for _, receiver := range m.ReceiverObjects {
  417. if receiver != nil {
  418. receiverEmails = append(receiverEmails, receiver.Email)
  419. }
  420. }
  421. } else {
  422. // Fallback to IDs if objects aren't available
  423. receiverEmails = m.Receivers
  424. }
  425. fmt.Printf("\n--- New Message ---\n")
  426. fmt.Printf("ID: %s\n", m.ID)
  427. fmt.Printf("From: %s\n", sender)
  428. fmt.Printf("Content: %s\n", m.Content)
  429. fmt.Printf("Receivers: %s\n", strings.Join(receiverEmails, ", "))
  430. fmt.Printf("Sent At: %s\n", m.SentAt)
  431. fmt.Println("--------------------")
  432. }
  433. case err := <-wsClient.Errors():
  434. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  435. case <-wsClient.Done():
  436. return nil
  437. case <-ctx.Done():
  438. return nil
  439. }
  440. }
  441. }