feat: add due date filters for list command
- --due: show only nodes with a due date set - --due-within N: show only nodes due within N days (includes overdue) Implemented in service layer with post-fetch filtering, threaded through API client and server, and exposed via CLI flags. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ import (
|
|||||||
|
|
||||||
var lTags, lRels []string
|
var lTags, lRels []string
|
||||||
var lStatus, lPrio, lType, lNamespace, lAssignee, lMention string
|
var lStatus, lPrio, lType, lNamespace, lAssignee, lMention string
|
||||||
|
var lDue bool
|
||||||
|
var lDueWithin int
|
||||||
|
|
||||||
var listCmd = &cobra.Command{
|
var listCmd = &cobra.Command{
|
||||||
Use: "list", Short: "List nodes",
|
Use: "list", Short: "List nodes",
|
||||||
@@ -22,6 +24,11 @@ var listCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var filter service.ListFilter
|
var filter service.ListFilter
|
||||||
|
filter.HasDueDate = lDue
|
||||||
|
if lDueWithin >= 0 {
|
||||||
|
n := lDueWithin
|
||||||
|
filter.DueWithin = &n
|
||||||
|
}
|
||||||
|
|
||||||
// --tag is an alias for a label filter with no target.
|
// --tag is an alias for a label filter with no target.
|
||||||
for _, tag := range lTags {
|
for _, tag := range lTags {
|
||||||
@@ -76,4 +83,6 @@ func init() {
|
|||||||
f.StringVar(&lNamespace, "namespace", "", "filter by namespace")
|
f.StringVar(&lNamespace, "namespace", "", "filter by namespace")
|
||||||
f.StringVar(&lAssignee, "assignee", "", "filter by assignee")
|
f.StringVar(&lAssignee, "assignee", "", "filter by assignee")
|
||||||
f.StringVar(&lMention, "mention", "", "filter by mention")
|
f.StringVar(&lMention, "mention", "", "filter by mention")
|
||||||
|
f.BoolVar(&lDue, "due", false, "filter to nodes with a due date")
|
||||||
|
f.IntVar(&lDueWithin, "due-within", -1, "filter to nodes due within N days (includes overdue)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"axolotl/store"
|
"axolotl/store"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,6 +85,14 @@ func (s *server) listNodes(w http.ResponseWriter, r *http.Request) {
|
|||||||
if v := q.Get("mention"); v != "" {
|
if v := q.Get("mention"); v != "" {
|
||||||
filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelMentions, Target: v})
|
filter.Rels = append(filter.Rels, service.RelInput{Type: models.RelMentions, Target: v})
|
||||||
}
|
}
|
||||||
|
if q.Get("has_due_date") == "true" {
|
||||||
|
filter.HasDueDate = true
|
||||||
|
}
|
||||||
|
if v := q.Get("due_within"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil {
|
||||||
|
filter.DueWithin = &n
|
||||||
|
}
|
||||||
|
}
|
||||||
nodes, err := svc.List(filter)
|
nodes, err := svc.List(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, http.StatusInternalServerError, err.Error())
|
writeError(w, http.StatusInternalServerError, err.Error())
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiClient struct {
|
type apiClient struct {
|
||||||
@@ -78,6 +79,12 @@ func (c *apiClient) List(filter ListFilter) ([]*models.Node, error) {
|
|||||||
q.Add("rel", string(r.Type)+":"+r.Target)
|
q.Add("rel", string(r.Type)+":"+r.Target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if filter.HasDueDate {
|
||||||
|
q.Set("has_due_date", "true")
|
||||||
|
}
|
||||||
|
if filter.DueWithin != nil {
|
||||||
|
q.Set("due_within", strconv.Itoa(*filter.DueWithin))
|
||||||
|
}
|
||||||
path := "/nodes"
|
path := "/nodes"
|
||||||
if len(q) > 0 {
|
if len(q) > 0 {
|
||||||
path += "?" + q.Encode()
|
path += "?" + q.Encode()
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ type UpdateInput struct {
|
|||||||
// Tag filters (Target == "") match by rel_name prefix.
|
// Tag filters (Target == "") match by rel_name prefix.
|
||||||
// Edge filters (Target != "") are resolved to node IDs.
|
// Edge filters (Target != "") are resolved to node IDs.
|
||||||
type ListFilter struct {
|
type ListFilter struct {
|
||||||
Rels []RelInput
|
Rels []RelInput
|
||||||
|
HasDueDate bool // when true, only return nodes that have a due date set
|
||||||
|
DueWithin *int // when non-nil, only return nodes due within this many days (includes overdue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RelInput is a typed, directed rel with a target that may be a name or node ID.
|
// RelInput is a typed, directed rel with a target that may be a name or node ID.
|
||||||
|
|||||||
@@ -234,6 +234,26 @@ func (s *nodeServiceImpl) List(filter ListFilter) ([]*models.Node, error) {
|
|||||||
result = append(result, n)
|
result = append(result, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filter.HasDueDate || filter.DueWithin != nil {
|
||||||
|
now := time.Now().UTC().Truncate(24 * time.Hour)
|
||||||
|
filtered := result[:0]
|
||||||
|
for _, n := range result {
|
||||||
|
if n.DueDate == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter.DueWithin != nil {
|
||||||
|
due := n.DueDate.UTC().Truncate(24 * time.Hour)
|
||||||
|
cutoff := now.AddDate(0, 0, *filter.DueWithin)
|
||||||
|
if due.After(cutoff) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filtered = append(filtered, n)
|
||||||
|
}
|
||||||
|
result = filtered
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user