1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package t
6
7import (
8 "errors"
9 "fmt"
10 "strings"
11
12 "git.secluded.site/np/cmd/shared"
13 "git.secluded.site/np/internal/db"
14 "git.secluded.site/np/internal/event"
15 "git.secluded.site/np/internal/task"
16 "github.com/spf13/cobra"
17)
18
19var aCmd = &cobra.Command{
20 Use: "a",
21 Short: "Add tasks",
22 Long: `Add one or more tasks to the session`,
23 RunE: runAddTasks,
24}
25
26func init() {
27 TCmd.AddCommand(aCmd)
28
29 aCmd.Flags().StringArrayP("title", "t", []string{}, "Task title (required, repeatable)")
30 aCmd.Flags().StringArrayP("description", "d", []string{}, "Task description (required, repeatable)")
31 _ = aCmd.MarkFlagRequired("title")
32 _ = aCmd.MarkFlagRequired("description")
33}
34
35func runAddTasks(cmd *cobra.Command, _ []string) error {
36 env, err := shared.Environment(cmd)
37 if err != nil {
38 return err
39 }
40
41 sessionDoc, found, err := shared.ActiveSession(cmd, env)
42 if err != nil {
43 return err
44 }
45 if !found {
46 return nil
47 }
48
49 titles, err := cmd.Flags().GetStringArray("title")
50 if err != nil {
51 return err
52 }
53 descriptions, err := cmd.Flags().GetStringArray("description")
54 if err != nil {
55 return err
56 }
57
58 if len(titles) != len(descriptions) {
59 _, _ = fmt.Fprintln(cmd.OutOrStdout(), env.Localizer.T("task.add.count_mismatch"))
60 return nil
61 }
62 if len(titles) == 0 {
63 _, _ = fmt.Fprintln(cmd.OutOrStdout(), env.Localizer.T("task.add.none_provided"))
64 return nil
65 }
66
67 type taskInput struct {
68 title string
69 description string
70 id string
71 }
72
73 var inputs []taskInput
74 for i := range titles {
75 title := strings.TrimSpace(titles[i])
76 description := strings.TrimSpace(descriptions[i])
77
78 if title == "" {
79 _, _ = fmt.Fprintf(cmd.OutOrStdout(), env.Localizer.T("task.add.title_empty_at"), i+1)
80 return nil
81 }
82
83 id := task.GenerateID(sessionDoc.SID, title, description)
84 inputs = append(inputs, taskInput{
85 title: title,
86 description: description,
87 id: id,
88 })
89 }
90
91 var lastUpdated task.Task
92 var addedCount int
93
94 err = env.DB.Update(cmd.Context(), func(txn *db.Txn) error {
95 taskTxn := env.TaskStore.WithTxn(txn)
96 eventTxn := env.EventStore.WithTxn(txn)
97 sessionTxn := env.SessionStore.WithTxn(txn)
98
99 for _, input := range inputs {
100 exists, err := taskTxn.Exists(sessionDoc.SID, input.id)
101 if err != nil {
102 return err
103 }
104 if exists {
105 continue
106 }
107
108 seq, err := eventTxn.LatestSequence(sessionDoc.SID)
109 if err != nil {
110 return err
111 }
112 nextSeq := seq + 1
113
114 created, err := taskTxn.Create(sessionDoc.SID, task.CreateParams{
115 ID: input.id,
116 Title: input.title,
117 Description: input.description,
118 Status: task.StatusPending,
119 CreatedSeq: nextSeq,
120 })
121 if err != nil {
122 return err
123 }
124
125 _, err = eventTxn.Append(sessionDoc.SID, event.BuildTaskAdded(shared.CommandString(), "", created))
126 if err != nil {
127 return err
128 }
129
130 lastUpdated = created
131 addedCount++
132 }
133
134 if addedCount > 0 {
135 _, err := sessionTxn.TouchAt(sessionDoc.SID, lastUpdated.UpdatedAt)
136 if err != nil {
137 return err
138 }
139 }
140
141 return nil
142 })
143 if err != nil {
144 if errors.Is(err, task.ErrEmptyTitle) {
145 _, _ = fmt.Fprintln(cmd.OutOrStdout(), env.Localizer.T("task.add.title_empty"))
146 return nil
147 }
148 return err
149 }
150
151 if addedCount == 0 {
152 return nil
153 }
154
155 if addedCount == 1 {
156 _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Added")
157 return nil
158 }
159
160 // Multiple tasks added, show full plan
161 if _, err := shared.PrintPlan(cmd, env, sessionDoc.SID); err != nil {
162 return err
163 }
164
165 out := cmd.OutOrStdout()
166 _, _ = fmt.Fprintln(out, "")
167 _, _ = fmt.Fprintf(out, env.Localizer.T("task.add.success"), addedCount)
168 _, _ = fmt.Fprintln(out, env.Localizer.T("task.add.guidance"))
169
170 return nil
171}