diff --git a/backend/controllers/edit_task.go b/backend/controllers/edit_task.go index 1e65a851..f76e8467 100644 --- a/backend/controllers/edit_task.go +++ b/backend/controllers/edit_task.go @@ -120,13 +120,30 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) { job := Job{ Name: "Edit Task", Execute: func() error { - logStore.AddLog("INFO", fmt.Sprintf("Editing task ID: %s", taskUUID), uuid, "Edit Task") - err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskUUID, tags, project, start, entry, wait, end, depends, due, recur, annotations) + logStore.AddLog("INFO", fmt.Sprintf("Editing task UUID: %s", taskUUID), uuid, "Edit Task") + + err := tw.EditTaskInTaskwarrior( + uuid, + taskUUID, + email, + encryptionSecret, + description, + project, + start, + entry, + wait, + end, + due, + recur, + tags, + depends, + annotations, + ) if err != nil { - logStore.AddLog("ERROR", fmt.Sprintf("Failed to edit task ID %s: %v", taskUUID, err), uuid, "Edit Task") + logStore.AddLog("ERROR", fmt.Sprintf("Failed to edit task UUID %s: %v", taskUUID, err), uuid, "Edit Task") return err } - logStore.AddLog("INFO", fmt.Sprintf("Successfully edited task ID: %s", taskUUID), uuid, "Edit Task") + logStore.AddLog("INFO", fmt.Sprintf("Successfully edited task UUID: %s", taskUUID), uuid, "Edit Task") return nil }, } diff --git a/backend/models/request_body.go b/backend/models/request_body.go index 89126f5c..b9be6499 100644 --- a/backend/models/request_body.go +++ b/backend/models/request_body.go @@ -48,6 +48,7 @@ type EditTaskRequestBody struct { Recur string `json:"recur"` Annotations []Annotation `json:"annotations"` } + type CompleteTaskRequestBody struct { Email string `json:"email"` EncryptionSecret string `json:"encryptionSecret"` diff --git a/backend/utils/tw/edit_task.go b/backend/utils/tw/edit_task.go index 580e1df3..59a49054 100644 --- a/backend/utils/tw/edit_task.go +++ b/backend/utils/tw/edit_task.go @@ -9,10 +9,11 @@ import ( "strings" ) -func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string, depends []string, due string, recur string, annotations []models.Annotation) error { - if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil { - return fmt.Errorf("error deleting Taskwarrior data: %v", err) - } +func EditTaskInTaskwarrior( + uuid, taskUUID, email, encryptionSecret, description, project, start, entry, wait, end, due, recur string, + tags, depends []string, + annotations []models.Annotation, +) error { tempDir, err := os.MkdirTemp("", utils.SafeTempDirPrefix("taskwarrior-", email)) if err != nil { return fmt.Errorf("failed to create temporary directory: %v", err) @@ -28,104 +29,61 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st return err } - // Escape the double quotes in the description and format it - if err := utils.ExecCommand("task", taskID, "modify", description); err != nil { - return fmt.Errorf("failed to edit task: %v", err) + modifyArgs := []string{taskUUID, "modify"} + + if description != "" { + modifyArgs = append(modifyArgs, description) } - // Handle project if project != "" { - if err := utils.ExecCommand("task", taskID, "modify", "project:"+project); err != nil { - return fmt.Errorf("failed to set project %s: %v", project, err) - } + modifyArgs = append(modifyArgs, "project:"+project) } - // Handle wait date if wait != "" { - // Convert `2025-11-29` -> `2025-11-29T00:00:00` formattedWait := wait + "T00:00:00" - - if err := utils.ExecCommand("task", taskID, "modify", "wait:"+formattedWait); err != nil { - return fmt.Errorf("failed to set wait date %s: %v", formattedWait, err) - } - } - - // Handle tags - if len(tags) > 0 { - for _, tag := range tags { - if strings.HasPrefix(tag, "+") { - // Add tag - tagValue := strings.TrimPrefix(tag, "+") - if err := utils.ExecCommand("task", taskID, "modify", "+"+tagValue); err != nil { - return fmt.Errorf("failed to add tag %s: %v", tagValue, err) - } - } else if strings.HasPrefix(tag, "-") { - // Remove tag - tagValue := strings.TrimPrefix(tag, "-") - if err := utils.ExecCommand("task", taskID, "modify", "-"+tagValue); err != nil { - return fmt.Errorf("failed to remove tag %s: %v", tagValue, err) - } - } else { - // Add tag without prefix - if err := utils.ExecCommand("task", taskID, "modify", "+"+tag); err != nil { - return fmt.Errorf("failed to add tag %s: %v", tag, err) - } - } - } + modifyArgs = append(modifyArgs, "wait:"+formattedWait) } - // Handle start date if start != "" { - if err := utils.ExecCommand("task", taskID, "modify", "start:"+start); err != nil { - return fmt.Errorf("failed to set start date %s: %v", start, err) - } + modifyArgs = append(modifyArgs, "start:"+start) } - // Handle entry date if entry != "" { - if err := utils.ExecCommand("task", taskID, "modify", "entry:"+entry); err != nil { - return fmt.Errorf("failed to set entry date %s: %v", entry, err) - } + modifyArgs = append(modifyArgs, "entry:"+entry) } - // Handle end date if end != "" { - if err := utils.ExecCommand("task", taskID, "modify", "end:"+end); err != nil { - return fmt.Errorf("failed to set end date %s: %v", end, err) - } + modifyArgs = append(modifyArgs, "end:"+end) } - // Handle depends - always set to ensure clearing works - if err := utils.ExecCommand("task", taskID, "modify", "depends:"); err != nil { - return fmt.Errorf("failed to clear dependencies: %v", err) - } - if len(depends) > 0 { - dependsStr := strings.Join(depends, ",") - if err := utils.ExecCommand("task", taskID, "modify", "depends:"+dependsStr); err != nil { - return fmt.Errorf("failed to set dependencies %s: %v", dependsStr, err) - } - } + dependsStr := strings.Join(depends, ",") + modifyArgs = append(modifyArgs, "depends:"+dependsStr) - // Handle due date if due != "" { - // Convert `2025-11-29` -> `2025-11-29T00:00:00` formattedDue := due + "T00:00:00" - - if err := utils.ExecCommand("task", taskID, "modify", "due:"+formattedDue); err != nil { - return fmt.Errorf("failed to set due date %s: %v", formattedDue, err) - } + modifyArgs = append(modifyArgs, "due:"+formattedDue) } - // Handle recur - this will automatically set rtype field if recur != "" { - if err := utils.ExecCommand("task", taskID, "modify", "recur:"+recur); err != nil { - return fmt.Errorf("failed to set recur %s: %v", recur, err) + modifyArgs = append(modifyArgs, "recur:"+recur) + } + + for _, tag := range tags { + if strings.HasPrefix(tag, "+") { + modifyArgs = append(modifyArgs, tag) + } else if strings.HasPrefix(tag, "-") { + modifyArgs = append(modifyArgs, tag) + } else { + modifyArgs = append(modifyArgs, "+"+tag) } } - // Handle annotations + if err := utils.ExecCommand("task", modifyArgs...); err != nil { + return fmt.Errorf("failed to edit task: %v", err) + } + if len(annotations) >= 0 { - output, err := utils.ExecCommandForOutputInDir(tempDir, "task", taskID, "export") + output, err := utils.ExecCommandForOutputInDir(tempDir, "task", taskUUID, "export") if err == nil { var tasks []map[string]interface{} if err := json.Unmarshal(output, &tasks); err == nil && len(tasks) > 0 { @@ -133,7 +91,7 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st for _, ann := range existingAnnotations { if annMap, ok := ann.(map[string]interface{}); ok { if desc, ok := annMap["description"].(string); ok { - utils.ExecCommand("task", taskID, "denotate", desc) + utils.ExecCommand("task", taskUUID, "denotate", desc) } } } @@ -143,16 +101,16 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st for _, annotation := range annotations { if annotation.Description != "" { - if err := utils.ExecCommand("task", taskID, "annotate", annotation.Description); err != nil { + if err := utils.ExecCommand("task", taskUUID, "annotate", annotation.Description); err != nil { return fmt.Errorf("failed to add annotation %s: %v", annotation.Description, err) } } } } - // Sync Taskwarrior again if err := SyncTaskwarrior(tempDir); err != nil { return err } + return nil } diff --git a/backend/utils/tw/taskwarrior_test.go b/backend/utils/tw/taskwarrior_test.go index e980db18..9241c85e 100644 --- a/backend/utils/tw/taskwarrior_test.go +++ b/backend/utils/tw/taskwarrior_test.go @@ -25,7 +25,7 @@ func TestSyncTaskwarrior(t *testing.T) { } func TestEditTaskInATaskwarrior(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", nil, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "weekly", []models.Annotation{{Description: "test annotation"}}) + err := EditTaskInTaskwarrior("uuid", "taskuuid", "email", "encryptionSecret", "description", "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", "2025-12-01T18:30:00.000Z", "weekly", []string{}, []string{}, []models.Annotation{{Description: "test annotation"}}) if err != nil { t.Errorf("EditTaskInTaskwarrior() failed: %v", err) } else { @@ -202,7 +202,7 @@ func TestAddTaskToTaskwarriorWithWaitDateWithTags(t *testing.T) { } func TestEditTaskWithTagAddition(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "+important"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "daily", []models.Annotation{}) + err := EditTaskInTaskwarrior("uuid", "taskuuid", "email", "encryptionSecret", "description", "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", "2025-12-01T18:30:00.000Z", "daily", []string{"+urgent", "+important"}, []string{}, []models.Annotation{}) if err != nil { t.Errorf("EditTaskInTaskwarrior with tag addition failed: %v", err) } else { @@ -211,7 +211,7 @@ func TestEditTaskWithTagAddition(t *testing.T) { } func TestEditTaskWithTagRemoval(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"-work", "-lowpriority"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "monthly", []models.Annotation{}) + err := EditTaskInTaskwarrior("uuid", "taskuuid", "email", "encryptionSecret", "description", "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", "2025-12-01T18:30:00.000Z", "monthly", []string{"-work", "-lowpriority"}, []string{}, []models.Annotation{}) if err != nil { t.Errorf("EditTaskInTaskwarrior with tag removal failed: %v", err) } else { @@ -220,7 +220,7 @@ func TestEditTaskWithTagRemoval(t *testing.T) { } func TestEditTaskWithMixedTagOperations(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "-work", "normal"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "yearly", []models.Annotation{}) + err := EditTaskInTaskwarrior("uuid", "taskuuid", "email", "encryptionSecret", "description", "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", "2025-12-01T18:30:00.000Z", "yearly", []string{"+urgent", "-work", "normal"}, []string{}, []models.Annotation{}) if err != nil { t.Errorf("EditTaskInTaskwarrior with mixed tag operations failed: %v", err) } else {