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
44 changes: 40 additions & 4 deletions backend/controllers/edit_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,52 @@ 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 ID: %s", taskUUID),
uuid,
"Edit Task",
)

req := models.EditTaskRequestBody{
UUID: uuid,
Email: email,
EncryptionSecret: encryptionSecret,
TaskUUID: taskUUID,
Description: description,
Project: project,
Start: start,
Entry: entry,
Wait: wait,
End: end,
Depends: depends,
Due: due,
Recur: recur,
Tags: tags,
Annotations: annotations,
}

err := tw.EditTaskInTaskwarrior(req)
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 ID %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 ID: %s", taskUUID),
uuid,
"Edit Task",
)
return nil
},
}

GlobalJobQueue.AddJob(job)
w.WriteHeader(http.StatusAccepted)

Expand Down
166 changes: 76 additions & 90 deletions backend/utils/tw/edit_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,148 +6,134 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"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 {
func EditTaskInTaskwarrior(
req models.EditTaskRequestBody,
) error {

if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil {
return fmt.Errorf("error deleting Taskwarrior data: %v", err)
}
tempDir, err := os.MkdirTemp("", "taskwarrior-"+email)

tempDir, err := os.MkdirTemp("", "taskwarrior-"+req.Email)
if err != nil {
return fmt.Errorf("failed to create temporary directory: %v", err)
}
defer os.RemoveAll(tempDir)

origin := os.Getenv("CONTAINER_ORIGIN")
if err := SetTaskwarriorConfig(tempDir, encryptionSecret, origin, uuid); err != nil {
if err := SetTaskwarriorConfig(
tempDir,
req.EncryptionSecret,
origin,
req.UUID,
); err != nil {
return err
}

if err := SyncTaskwarrior(tempDir); err != nil {
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)
args := []string{"modify", req.TaskUUID}

if req.Description != "" {
args = append(args, req.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)
}
if req.Project != "" {
args = append(args, "project:"+req.Project)
}

// Handle wait date
if wait != "" {
// Convert `2025-11-29` -> `2025-11-29T00:00:00`
formattedWait := wait + "T00:00:00"
if req.Start != "" {
args = append(args, "start:"+req.Start)
}

if err := utils.ExecCommand("task", taskID, "modify", "wait:"+formattedWait); err != nil {
return fmt.Errorf("failed to set wait date %s: %v", formattedWait, err)
}
if req.Entry != "" {
args = append(args, "entry:"+req.Entry)
}

// 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)
}
}
}
if req.Wait != "" {
formattedWait := req.Wait + "T00:00:00"
args = append(args, "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)
}
if req.End != "" {
args = append(args, "end:"+req.End)
}

// 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)
}
if len(req.Depends) > 0 {
args = append(args, "depends:"+strings.Join(req.Depends, ","))
}

if req.Due != "" {
args = append(args, "due:"+req.Due)
}

// 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)
if req.Recur != "" {
args = append(args, "recur:"+req.Recur)
}

for _, tag := range req.Tags {
if strings.HasPrefix(tag, "+") || strings.HasPrefix(tag, "-") {
args = append(args, tag)
} else if tag != "" {
args = append(args, "+"+tag)
}
}

// Handle depends - always set to ensure clearing works
dependsStr := strings.Join(depends, ",")
if err := utils.ExecCommand("task", taskID, "modify", "depends:"+dependsStr); err != nil {
return fmt.Errorf("failed to set depends %s: %v", dependsStr, err)
if err := utils.ExecCommandInDir(tempDir, "task", args...); err != nil {
return fmt.Errorf("failed to edit task %s: %v", req.TaskUUID, err)
}

// Handle due date
if due != "" {
// Convert `2025-11-29` -> `2025-11-29T00:00:00`
formattedDue := due + "T00:00:00"
if len(req.Annotations) > 0 {
cmd := exec.Command("task", req.TaskUUID, "export")
cmd.Dir = tempDir

if err := utils.ExecCommand("task", taskID, "modify", "due:"+formattedDue); err != nil {
return fmt.Errorf("failed to set due date %s: %v", formattedDue, err)
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to get export output: %v", err)
}
}

// 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)
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start export command: %v", err)
}
}

// Handle annotations
if len(annotations) >= 0 {
output, err := utils.ExecCommandForOutputInDir(tempDir, "task", taskID, "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)
}
}
}
}
}
var tasks []map[string]interface{}
decoder := json.NewDecoder(stdout)
if err := decoder.Decode(&tasks); err != nil || len(tasks) == 0 {
return fmt.Errorf("invalid export output for annotations")
}

for _, annotation := range annotations {
if err := cmd.Wait(); err != nil {
return fmt.Errorf("export command failed: %v", err)
}

for _, annotation := range req.Annotations {
if annotation.Description != "" {
if err := utils.ExecCommand("task", taskID, "annotate", annotation.Description); err != nil {
return fmt.Errorf("failed to add annotation %s: %v", annotation.Description, err)
if err := utils.ExecCommandInDir(
tempDir,
"task",
req.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
}
Loading