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) }