| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- package cmd
- import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "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"
- )
- // TaskCommand returns the task command
- func TaskCommand() *cli.Command {
- return &cli.Command{
- Name: "task",
- Usage: "Manage tasks",
- Description: `Manage ARP tasks. Tasks are work items that can be assigned to users.
- Tasks have statuses, priorities, and due dates. Use this command to
- create, list, update, and delete tasks. You can also watch for real-time task updates.`,
- Commands: []*cli.Command{
- {
- Name: "list",
- Aliases: []string{"ls"},
- Usage: "List all tasks",
- Flags: []cli.Flag{
- &cli.BoolFlag{
- Name: "json",
- Aliases: []string{"j"},
- Usage: "Output as JSON",
- },
- },
- Action: taskList,
- },
- {
- Name: "get",
- Usage: "Get a task by ID",
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "id",
- Aliases: []string{"i"},
- Usage: "Task ID",
- Required: true,
- },
- &cli.BoolFlag{
- Name: "json",
- Aliases: []string{"j"},
- Usage: "Output as JSON",
- },
- },
- Action: taskGet,
- },
- {
- Name: "create",
- Usage: "Create a new task",
- Action: taskCreate,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "title",
- Aliases: []string{"t"},
- Usage: "Task title",
- },
- &cli.StringFlag{
- Name: "content",
- Aliases: []string{"c"},
- Usage: "Task content",
- },
- &cli.StringFlag{
- Name: "created-by",
- Aliases: []string{"b"},
- Usage: "Creator user ID",
- },
- &cli.StringFlag{
- Name: "assignee",
- Aliases: []string{"a"},
- Usage: "Assignee user ID",
- },
- &cli.StringFlag{
- Name: "status",
- Aliases: []string{"s"},
- Usage: "Status ID",
- },
- &cli.StringFlag{
- Name: "due-date",
- Aliases: []string{"d"},
- Usage: "Due date",
- },
- &cli.StringFlag{
- Name: "priority",
- Aliases: []string{"p"},
- Usage: "Priority (low, medium, high)",
- },
- },
- },
- {
- Name: "update",
- Usage: "Update a task",
- Action: taskUpdate,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "id",
- Aliases: []string{"i"},
- Usage: "Task ID",
- Required: true,
- },
- &cli.StringFlag{
- Name: "title",
- Aliases: []string{"t"},
- Usage: "Task title",
- },
- &cli.StringFlag{
- Name: "content",
- Aliases: []string{"c"},
- Usage: "Task content",
- },
- &cli.StringFlag{
- Name: "assignee",
- Aliases: []string{"a"},
- Usage: "Assignee user ID",
- },
- &cli.StringFlag{
- Name: "status",
- Aliases: []string{"s"},
- Usage: "Status ID",
- },
- &cli.StringFlag{
- Name: "due-date",
- Aliases: []string{"d"},
- Usage: "Due date",
- },
- &cli.StringFlag{
- Name: "priority",
- Aliases: []string{"p"},
- Usage: "Priority",
- },
- },
- },
- {
- Name: "delete",
- Usage: "Delete a task",
- Action: taskDelete,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "id",
- Aliases: []string{"i"},
- Usage: "Task ID",
- Required: true,
- },
- &cli.BoolFlag{
- Name: "yes",
- Aliases: []string{"y"},
- Usage: "Skip confirmation",
- },
- },
- },
- {
- Name: "watch",
- Usage: "Watch for real-time task updates",
- Action: taskWatch,
- Flags: []cli.Flag{
- &cli.StringFlag{
- Name: "event",
- Aliases: []string{"e"},
- Usage: "Event type (created, updated, deleted, all)",
- Value: "all",
- },
- },
- },
- },
- }
- }
- type Task struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Content string `json:"content"`
- CreatedByID string `json:"createdById"`
- CreatedBy *User `json:"createdBy"`
- UpdatedByID string `json:"updatedById"`
- UpdatedBy *User `json:"updatedBy"`
- AssigneeID *string `json:"assigneeId"`
- Assignee *User `json:"assignee"`
- StatusID *string `json:"statusId"`
- Status *TaskStatus `json:"status"`
- DueDate *string `json:"dueDate"`
- Priority string `json:"priority"`
- CreatedAt string `json:"createdAt"`
- UpdatedAt string `json:"updatedAt"`
- }
- type TaskStatus struct {
- ID string `json:"id"`
- Code string `json:"code"`
- Label string `json:"label"`
- CreatedAt string `json:"createdAt"`
- UpdatedAt string `json:"updatedAt"`
- }
- func taskList(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 Tasks { tasks { id title priority createdBy { id email } assignee { id email } status { id code label } dueDate createdAt updatedAt } }"
- resp, err := c.Query(query, nil)
- if err != nil {
- return err
- }
- var result struct {
- Tasks []Task `json:"tasks"`
- }
- 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.Tasks)
- }
- if len(result.Tasks) == 0 {
- fmt.Println("No tasks found.")
- return nil
- }
- table := tablewriter.NewWriter(os.Stdout)
- table.Header([]string{"ID", "Title", "Priority", "Status", "Assignee", "Due Date"})
-
- for _, t := range result.Tasks {
- status := ""
- if t.Status != nil {
- status = t.Status.Label
- }
- assignee := ""
- if t.Assignee != nil {
- assignee = t.Assignee.Email
- }
- dueDate := ""
- if t.DueDate != nil {
- dueDate = *t.DueDate
- }
- table.Append([]string{t.ID, t.Title, t.Priority, status, assignee, dueDate})
- }
- table.Render()
- return nil
- }
- func taskGet(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 Task($id: ID!) { task(id: $id) { id title content createdBy { id email } assignee { id email } status { id code label } dueDate priority createdAt updatedAt } }"
- resp, err := c.Query(query, map[string]interface{}{"id": id})
- if err != nil {
- return err
- }
- var result struct {
- Task *Task `json:"task"`
- }
- if err := json.Unmarshal(resp.Data, &result); err != nil {
- return err
- }
- if result.Task == nil {
- return fmt.Errorf("task not found")
- }
- if cmd.Bool("json") {
- enc := json.NewEncoder(os.Stdout)
- enc.SetIndent("", " ")
- return enc.Encode(result.Task)
- }
- t := result.Task
- fmt.Printf("ID: %s\n", t.ID)
- fmt.Printf("Title: %s\n", t.Title)
- fmt.Printf("Content: %s\n", t.Content)
- if t.CreatedBy != nil {
- fmt.Printf("Created By: %s\n", t.CreatedBy.Email)
- }
- if t.Assignee != nil {
- fmt.Printf("Assignee: %s\n", t.Assignee.Email)
- }
- if t.Status != nil {
- fmt.Printf("Status: %s (%s)\n", t.Status.Label, t.Status.Code)
- }
- if t.DueDate != nil {
- fmt.Printf("Due Date: %s\n", *t.DueDate)
- }
- fmt.Printf("Priority: %s\n", t.Priority)
- fmt.Printf("Created At: %s\n", t.CreatedAt)
- fmt.Printf("Updated At: %s\n", t.UpdatedAt)
- return nil
- }
- func taskCreate(ctx context.Context, cmd *cli.Command) error {
- cfg, err := config.Load()
- if err != nil {
- return err
- }
- if err := RequireAuth(cfg); err != nil {
- return err
- }
- title := cmd.String("title")
- content := cmd.String("content")
- createdBy := cmd.String("created-by")
- assignee := cmd.String("assignee")
- status := cmd.String("status")
- dueDate := cmd.String("due-date")
- priority := cmd.String("priority")
- if title == "" {
- prompt := &survey.Input{Message: "Title:"}
- if err := survey.AskOne(prompt, &title, survey.WithValidator(survey.Required)); err != nil {
- return err
- }
- }
- if content == "" {
- prompt := &survey.Multiline{Message: "Content:"}
- if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil {
- return err
- }
- }
- if createdBy == "" {
- prompt := &survey.Input{Message: "Creator user ID:"}
- if err := survey.AskOne(prompt, &createdBy, survey.WithValidator(survey.Required)); err != nil {
- return err
- }
- }
- if priority == "" {
- prompt := &survey.Select{
- Message: "Priority:",
- Options: []string{"low", "medium", "high"},
- Default: "medium",
- }
- if err := survey.AskOne(prompt, &priority); err != nil {
- return err
- }
- }
- c := client.New(cfg.ServerURL)
- c.SetToken(cfg.Token)
- mutation := `mutation CreateTask($input: NewTask!) { createTask(input: $input) { id title priority createdBy { id email } assignee { id email } status { id code label } dueDate createdAt updatedAt } }`
- input := map[string]interface{}{
- "title": title,
- "content": content,
- "createdById": createdBy,
- "priority": priority,
- }
- if assignee != "" {
- input["assigneeId"] = assignee
- }
- if status != "" {
- input["statusId"] = status
- }
- if dueDate != "" {
- input["dueDate"] = dueDate
- }
- resp, err := c.Mutation(mutation, map[string]interface{}{"input": input})
- if err != nil {
- return err
- }
- var result struct {
- CreateTask *Task `json:"createTask"`
- }
- if err := json.Unmarshal(resp.Data, &result); err != nil {
- return err
- }
- if result.CreateTask == nil {
- return fmt.Errorf("failed to create task")
- }
- fmt.Printf("Task created successfully!\n")
- fmt.Printf("ID: %s\n", result.CreateTask.ID)
- fmt.Printf("Title: %s\n", result.CreateTask.Title)
- return nil
- }
- func taskUpdate(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")
- title := cmd.String("title")
- content := cmd.String("content")
- assignee := cmd.String("assignee")
- status := cmd.String("status")
- dueDate := cmd.String("due-date")
- priority := cmd.String("priority")
- if title == "" && content == "" && assignee == "" && status == "" && dueDate == "" && priority == "" {
- fmt.Println("No updates provided. Use flags to specify what to update.")
- return nil
- }
- c := client.New(cfg.ServerURL)
- c.SetToken(cfg.Token)
- input := make(map[string]interface{})
- if title != "" {
- input["title"] = title
- }
- if content != "" {
- input["content"] = content
- }
- if assignee != "" {
- input["assigneeId"] = assignee
- }
- if status != "" {
- input["statusId"] = status
- }
- if dueDate != "" {
- input["dueDate"] = dueDate
- }
- if priority != "" {
- input["priority"] = priority
- }
- mutation := `mutation UpdateTask($id: ID!, $input: UpdateTaskInput!) { updateTask(id: $id, input: $input) { id title priority status { id code label } createdAt updatedAt } }`
- resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input})
- if err != nil {
- return err
- }
- var result struct {
- UpdateTask *Task `json:"updateTask"`
- }
- if err := json.Unmarshal(resp.Data, &result); err != nil {
- return err
- }
- if result.UpdateTask == nil {
- return fmt.Errorf("task not found")
- }
- fmt.Printf("Task updated successfully!\n")
- fmt.Printf("ID: %s\n", result.UpdateTask.ID)
- fmt.Printf("Title: %s\n", result.UpdateTask.Title)
- return nil
- }
- func taskDelete(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 task %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 DeleteTask($id: ID!) { deleteTask(id: $id) }`
- resp, err := c.Mutation(mutation, map[string]interface{}{"id": id})
- if err != nil {
- return err
- }
- var result struct {
- DeleteTask bool `json:"deleteTask"`
- }
- if err := json.Unmarshal(resp.Data, &result); err != nil {
- return err
- }
- if result.DeleteTask {
- fmt.Printf("Task %s deleted successfully.\n", id)
- } else {
- fmt.Printf("Failed to delete task %s.\n", id)
- }
- return nil
- }
- func taskWatch(ctx context.Context, cmd *cli.Command) error {
- cfg, err := config.Load()
- if err != nil {
- return err
- }
- if err := RequireAuth(cfg); err != nil {
- return err
- }
- eventType := cmd.String("event")
- wsClient := client.NewWebSocketClient(cfg.ServerURL, cfg.Token)
- if err := wsClient.Connect(); err != nil {
- return fmt.Errorf("failed to connect: %w", err)
- }
- defer wsClient.Close()
- fmt.Printf("Watching for task events (type: %s)...\n", eventType)
- fmt.Println("Press Ctrl+C to stop.")
- var subscription string
- switch eventType {
- case "created":
- subscription = "subscription { taskCreated { id title priority status { id code label } createdAt } }"
- case "updated":
- subscription = "subscription { taskUpdated { id title priority status { id code label } updatedAt } }"
- case "deleted":
- subscription = "subscription { taskDeleted { id title } }"
- default:
- subscription = "subscription { taskCreated { id title priority status { id code label } createdAt } taskUpdated { id title priority status { id code label } updatedAt } taskDeleted { id title } }"
- }
- if err := wsClient.Subscribe("1", subscription, nil); err != nil {
- return fmt.Errorf("failed to subscribe: %w", err)
- }
- for {
- select {
- case msg := <-wsClient.Messages():
- fmt.Printf("Event: %s\n", string(msg))
- case err := <-wsClient.Errors():
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- case <-wsClient.Done():
- return nil
- case <-ctx.Done():
- return nil
- }
- }
- }
|