package cmd import ( "context" "encoding/json" "fmt" "os" "strings" "gogs.dmsc.dev/arp/arp_cli/client" "gogs.dmsc.dev/arp/arp_cli/config" "github.com/AlecAivazis/survey/v2" "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v3" ) // MessageCommand returns the message command func MessageCommand() *cli.Command { return &cli.Command{ Name: "message", Usage: "Manage messages", Description: `Manage ARP messages. Messages are sent between users/agents. Use this command to create, list, update, and delete messages. You can also watch for real-time message updates.`, Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all messages", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: messageList, }, { Name: "get", Usage: "Get a message by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: messageGet, }, { Name: "create", Usage: "Create a new message", Action: messageCreate, Flags: []cli.Flag{ &cli.StringSliceFlag{ Name: "receivers", Aliases: []string{"r"}, Usage: "Receiver user IDs (comma-separated or multiple flags)", }, &cli.StringFlag{ Name: "content", Aliases: []string{"m"}, Usage: "Message content", }, }, }, { Name: "update", Usage: "Update a message", Action: messageUpdate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.StringFlag{ Name: "content", Aliases: []string{"c"}, Usage: "Message content", }, &cli.StringSliceFlag{ Name: "receivers", Aliases: []string{"r"}, Usage: "Receiver user IDs (comma-separated or multiple flags)", }, }, }, { Name: "delete", Usage: "Delete a message", Action: messageDelete, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.BoolFlag{ Name: "yes", Aliases: []string{"y"}, Usage: "Skip confirmation", }, }, }, { Name: "watch", Usage: "Watch for real-time message updates", Action: messageWatch, }, }, } } type Message struct { ID string `json:"id"` SenderID string `json:"senderId"` Sender *User `json:"sender"` Content string `json:"content"` SentAt string `json:"sentAt"` Receivers []string `json:"receivers"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } func messageList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) query := "query Messages { messages { id sender { id email } content sentAt receivers createdAt } }" resp, err := c.Query(query, nil) if err != nil { return err } var result struct { Messages []Message `json:"messages"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Messages) } if len(result.Messages) == 0 { fmt.Println("No messages found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Sender", "Content", "Receivers", "Sent At"}) for _, m := range result.Messages { sender := "" if m.Sender != nil { sender = m.Sender.Email } content := m.Content if len(content) > 50 { content = content[:47] + "..." } receivers := strings.Join(m.Receivers, ", ") table.Append([]string{m.ID, sender, content, receivers, m.SentAt}) } table.Render() return nil } func messageGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := "query Message($id: ID!) { message(id: $id) { id sender { id email } content sentAt receivers createdAt updatedAt } }" resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { Message *Message `json:"message"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.Message == nil { return fmt.Errorf("message not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Message) } m := result.Message fmt.Printf("ID: %s\n", m.ID) if m.Sender != nil { fmt.Printf("Sender: %s (%s)\n", m.Sender.Email, m.SenderID) } else { fmt.Printf("Sender ID: %s\n", m.SenderID) } fmt.Printf("Content: %s\n", m.Content) fmt.Printf("Receivers: %s\n", strings.Join(m.Receivers, ", ")) fmt.Printf("Sent At: %s\n", m.SentAt) fmt.Printf("Created At: %s\n", m.CreatedAt) fmt.Printf("Updated At: %s\n", m.UpdatedAt) return nil } func messageCreate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } receivers := cmd.StringSlice("receivers") content := cmd.String("content") if len(receivers) == 0 { var receiverInput string prompt := &survey.Input{Message: "Receiver user IDs (comma-separated):"} if err := survey.AskOne(prompt, &receiverInput, survey.WithValidator(survey.Required)); err != nil { return err } receivers = strings.Split(receiverInput, ",") for i, r := range receivers { receivers[i] = strings.TrimSpace(r) } } if content == "" { prompt := &survey.Multiline{Message: "Message content:"} if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil { return err } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CreateMessage($input: NewMessage!) { createMessage(input: $input) { id sender { id email } content sentAt receivers createdAt } }` input := map[string]interface{}{ "content": content, "receivers": receivers, } resp, err := c.Mutation(mutation, map[string]interface{}{"input": input}) if err != nil { return err } var result struct { CreateMessage *Message `json:"createMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CreateMessage == nil { return fmt.Errorf("failed to create message") } fmt.Printf("Message created successfully!\n") fmt.Printf("ID: %s\n", result.CreateMessage.ID) return nil } func messageUpdate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") content := cmd.String("content") receivers := cmd.StringSlice("receivers") if content == "" && len(receivers) == 0 { prompt := &survey.Multiline{Message: "Message content:"} if err := survey.AskOne(prompt, &content); err != nil { return err } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation UpdateMessage($id: ID!, $input: UpdateMessage!) { updateMessage(id: $id, input: $input) { id sender { id email } content sentAt receivers createdAt updatedAt } }` input := map[string]interface{}{} if content != "" { input["content"] = content } if len(receivers) > 0 { input["receivers"] = receivers } resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input}) if err != nil { return err } var result struct { UpdateMessage *Message `json:"updateMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.UpdateMessage == nil { return fmt.Errorf("failed to update message") } fmt.Printf("Message updated successfully!\n") fmt.Printf("ID: %s\n", result.UpdateMessage.ID) return nil } func messageDelete(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") if !cmd.Bool("yes") { confirm := false prompt := &survey.Confirm{Message: fmt.Sprintf("Are you sure you want to delete message %s?", id)} if err := survey.AskOne(prompt, &confirm); err != nil { return err } if !confirm { fmt.Println("Cancelled.") return nil } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation DeleteMessage($id: ID!) { deleteMessage(id: $id) { id } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { DeleteMessage *Message `json:"deleteMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.DeleteMessage == nil { return fmt.Errorf("failed to delete message") } fmt.Printf("Message %s deleted successfully!\n", result.DeleteMessage.ID) return nil } func messageWatch(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } wsClient := client.NewWebSocketClient(cfg.ServerURL, cfg.Token) if err := wsClient.Connect(); err != nil { return fmt.Errorf("failed to connect: %w", err) } defer wsClient.Close() subscription := `subscription MessageAdded { messageAdded { id sender { id email } content sentAt receivers createdAt } }` if err := wsClient.Subscribe("1", subscription, nil); err != nil { return fmt.Errorf("failed to subscribe: %w", err) } fmt.Println("Watching for new messages... (press Ctrl+C to stop)") for { select { case msg := <-wsClient.Messages(): var result struct { MessageAdded *Message `json:"messageAdded"` } if err := json.Unmarshal(msg, &result); err != nil { fmt.Fprintf(os.Stderr, "Error parsing message: %v\n", err) continue } if result.MessageAdded != nil { m := result.MessageAdded sender := "Unknown" if m.Sender != nil { sender = m.Sender.Email } fmt.Printf("\n--- New Message ---\n") fmt.Printf("ID: %s\n", m.ID) fmt.Printf("From: %s\n", sender) fmt.Printf("Content: %s\n", m.Content) fmt.Printf("Receivers: %s\n", strings.Join(m.Receivers, ", ")) fmt.Printf("Sent At: %s\n", m.SentAt) fmt.Println("--------------------") } case err := <-wsClient.Errors(): fmt.Fprintf(os.Stderr, "Error: %v\n", err) case <-wsClient.Done(): return nil case <-ctx.Done(): return nil } } }