package main import ( "context" "fmt" "log" "os" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/joho/godotenv" "github.com/urfave/cli/v2" ) var pool *pgxpool.Pool var ctx = context.Background() type Task struct { Id int `json:"id"` Text string `json:"text"` Completed bool `json:"completed"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func init() { if err := godotenv.Load(); err != nil { log.Fatal("Error al cargar el archivo .env", err) } var err error pool, err = pgxpool.New(ctx, os.Getenv("PG_FROM_DB_URL")) if err != nil { log.Fatal("Unable to connect to database:", err) } if err := pool.Ping(ctx); err != nil { log.Fatal("Unable to ping database:", err) } // fmt.Println("Connected to PostgreSQL database!") } func main() { app := &cli.App{ 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{ { 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) }, }, }, } err := app.Run(os.Args) if err != nil { 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")) } }