| 763 | } |
| 764 | |
| 765 | func ListTasks(db *sql.DB, filter TaskFilter) ([]*Task, error) { |
| 766 | var where []string |
| 767 | var args []any |
| 768 | if filter.Status != "" { |
| 769 | where = append(where, "status = ?") |
| 770 | args = append(args, filter.Status) |
| 771 | } else if filter.ExcludeDone { |
| 772 | where = append(where, "status != 'done'") |
| 773 | } |
| 774 | if filter.Project != "" { |
| 775 | where = append(where, "project_slug = ?") |
| 776 | args = append(args, filter.Project) |
| 777 | } |
| 778 | if filter.Kind != "" { |
| 779 | where = append(where, "kind = ?") |
| 780 | args = append(args, filter.Kind) |
| 781 | } |
| 782 | if filter.PlaybookSlug != "" { |
| 783 | where = append(where, "playbook_slug = ?") |
| 784 | args = append(args, filter.PlaybookSlug) |
| 785 | } |
| 786 | if filter.Priority != "" { |
| 787 | where = append(where, "priority = ?") |
| 788 | args = append(args, filter.Priority) |
| 789 | } |
| 790 | if filter.Tag != "" { |
| 791 | where = append(where, "slug IN (SELECT task_slug FROM task_tags WHERE tag = ?)") |
| 792 | args = append(args, filter.Tag) |
| 793 | } |
| 794 | // Intersection: each tag adds its own EXISTS-style subquery, ANDed |
| 795 | // together, so a task must carry EVERY requested tag (e.g. |
| 796 | // `--tag owner:x --tag question`). |
| 797 | for _, t := range filter.Tags { |
| 798 | if t == "" { |
| 799 | continue |
| 800 | } |
| 801 | where = append(where, "slug IN (SELECT task_slug FROM task_tags WHERE tag = ?)") |
| 802 | args = append(args, t) |
| 803 | } |
| 804 | if filter.Since != "" { |
| 805 | where = append(where, "updated_at >= ?") |
| 806 | args = append(args, filter.Since) |
| 807 | } |
| 808 | if !filter.IncludeArchived { |
| 809 | where = append(where, "archived_at IS NULL") |
| 810 | } |
| 811 | q := "SELECT " + TaskCols + " FROM tasks" |
| 812 | if len(where) > 0 { |
| 813 | q += " WHERE " + strings.Join(where, " AND ") |
| 814 | } |
| 815 | q += ` ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'medium' THEN 1 WHEN 'low' THEN 2 ELSE 3 END, slug` |
| 816 | rows, err := db.Query(q, args...) |
| 817 | if err != nil { |
| 818 | return nil, fmt.Errorf("list tasks: %w", err) |
| 819 | } |
| 820 | defer rows.Close() |
| 821 | var out []*Task |
| 822 | for rows.Next() { |