summaryrefslogtreecommitdiff
path: root/internal/api/todoist.go
diff options
context:
space:
mode:
authorDoot Agent <agent@doot.local>2026-07-06 00:00:59 +0000
committerDoot Agent <agent@doot.local>2026-07-06 00:00:59 +0000
commit945c34590677e161a711ccaff0e1077197b1b178 (patch)
treedf26e8ba5a369b3323649c2fab95bd8e7a49b58e /internal/api/todoist.go
parentcd14604bbef17f696475cf8737f1e41c9fa2dd06 (diff)
feat: remove Todoist integration entirely
Native tasks (native_tasks table) fully replace Todoist. All Todoist API code, store functions, handlers, routes, templates, and tests have been removed. Migration 021 drops the now-unused tasks cache table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/todoist.go')
-rw-r--r--internal/api/todoist.go256
1 files changed, 0 insertions, 256 deletions
diff --git a/internal/api/todoist.go b/internal/api/todoist.go
deleted file mode 100644
index 2454233..0000000
--- a/internal/api/todoist.go
+++ /dev/null
@@ -1,256 +0,0 @@
-package api
-
-import (
- "context"
- "fmt"
- "strings"
- "time"
-
- "task-dashboard/internal/config"
- "task-dashboard/internal/models"
-)
-
-const (
- todoistBaseURL = "https://api.todoist.com/api/v1"
-)
-
-// TodoistClient handles interactions with the Todoist API
-type TodoistClient struct {
- BaseClient
- apiKey string
-}
-
-// NewTodoistClient creates a new Todoist API client
-func NewTodoistClient(apiKey string) *TodoistClient {
- return &TodoistClient{
- BaseClient: NewBaseClient(todoistBaseURL),
- apiKey: apiKey,
- }
-}
-
-func (c *TodoistClient) authHeaders() map[string]string {
- return map[string]string{"Authorization": "Bearer " + c.apiKey}
-}
-
-// todoistTaskResponse represents the API response structure
-type todoistTaskResponse struct {
- ID string `json:"id"`
- Content string `json:"content"`
- Description string `json:"description"`
- ProjectID string `json:"project_id"`
- Priority int `json:"priority"`
- Labels []string `json:"labels"`
- Due *dueInfo `json:"due"`
- URL string `json:"url"`
- CreatedAt string `json:"created_at"`
-}
-
-// todoistProjectResponse represents the project API response
-type todoistProjectResponse struct {
- ID string `json:"id"`
- Name string `json:"name"`
-}
-
-
-// todoistTasksPage represents the paginated response from the Todoist REST API v1
-type todoistTasksPage struct {
- Results []todoistTaskResponse `json:"results"`
- NextCursor *string `json:"next_cursor"`
-}
-
-// GetTasks fetches all active tasks from Todoist
-func (c *TodoistClient) GetTasks(ctx context.Context) ([]models.Task, error) {
- var allTasks []todoistTaskResponse
- cursor := ""
- for {
- path := "/tasks"
- if cursor != "" {
- path = "/tasks?cursor=" + cursor
- }
- var page todoistTasksPage
- if err := c.Get(ctx, path, c.authHeaders(), &page); err != nil {
- return nil, fmt.Errorf("failed to fetch tasks: %w", err)
- }
- allTasks = append(allTasks, page.Results...)
- if page.NextCursor == nil || *page.NextCursor == "" {
- break
- }
- cursor = *page.NextCursor
- }
- apiTasks := allTasks
-
- // Fetch projects to get project names
- projects, err := c.GetProjects(ctx)
- projectMap := make(map[string]string)
- if err == nil {
- for _, proj := range projects {
- projectMap[proj.ID] = proj.Name
- }
- }
-
- // Convert to our model
- tasks := make([]models.Task, 0, len(apiTasks))
- for _, apiTask := range apiTasks {
- task := models.Task{
- ID: apiTask.ID,
- Content: apiTask.Content,
- Description: apiTask.Description,
- ProjectID: apiTask.ProjectID,
- ProjectName: projectMap[apiTask.ProjectID],
- Priority: apiTask.Priority,
- Completed: false,
- Labels: apiTask.Labels,
- URL: apiTask.URL,
- }
-
- if createdAt, err := time.Parse(time.RFC3339, apiTask.CreatedAt); err == nil {
- task.CreatedAt = createdAt
- }
-
- task.DueDate = parseDueDate(apiTask.Due)
- if apiTask.Due != nil {
- task.IsRecurring = apiTask.Due.IsRecurring
- }
- tasks = append(tasks, task)
- }
-
- return tasks, nil
-}
-
-// todoistProjectsPage represents the paginated response for projects
-type todoistProjectsPage struct {
- Results []todoistProjectResponse `json:"results"`
- NextCursor *string `json:"next_cursor"`
-}
-
-// GetProjects fetches all projects
-func (c *TodoistClient) GetProjects(ctx context.Context) ([]models.Project, error) {
- var allProjects []todoistProjectResponse
- cursor := ""
- for {
- path := "/projects"
- if cursor != "" {
- path = "/projects?cursor=" + cursor
- }
- var page todoistProjectsPage
- if err := c.Get(ctx, path, c.authHeaders(), &page); err != nil {
- return nil, fmt.Errorf("failed to fetch projects: %w", err)
- }
- allProjects = append(allProjects, page.Results...)
- if page.NextCursor == nil || *page.NextCursor == "" {
- break
- }
- cursor = *page.NextCursor
- }
- apiProjects := allProjects
-
- projects := make([]models.Project, 0, len(apiProjects))
- for _, apiProj := range apiProjects {
- projects = append(projects, models.Project{
- ID: apiProj.ID,
- Name: apiProj.Name,
- })
- }
-
- return projects, nil
-}
-
-// CreateTask creates a new task in Todoist
-func (c *TodoistClient) CreateTask(ctx context.Context, content, projectID string, dueDate *time.Time, priority int) (*models.Task, error) {
- payload := map[string]interface{}{"content": content}
- if projectID != "" {
- payload["project_id"] = projectID
- }
- if dueDate != nil {
- payload["due_date"] = dueDate.Format("2006-01-02")
- }
- if priority > 0 {
- payload["priority"] = priority
- }
-
- var apiTask todoistTaskResponse
- if err := c.Post(ctx, "/tasks", c.authHeaders(), payload, &apiTask); err != nil {
- return nil, fmt.Errorf("failed to create task: %w", err)
- }
-
- task := &models.Task{
- ID: apiTask.ID,
- Content: apiTask.Content,
- Description: apiTask.Description,
- ProjectID: apiTask.ProjectID,
- Priority: apiTask.Priority,
- Completed: false,
- Labels: apiTask.Labels,
- URL: apiTask.URL,
- }
-
- if createdAt, err := time.Parse(time.RFC3339, apiTask.CreatedAt); err == nil {
- task.CreatedAt = createdAt
- }
-
- task.DueDate = parseDueDate(apiTask.Due)
- return task, nil
-}
-
-// UpdateTask updates a task with the specified changes
-func (c *TodoistClient) UpdateTask(ctx context.Context, taskID string, updates map[string]interface{}) error {
- path := fmt.Sprintf("/tasks/%s", taskID)
- if err := c.Post(ctx, path, c.authHeaders(), updates, nil); err != nil {
- return fmt.Errorf("failed to update task: %w", err)
- }
- return nil
-}
-
-// CompleteTask marks a task as complete in Todoist
-func (c *TodoistClient) CompleteTask(ctx context.Context, taskID string) error {
- path := fmt.Sprintf("/tasks/%s/close", taskID)
- if err := c.PostEmpty(ctx, path, c.authHeaders()); err != nil {
- return fmt.Errorf("failed to complete task: %w", err)
- }
- return nil
-}
-
-// ReopenTask marks a completed task as active in Todoist
-func (c *TodoistClient) ReopenTask(ctx context.Context, taskID string) error {
- path := fmt.Sprintf("/tasks/%s/reopen", taskID)
- if err := c.PostEmpty(ctx, path, c.authHeaders()); err != nil {
- return fmt.Errorf("failed to reopen task: %w", err)
- }
- return nil
-}
-
-// parseDueDate parses due date from API response
-// dueInfo represents the due date structure from Todoist API
-type dueInfo struct {
- Date string `json:"date"`
- Datetime string `json:"datetime"`
- IsRecurring bool `json:"is_recurring"`
-}
-
-func parseDueDate(due *dueInfo) *time.Time {
- if due == nil {
- return nil
- }
- var dueDate time.Time
- var err error
- if due.Datetime != "" {
- // RFC3339 includes timezone, then convert to display timezone
- dueDate, err = time.Parse(time.RFC3339, due.Datetime)
- if err == nil {
- dueDate = config.ToDisplayTZ(dueDate)
- }
- } else if due.Date != "" {
- // Todoist may put a local datetime (no tz offset) in the date field
- // e.g. "2026-03-22T19:00:00" for recurring tasks with a set time.
- // Fall back to date-only "2006-01-02" if no T is present.
- if strings.Contains(due.Date, "T") {
- dueDate, err = config.ParseDateTimeInDisplayTZ("2006-01-02T15:04:05", due.Date)
- } else {
- dueDate, err = config.ParseDateInDisplayTZ(due.Date)
- }
- }
- if err != nil {
- return nil
- }
- return &dueDate
-}