message.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. package cmd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "gogs.dmsc.dev/arp/arp_cli/client"
  8. "gogs.dmsc.dev/arp/arp_cli/config"
  9. "github.com/AlecAivazis/survey/v2"
  10. "github.com/olekukonko/tablewriter"
  11. "github.com/urfave/cli/v3"
  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 in channels 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.StringFlag{
  58. Name: "conversation",
  59. Aliases: []string{"c"},
  60. Usage: "Conversation/Channel ID",
  61. },
  62. &cli.StringFlag{
  63. Name: "sender",
  64. Aliases: []string{"s"},
  65. Usage: "Sender user ID",
  66. },
  67. &cli.StringFlag{
  68. Name: "content",
  69. Aliases: []string{"m"},
  70. Usage: "Message content",
  71. },
  72. },
  73. },
  74. {
  75. Name: "update",
  76. Usage: "Update a message",
  77. Action: messageUpdate,
  78. Flags: []cli.Flag{
  79. &cli.StringFlag{
  80. Name: "id",
  81. Aliases: []string{"i"},
  82. Usage: "Message ID",
  83. Required: true,
  84. },
  85. &cli.StringFlag{
  86. Name: "content",
  87. Aliases: []string{"c"},
  88. Usage: "Message content",
  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. ConversationID string `json:"conversationId"`
  121. SenderID string `json:"senderId"`
  122. Sender *User `json:"sender"`
  123. Content string `json:"content"`
  124. SentAt string `json:"sentAt"`
  125. CreatedAt string `json:"createdAt"`
  126. UpdatedAt string `json:"updatedAt"`
  127. }
  128. func messageList(ctx context.Context, cmd *cli.Command) error {
  129. cfg, err := config.Load()
  130. if err != nil {
  131. return err
  132. }
  133. if err := RequireAuth(cfg); err != nil {
  134. return err
  135. }
  136. c := client.New(cfg.ServerURL)
  137. c.SetToken(cfg.Token)
  138. query := "query Messages { messages { id conversationId sender { id email } content sentAt createdAt } }"
  139. resp, err := c.Query(query, nil)
  140. if err != nil {
  141. return err
  142. }
  143. var result struct {
  144. Messages []Message `json:"messages"`
  145. }
  146. if err := json.Unmarshal(resp.Data, &result); err != nil {
  147. return err
  148. }
  149. if cmd.Bool("json") {
  150. enc := json.NewEncoder(os.Stdout)
  151. enc.SetIndent("", " ")
  152. return enc.Encode(result.Messages)
  153. }
  154. if len(result.Messages) == 0 {
  155. fmt.Println("No messages found.")
  156. return nil
  157. }
  158. table := tablewriter.NewWriter(os.Stdout)
  159. table.Header([]string{"ID", "Conversation", "Sender", "Content", "Sent At"})
  160. for _, m := range result.Messages {
  161. sender := ""
  162. if m.Sender != nil {
  163. sender = m.Sender.Email
  164. }
  165. content := m.Content
  166. if len(content) > 50 {
  167. content = content[:47] + "..."
  168. }
  169. table.Append([]string{m.ID, m.ConversationID, sender, content, m.SentAt})
  170. }
  171. table.Render()
  172. return nil
  173. }
  174. func messageGet(ctx context.Context, cmd *cli.Command) error {
  175. cfg, err := config.Load()
  176. if err != nil {
  177. return err
  178. }
  179. if err := RequireAuth(cfg); err != nil {
  180. return err
  181. }
  182. c := client.New(cfg.ServerURL)
  183. c.SetToken(cfg.Token)
  184. id := cmd.String("id")
  185. query := "query Message($id: ID!) { message(id: $id) { id conversationId sender { id email } content sentAt createdAt updatedAt } }"
  186. resp, err := c.Query(query, map[string]interface{}{"id": id})
  187. if err != nil {
  188. return err
  189. }
  190. var result struct {
  191. Message *Message `json:"message"`
  192. }
  193. if err := json.Unmarshal(resp.Data, &result); err != nil {
  194. return err
  195. }
  196. if result.Message == nil {
  197. return fmt.Errorf("message not found")
  198. }
  199. if cmd.Bool("json") {
  200. enc := json.NewEncoder(os.Stdout)
  201. enc.SetIndent("", " ")
  202. return enc.Encode(result.Message)
  203. }
  204. m := result.Message
  205. fmt.Printf("ID: %s\n", m.ID)
  206. fmt.Printf("Conversation ID: %s\n", m.ConversationID)
  207. if m.Sender != nil {
  208. fmt.Printf("Sender: %s (%s)\n", m.Sender.Email, m.SenderID)
  209. } else {
  210. fmt.Printf("Sender ID: %s\n", m.SenderID)
  211. }
  212. fmt.Printf("Content: %s\n", m.Content)
  213. fmt.Printf("Sent At: %s\n", m.SentAt)
  214. fmt.Printf("Created At: %s\n", m.CreatedAt)
  215. fmt.Printf("Updated At: %s\n", m.UpdatedAt)
  216. return nil
  217. }
  218. func messageCreate(ctx context.Context, cmd *cli.Command) error {
  219. cfg, err := config.Load()
  220. if err != nil {
  221. return err
  222. }
  223. if err := RequireAuth(cfg); err != nil {
  224. return err
  225. }
  226. conversationID := cmd.String("conversation")
  227. senderID := cmd.String("sender")
  228. content := cmd.String("content")
  229. if conversationID == "" {
  230. prompt := &survey.Input{Message: "Conversation ID:"}
  231. if err := survey.AskOne(prompt, &conversationID, survey.WithValidator(survey.Required)); err != nil {
  232. return err
  233. }
  234. }
  235. if senderID == "" {
  236. prompt := &survey.Input{Message: "Sender user ID:"}
  237. if err := survey.AskOne(prompt, &senderID, survey.WithValidator(survey.Required)); err != nil {
  238. return err
  239. }
  240. }
  241. if content == "" {
  242. prompt := &survey.Multiline{Message: "Message content:"}
  243. if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil {
  244. return err
  245. }
  246. }
  247. c := client.New(cfg.ServerURL)
  248. c.SetToken(cfg.Token)
  249. mutation := `mutation CreateMessage($input: NewMessage!) { createMessage(input: $input) { id conversationId sender { id email } content sentAt createdAt } }`
  250. input := map[string]interface{}{
  251. "conversationId": conversationID,
  252. "senderId": senderID,
  253. "content": content,
  254. }
  255. resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
  256. if err != nil {
  257. return err
  258. }
  259. var result struct {
  260. CreateMessage *Message `json:"createMessage"`
  261. }
  262. if err := json.Unmarshal(resp.Data, &result); err != nil {
  263. return err
  264. }
  265. if result.CreateMessage == nil {
  266. return fmt.Errorf("failed to create message")
  267. }
  268. fmt.Printf("Message created successfully!\n")
  269. fmt.Printf("ID: %s\n", result.CreateMessage.ID)
  270. return nil
  271. }
  272. func messageUpdate(ctx context.Context, cmd *cli.Command) error {
  273. cfg, err := config.Load()
  274. if err != nil {
  275. return err
  276. }
  277. if err := RequireAuth(cfg); err != nil {
  278. return err
  279. }
  280. id := cmd.String("id")
  281. content := cmd.String("content")
  282. if content == "" {
  283. prompt := &survey.Multiline{Message: "New message content:"}
  284. if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil {
  285. return err
  286. }
  287. }
  288. c := client.New(cfg.ServerURL)
  289. c.SetToken(cfg.Token)
  290. input := map[string]interface{}{
  291. "content": content,
  292. }
  293. mutation := `mutation UpdateMessage($id: ID!, $input: UpdateMessageInput!) { updateMessage(id: $id, input: $input) { id content updatedAt } }`
  294. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
  295. if err != nil {
  296. return err
  297. }
  298. var result struct {
  299. UpdateMessage *Message `json:"updateMessage"`
  300. }
  301. if err := json.Unmarshal(resp.Data, &result); err != nil {
  302. return err
  303. }
  304. if result.UpdateMessage == nil {
  305. return fmt.Errorf("message not found")
  306. }
  307. fmt.Printf("Message updated successfully!\n")
  308. fmt.Printf("ID: %s\n", result.UpdateMessage.ID)
  309. return nil
  310. }
  311. func messageDelete(ctx context.Context, cmd *cli.Command) error {
  312. cfg, err := config.Load()
  313. if err != nil {
  314. return err
  315. }
  316. if err := RequireAuth(cfg); err != nil {
  317. return err
  318. }
  319. id := cmd.String("id")
  320. skipConfirm := cmd.Bool("yes")
  321. if !skipConfirm {
  322. confirm := false
  323. prompt := &survey.Confirm{
  324. Message: fmt.Sprintf("Are you sure you want to delete message %s?", id),
  325. Default: false,
  326. }
  327. if err := survey.AskOne(prompt, &confirm); err != nil {
  328. return err
  329. }
  330. if !confirm {
  331. fmt.Println("Deletion cancelled.")
  332. return nil
  333. }
  334. }
  335. c := client.New(cfg.ServerURL)
  336. c.SetToken(cfg.Token)
  337. mutation := `mutation DeleteMessage($id: ID!) { deleteMessage(id: $id) }`
  338. resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
  339. if err != nil {
  340. return err
  341. }
  342. var result struct {
  343. DeleteMessage bool `json:"deleteMessage"`
  344. }
  345. if err := json.Unmarshal(resp.Data, &result); err != nil {
  346. return err
  347. }
  348. if result.DeleteMessage {
  349. fmt.Printf("Message %s deleted successfully.\n", id)
  350. } else {
  351. fmt.Printf("Failed to delete message %s.\n", id)
  352. }
  353. return nil
  354. }
  355. func messageWatch(ctx context.Context, cmd *cli.Command) error {
  356. cfg, err := config.Load()
  357. if err != nil {
  358. return err
  359. }
  360. if err := RequireAuth(cfg); err != nil {
  361. return err
  362. }
  363. wsClient := client.NewWebSocketClient(cfg.ServerURL, cfg.Token)
  364. if err := wsClient.Connect(); err != nil {
  365. return fmt.Errorf("failed to connect: %w", err)
  366. }
  367. defer wsClient.Close()
  368. fmt.Println("Watching for new messages...")
  369. fmt.Println("Press Ctrl+C to stop.")
  370. subscription := "subscription { messageAdded { id conversationId sender { id email } content sentAt } }"
  371. if err := wsClient.Subscribe("1", subscription, nil); err != nil {
  372. return fmt.Errorf("failed to subscribe: %w", err)
  373. }
  374. for {
  375. select {
  376. case msg := <-wsClient.Messages():
  377. fmt.Printf("New message: %s\n", string(msg))
  378. case err := <-wsClient.Errors():
  379. fmt.Fprintf(os.Stderr, "Error: %v\n", err)
  380. case <-wsClient.Done():
  381. return nil
  382. case <-ctx.Done():
  383. return nil
  384. }
  385. }
  386. }