Files
task-master/internal/daemon/client/stores.go
T
skrueckenandCursor 2dbf0918de Add local JSON daemon and route TUI/CLI through it.
A per-DataDir background process owns ticket JSON, history, and sync so
multiple TUI instances stay consistent; MCP token APIs are ready for phase 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-01 16:32:13 +01:00

160 lines
4.6 KiB
Go

package client
import (
"fmt"
"net/http"
"net/url"
"time"
"task/internal/project"
"task/internal/ticket"
)
// TicketStore implements ticket.Store against the daemon (reads + MarkSynced).
// Mutations used by the TUI go through Executor; Create/Update/Delete/Move here
// call the daemon mutation endpoints (with undo) for completeness.
type TicketStore struct{ c *Client }
func (c *Client) TicketStore() *TicketStore { return &TicketStore{c: c} }
func (s *TicketStore) List(projectID string) ([]ticket.Ticket, error) {
var out []ticket.Ticket
q := url.Values{"projectId": {projectID}}.Encode()
err := s.c.do(http.MethodGet, "/v1/tickets?"+q, nil, &out)
if out == nil {
out = []ticket.Ticket{}
}
return out, err
}
func (s *TicketStore) Get(id string) (ticket.Ticket, error) {
var out ticket.Ticket
err := s.c.do(http.MethodGet, "/v1/tickets/"+url.PathEscape(id), nil, &out)
return out, err
}
func (s *TicketStore) Create(t ticket.Ticket) (ticket.Ticket, error) {
var out ticket.Ticket
err := s.c.do(http.MethodPost, "/v1/tickets", t, &out)
return out, err
}
func (s *TicketStore) Update(t ticket.Ticket) error {
return s.c.do(http.MethodPatch, "/v1/tickets/"+url.PathEscape(t.ID), t, nil)
}
func (s *TicketStore) MarkSynced(id string) error {
return fmt.Errorf("MarkSynced is daemon-internal (use sync endpoints)")
}
func (s *TicketStore) Move(id, toProjectID string) (ticket.Ticket, error) {
var out ticket.Ticket
err := s.c.do(http.MethodPost, "/v1/tickets/"+url.PathEscape(id)+"/move", map[string]string{
"toProjectId": toProjectID,
}, &out)
return out, err
}
func (s *TicketStore) Delete(id string) error {
return s.c.do(http.MethodDelete, "/v1/tickets/"+url.PathEscape(id), nil, nil)
}
// TimeStore implements ticket.TimeStore.
type TimeStore struct{ c *Client }
func (c *Client) TimeStore() *TimeStore { return &TimeStore{c: c} }
func (s *TimeStore) List() ([]ticket.TimeEntry, error) {
var out []ticket.TimeEntry
err := s.c.do(http.MethodGet, "/v1/time", nil, &out)
if out == nil {
out = []ticket.TimeEntry{}
}
return out, err
}
func (s *TimeStore) ListByTicket(ticketID string) ([]ticket.TimeEntry, error) {
var out []ticket.TimeEntry
err := s.c.do(http.MethodGet, "/v1/time/by-ticket/"+url.PathEscape(ticketID), nil, &out)
if out == nil {
out = []ticket.TimeEntry{}
}
return out, err
}
func (s *TimeStore) Create(e ticket.TimeEntry) (ticket.TimeEntry, error) {
var out ticket.TimeEntry
err := s.c.do(http.MethodPost, "/v1/time", e, &out)
return out, err
}
func (s *TimeStore) Update(e ticket.TimeEntry) error {
return s.c.do(http.MethodPatch, "/v1/time/"+url.PathEscape(e.ID), e, nil)
}
func (s *TimeStore) Delete(id string) error {
return s.c.do(http.MethodDelete, "/v1/time/"+url.PathEscape(id), nil, nil)
}
func (s *TimeStore) DeleteByProject(projectID string) error {
// Composite delete is done server-side on project delete; not exposed alone.
return fmt.Errorf("DeleteByProject is daemon-internal")
}
func (s *TimeStore) RetargetTicket(ticketID, projectID string) error {
return fmt.Errorf("RetargetTicket is daemon-internal")
}
func (s *TimeStore) SumByTicket(ticketID string) (time.Duration, error) {
var out struct {
Duration int64 `json:"duration"`
}
err := s.c.do(http.MethodGet, "/v1/time/sum/"+url.PathEscape(ticketID), nil, &out)
return time.Duration(out.Duration), err
}
func (s *TimeStore) LoadActiveTimer() (*ticket.ActiveTimer, error) {
var out *ticket.ActiveTimer
err := s.c.do(http.MethodGet, "/v1/timer", nil, &out)
return out, err
}
func (s *TimeStore) SaveActiveTimer(t *ticket.ActiveTimer) error {
if t == nil {
return s.c.do(http.MethodPut, "/v1/timer", map[string]any{}, nil)
}
return s.c.do(http.MethodPut, "/v1/timer", t, nil)
}
// ProjectStore implements project.Store.
type ProjectStore struct{ c *Client }
func (c *Client) ProjectStore() *ProjectStore { return &ProjectStore{c: c} }
func (s *ProjectStore) List() ([]project.Project, error) {
var out []project.Project
err := s.c.do(http.MethodGet, "/v1/projects", nil, &out)
if out == nil {
out = []project.Project{}
}
return out, err
}
func (s *ProjectStore) Ensure(id string) error {
return s.c.do(http.MethodPost, "/v1/projects", map[string]string{"id": id}, nil)
}
func (s *ProjectStore) Delete(id string) error {
return s.c.do(http.MethodDelete, "/v1/projects/"+url.PathEscape(id), nil, nil)
}
func (s *ProjectStore) LoadMeta(id string) (project.Meta, error) {
var out project.Meta
err := s.c.do(http.MethodGet, "/v1/projects/"+url.PathEscape(id)+"/meta", nil, &out)
return out, err
}
func (s *ProjectStore) SaveMeta(id string, meta project.Meta) error {
return s.c.do(http.MethodPut, "/v1/projects/"+url.PathEscape(id)+"/meta", meta, nil)
}