1
0

message.go 12 KB

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