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" ) // ServiceCommand returns the service command func ServiceCommand() *cli.Command { return &cli.Command{ Name: "service", Usage: "Manage services", Description: `Manage ARP services. Services are entities that agents coordinate work around. Services can have participants (users) and contain tasks. Use this command to create, list, update, and delete services.`, Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all services", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: serviceList, }, { Name: "get", Usage: "Get a service by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Service ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: serviceGet, }, { Name: "create", Usage: "Create a new service", Action: serviceCreate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "name", Aliases: []string{"n"}, Usage: "Service name", }, &cli.StringFlag{ Name: "description", Aliases: []string{"d"}, Usage: "Service description", }, &cli.StringFlag{ Name: "created-by", Aliases: []string{"c"}, Usage: "Creator user ID", }, &cli.StringSliceFlag{ Name: "participants", Aliases: []string{"p"}, Usage: "Participant user IDs", }, }, }, { Name: "update", Usage: "Update a service", Action: serviceUpdate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Service ID", Required: true, }, &cli.StringFlag{ Name: "name", Aliases: []string{"n"}, Usage: "Service name", }, &cli.StringFlag{ Name: "description", Aliases: []string{"d"}, Usage: "Service description", }, &cli.StringSliceFlag{ Name: "participants", Aliases: []string{"p"}, Usage: "Participant user IDs", }, }, }, { Name: "delete", Usage: "Delete a service", Action: serviceDelete, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Service ID", Required: true, }, &cli.BoolFlag{ Name: "yes", Aliases: []string{"y"}, Usage: "Skip confirmation", }, }, }, }, } } type Service struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` CreatedByID string `json:"createdById"` CreatedBy struct { ID string `json:"id"` Email string `json:"email"` } `json:"createdBy"` Participants []struct { ID string `json:"id"` Email string `json:"email"` } `json:"participants"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } func serviceList(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 Services { services { id name description createdBy { id email } participants { id email } createdAt updatedAt } }" resp, err := c.Query(query, nil) if err != nil { return err } var result struct { Services []Service `json:"services"` } 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.Services) } if len(result.Services) == 0 { fmt.Println("No services found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Name", "Description", "Created By", "Participants", "Created At"}) for _, s := range result.Services { participants := fmt.Sprintf("%d", len(s.Participants)) table.Append([]string{s.ID, s.Name, s.Description, s.CreatedBy.Email, participants, s.CreatedAt}) } table.Render() return nil } func serviceGet(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 Service($id: ID!) { service(id: $id) { id name description createdBy { id email } participants { id email } createdAt updatedAt } }" resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { Service *Service `json:"service"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.Service == nil { return fmt.Errorf("service not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Service) } s := result.Service fmt.Printf("ID: %s\n", s.ID) fmt.Printf("Name: %s\n", s.Name) fmt.Printf("Description: %s\n", s.Description) fmt.Printf("Created By: %s\n", s.CreatedBy.Email) fmt.Printf("Participants:\n") for _, p := range s.Participants { fmt.Printf(" - %s (%s)\n", p.Email, p.ID) } fmt.Printf("Created At: %s\n", s.CreatedAt) fmt.Printf("Updated At: %s\n", s.UpdatedAt) return nil } func serviceCreate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } name := cmd.String("name") description := cmd.String("description") createdBy := cmd.String("created-by") participants := cmd.StringSlice("participants") if name == "" { prompt := &survey.Input{Message: "Service name:"} if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil { return err } } if description == "" { prompt := &survey.Input{Message: "Description (optional):"} survey.AskOne(prompt, &description) } if createdBy == "" { prompt := &survey.Input{Message: "Creator user ID:"} if err := survey.AskOne(prompt, &createdBy, survey.WithValidator(survey.Required)); err != nil { return err } } if len(participants) == 0 { var participantsStr string prompt := &survey.Input{Message: "Participant user IDs (comma-separated, optional):"} if err := survey.AskOne(prompt, &participantsStr); err != nil { return err } if participantsStr != "" { for _, p := range strings.Split(participantsStr, ",") { participants = append(participants, strings.TrimSpace(p)) } } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CreateService($input: NewService!) { createService(input: $input) { id name description createdBy { id email } participants { id email } createdAt updatedAt } }` input := map[string]interface{}{ "name": name, "description": description, "createdById": createdBy, "participants": participants, } resp, err := c.Mutation(mutation, map[string]interface{}{"input": input}) if err != nil { return err } var result struct { CreateService *Service `json:"createService"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CreateService == nil { return fmt.Errorf("failed to create service") } fmt.Printf("Service created successfully!\n") fmt.Printf("ID: %s\n", result.CreateService.ID) fmt.Printf("Name: %s\n", result.CreateService.Name) return nil } func serviceUpdate(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") name := cmd.String("name") description := cmd.String("description") participants := cmd.StringSlice("participants") // Check if any updates are provided if name == "" && description == "" && len(participants) == 0 { fmt.Println("No updates provided. Use flags to specify what to update.") return nil } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) // Build the input dynamically input := make(map[string]interface{}) if name != "" { input["name"] = name } if description != "" { input["description"] = description } if len(participants) > 0 { input["participants"] = participants } mutation := `mutation UpdateService($id: ID!, $input: UpdateServiceInput!) { updateService(id: $id, input: $input) { id name description createdBy { id email } participants { id email } createdAt updatedAt } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input}) if err != nil { return err } var result struct { UpdateService *Service `json:"updateService"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.UpdateService == nil { return fmt.Errorf("service not found") } fmt.Printf("Service updated successfully!\n") fmt.Printf("ID: %s\n", result.UpdateService.ID) fmt.Printf("Name: %s\n", result.UpdateService.Name) return nil } func serviceDelete(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") skipConfirm := cmd.Bool("yes") if !skipConfirm { confirm := false prompt := &survey.Confirm{ Message: fmt.Sprintf("Are you sure you want to delete service %s?", id), Default: false, } if err := survey.AskOne(prompt, &confirm); err != nil { return err } if !confirm { fmt.Println("Deletion cancelled.") return nil } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation DeleteService($id: ID!) { deleteService(id: $id) }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { DeleteService bool `json:"deleteService"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.DeleteService { fmt.Printf("Service %s deleted successfully.\n", id) } else { fmt.Printf("Failed to delete service %s.\n", id) } return nil }