From ace18923b9605cce90d3f1fdfdd71c26c773d0b9 Mon Sep 17 00:00:00 2001 From: Kylesoda <249518290+kylesoda@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:25:50 -0500 Subject: [PATCH] Implement task management commands: add, list, done, undo, remove --- main.go | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 306 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index a384a34..c8cd0a0 100644 --- a/main.go +++ b/main.go @@ -38,15 +38,118 @@ func init() { log.Fatal("Unable to ping database:", err) } - fmt.Println("Connected to PostgreSQL database!") + // fmt.Println("Connected to PostgreSQL database!") } func main() { app := &cli.App{ - Name: "Go Todo App", - Usage: "A simple CLI program to manage your tasks", + Name: "gotodo", + Usage: "A simple CLI program to manage your tasks", + Action: func(c *cli.Context) error { + tasks, err := getPendingTasks() + if err != nil { + return err + } + + fmt.Println("Pending Tasks:") + printTasks(tasks) + return nil + }, Commands: []*cli.Command{ - // We'll add commands here + { + Name: "add", + Aliases: []string{"a"}, + Usage: "Add a new task", + Action: func(c *cli.Context) error { + text := c.Args().First() + if text == "" { + return fmt.Errorf("task text cannot be empty") + } + return createTask(text) + }, + }, + { + Name: "list", + Aliases: []string{"ls"}, + Usage: "List tasks with filters", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "all", Aliases: []string{"a"}, Usage: "List all tasks"}, + &cli.BoolFlag{Name: "completed", Aliases: []string{"c"}, Usage: "List only completed tasks"}, + &cli.BoolFlag{Name: "pending", Aliases: []string{"p"}, Usage: "List only pending tasks"}, + }, + Action: func(c *cli.Context) error { + var tasks []Task + var err error + title := "Pending Tasks:" + + if c.Bool("all") { + tasks, err = getAllTasks() + title = "All Tasks:" + } else if c.Bool("completed") { + tasks, err = getCompletedTasks() + title = "Completed Tasks:" + } else { + tasks, err = getPendingTasks() + title = "Pending Tasks:" + } + + if err != nil { + return err + } + + fmt.Println(title) + printTasks(tasks) + return nil + }, + }, + { + Name: "done", + Aliases: []string{"do"}, + Usage: "Mark a task as completed", + Action: func(c *cli.Context) error { + idStr := c.Args().First() + if idStr == "" { + return fmt.Errorf("task ID required") + } + var id int + if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil { + return fmt.Errorf("invalid task ID: %s", idStr) + } + return completeTask(id) + }, + }, + { + Name: "undo", + Aliases: []string{"ud"}, + Usage: "Mark a task as not completed", + Action: func(c *cli.Context) error { + idStr := c.Args().First() + if idStr == "" { + return fmt.Errorf("task ID required") + } + var id int + if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil { + return fmt.Errorf("invalid task ID: %s", idStr) + } + return undoTask(id) + }, + }, + { + Name: "remove", + Aliases: []string{"rm"}, + Usage: "Remove a task", + Action: func(c *cli.Context) error { + idStr := c.Args().First() + if idStr == "" { + return fmt.Errorf("task ID required") + } + var id int + if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil { + return fmt.Errorf("invalid task ID: %s", idStr) + } + return deleteTask(id) + }, + }, }, } @@ -55,3 +158,202 @@ func main() { log.Fatal(err) } } + +func createTask(text string) error { + sql := ` + INSERT INTO tasks (text, completed) + VALUES ($1, $2) + RETURNING id + ` + + var id int + err := pool.QueryRow(ctx, sql, text, false).Scan(&id) + if err != nil { + return fmt.Errorf("error creating task: %w", err) + } + + fmt.Printf("Created task with ID: %d\n", id) + return nil +} + +func getAllTasks() ([]Task, error) { + sql := ` + SELECT id, text, completed, created_at, updated_at + FROM tasks + ORDER BY created_at DESC + ` + + rows, err := pool.Query(ctx, sql) + if err != nil { + return nil, fmt.Errorf("error querying tasks: %w", err) + } + defer rows.Close() + + var tasks []Task + for rows.Next() { + var task Task + err := rows.Scan( + &task.Id, + &task.Text, + &task.Completed, + &task.CreatedAt, + &task.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("error scanning task row: %w", err) + } + tasks = append(tasks, task) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating task rows: %w", err) + } + + return tasks, nil +} + +func completeTask(id int) error { + sql := ` + UPDATE tasks + SET completed = true, updated_at = NOW() + WHERE id = $1 + ` + + commandTag, err := pool.Exec(ctx, sql, id) + if err != nil { + return fmt.Errorf("error completing task: %w", err) + } + + if commandTag.RowsAffected() == 0 { + return fmt.Errorf("no task found with id %d", id) + } + + return nil +} + +func undoTask(id int) error { + sql := ` + UPDATE tasks + SET completed = false, updated_at = NOW() + WHERE id = $1 + ` + + commandTag, err := pool.Exec(ctx, sql, id) + if err != nil { + return fmt.Errorf("error updating task: %w", err) + } + + if commandTag.RowsAffected() == 0 { + return fmt.Errorf("no task found with id %d", id) + } + + return nil +} + +func deleteTask(id int) error { + sql := `DELETE FROM tasks WHERE id = $1` + + commandTag, err := pool.Exec(ctx, sql, id) + if err != nil { + return fmt.Errorf("error deleting task: %w", err) + } + + if commandTag.RowsAffected() == 0 { + return fmt.Errorf("no task found with id %d", id) + } + + return nil +} + +func getPendingTasks() ([]Task, error) { + sql := ` + SELECT id, text, completed, created_at, updated_at + FROM tasks + WHERE completed = false + ORDER BY created_at DESC + ` + + rows, err := pool.Query(ctx, sql) + if err != nil { + return nil, fmt.Errorf("error querying pending tasks: %w", err) + } + defer rows.Close() + + var tasks []Task + for rows.Next() { + var task Task + err := rows.Scan( + &task.Id, + &task.Text, + &task.Completed, + &task.CreatedAt, + &task.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("error scanning task row: %w", err) + } + tasks = append(tasks, task) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating task rows: %w", err) + } + + return tasks, nil +} + +func getCompletedTasks() ([]Task, error) { + sql := ` + SELECT id, text, completed, created_at, updated_at + FROM tasks + WHERE completed = true + ORDER BY created_at DESC + ` + + rows, err := pool.Query(ctx, sql) + if err != nil { + return nil, fmt.Errorf("error querying completed tasks: %w", err) + } + defer rows.Close() + + var tasks []Task + for rows.Next() { + var task Task + err := rows.Scan( + &task.Id, + &task.Text, + &task.Completed, + &task.CreatedAt, + &task.UpdatedAt, + ) + if err != nil { + return nil, fmt.Errorf("error scanning task row: %w", err) + } + tasks = append(tasks, task) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating task rows: %w", err) + } + + return tasks, nil +} + +func printTasks(tasks []Task) { + if len(tasks) == 0 { + fmt.Println("No tasks found") + return + } + + for _, task := range tasks { + status := "[ ]" + if task.Completed { + status = "[✓]" + } + fmt.Printf("%d. %s %s (Created: %s)\n", + task.Id, + status, + task.Text, + task.CreatedAt.Format("2006-01-02 15:04:05")) + } +}