checkvist-api/tasks.go
Oliver Jakoubek 3aa2a284de Implement Task operations
Add tasks.go with TaskService for full task CRUD:
- client.Tasks(checklistID) returns TaskService
- List(ctx) - get all tasks in checklist
- Get(ctx, taskID) - get single task with parent hierarchy
- Create(ctx, builder) - create task using TaskBuilder
- Update(ctx, taskID, req) - update task properties
- Delete(ctx, taskID) - permanently delete task
- Close(ctx, taskID) - mark task as completed
- Reopen(ctx, taskID) - reopen closed/invalidated task
- Invalidate(ctx, taskID) - mark task as invalidated (strikethrough)

TaskBuilder fluent interface for task creation:
- NewTask(content) - create builder
- WithParent(id) - set parent for subtask
- WithPosition(pos) - set position in list
- WithDueDate(due) - set due date using DueDate type
- WithPriority(level) - set priority (0=normal, 1=highest, 2=high)
- WithTags(tags...) - set tags

Automatic DueDate parsing from DueDateRaw (ISO 8601 format).

Closes checkvist-api-rl9
2026-01-14 13:39:37 +01:00

223 lines
5.7 KiB
Go

package checkvist
import (
"context"
"fmt"
"time"
)
// tasks.go contains the TaskService for CRUD operations on tasks within a checklist.
// TaskService provides operations on tasks within a specific checklist.
type TaskService struct {
client *Client
checklistID int
}
// Tasks returns a TaskService for performing task operations on the specified checklist.
func (c *Client) Tasks(checklistID int) *TaskService {
return &TaskService{
client: c,
checklistID: checklistID,
}
}
// List returns all tasks in the checklist.
func (s *TaskService) List(ctx context.Context) ([]Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks.json", s.checklistID)
var tasks []Task
if err := s.client.doGet(ctx, path, &tasks); err != nil {
return nil, err
}
// Parse due dates
for i := range tasks {
parseDueDate(&tasks[i])
}
return tasks, nil
}
// Get returns a single task by ID, including its parent hierarchy.
func (s *TaskService) Get(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d.json", s.checklistID, taskID)
var task Task
if err := s.client.doGet(ctx, path, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// CreateTaskRequest represents the request body for creating a task.
type CreateTaskRequest struct {
Content string `json:"content"`
ParentID int `json:"parent_id,omitempty"`
Position int `json:"position,omitempty"`
Due string `json:"due,omitempty"`
Priority int `json:"priority,omitempty"`
Tags string `json:"tags,omitempty"`
}
// TaskBuilder provides a fluent interface for building task creation requests.
type TaskBuilder struct {
content string
parentID int
position int
due string
priority int
tags []string
}
// NewTask creates a new TaskBuilder with the given content.
func NewTask(content string) *TaskBuilder {
return &TaskBuilder{content: content}
}
// WithParent sets the parent task ID for creating a subtask.
func (b *TaskBuilder) WithParent(parentID int) *TaskBuilder {
b.parentID = parentID
return b
}
// WithPosition sets the position of the task within its siblings.
func (b *TaskBuilder) WithPosition(position int) *TaskBuilder {
b.position = position
return b
}
// WithDueDate sets the due date using a DueDate value.
func (b *TaskBuilder) WithDueDate(due DueDate) *TaskBuilder {
b.due = due.String()
return b
}
// WithPriority sets the priority level (1 = highest, 2 = high, 0 = normal).
func (b *TaskBuilder) WithPriority(priority int) *TaskBuilder {
b.priority = priority
return b
}
// WithTags sets the tags for the task.
func (b *TaskBuilder) WithTags(tags ...string) *TaskBuilder {
b.tags = tags
return b
}
// build converts the TaskBuilder to a CreateTaskRequest.
func (b *TaskBuilder) build() CreateTaskRequest {
req := CreateTaskRequest{
Content: b.content,
ParentID: b.parentID,
Position: b.position,
Due: b.due,
Priority: b.priority,
}
if len(b.tags) > 0 {
for i, tag := range b.tags {
if i > 0 {
req.Tags += ", "
}
req.Tags += tag
}
}
return req
}
// Create creates a new task using a TaskBuilder.
func (s *TaskService) Create(ctx context.Context, builder *TaskBuilder) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks.json", s.checklistID)
body := builder.build()
var task Task
if err := s.client.doPost(ctx, path, body, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// UpdateTaskRequest represents the request body for updating a task.
type UpdateTaskRequest struct {
Content *string `json:"content,omitempty"`
ParentID *int `json:"parent_id,omitempty"`
Position *int `json:"position,omitempty"`
Due *string `json:"due,omitempty"`
Priority *int `json:"priority,omitempty"`
Tags *string `json:"tags,omitempty"`
}
// Update updates an existing task.
func (s *TaskService) Update(ctx context.Context, taskID int, req UpdateTaskRequest) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d.json", s.checklistID, taskID)
var task Task
if err := s.client.doPut(ctx, path, req, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// Delete permanently deletes a task.
func (s *TaskService) Delete(ctx context.Context, taskID int) error {
path := fmt.Sprintf("/checklists/%d/tasks/%d.json", s.checklistID, taskID)
return s.client.doDelete(ctx, path)
}
// Close marks a task as completed.
func (s *TaskService) Close(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/close.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// Reopen reopens a closed or invalidated task.
func (s *TaskService) Reopen(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/reopen.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// Invalidate marks a task as invalidated (strikethrough).
func (s *TaskService) Invalidate(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/invalidate.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
}
// parseDueDate attempts to parse the DueDateRaw string into a time.Time.
// It supports ISO 8601 date format (YYYY-MM-DD).
func parseDueDate(task *Task) {
if task.DueDateRaw == "" {
return
}
// Try to parse as ISO date
if t, err := time.Parse("2006-01-02", task.DueDateRaw); err == nil {
task.DueDate = &t
}
}