Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions backend/controllers/edit_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}
Expand Down
1 change: 1 addition & 0 deletions backend/models/request_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
112 changes: 35 additions & 77 deletions backend/utils/tw/edit_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -28,112 +29,69 @@ 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 {
if existingAnnotations, ok := tasks[0]["annotations"].([]interface{}); ok {
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)
}
}
}
Expand All @@ -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
}
8 changes: 4 additions & 4 deletions backend/utils/tw/taskwarrior_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down