summaryrefslogtreecommitdiff
path: root/internal/store/native_tasks.go
blob: ca19377e5a64054c7db3436530952263fa97f273 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package store

import (
	"encoding/json"
	"log"
	"time"

	"task-dashboard/internal/models"
)

// GetNativeTasks returns all non-completed native tasks.
func (s *Store) GetNativeTasks() ([]models.Task, error) {
	rows, err := s.db.Query(`
		SELECT id, content, description, project_name, due_date, priority, completed, labels, created_at
		FROM native_tasks
		WHERE completed = 0
		ORDER BY CASE WHEN due_date IS NULL THEN 1 ELSE 0 END, due_date ASC, priority DESC
	`)
	if err != nil {
		return nil, err
	}
	defer func() { _ = rows.Close() }()
	return scanNativeTasks(rows)
}

// GetNativeTasksByDateRange returns non-completed native tasks due within the given range.
func (s *Store) GetNativeTasksByDateRange(start, end time.Time) ([]models.Task, error) {
	rows, err := s.db.Query(`
		SELECT id, content, description, project_name, due_date, priority, completed, labels, created_at
		FROM native_tasks
		WHERE completed = 0 AND due_date IS NOT NULL AND due_date >= ? AND due_date < ?
		ORDER BY due_date ASC, priority DESC
	`, start, end)
	if err != nil {
		return nil, err
	}
	defer func() { _ = rows.Close() }()
	return scanNativeTasks(rows)
}

// GetUndatedNativeTasks returns non-completed native tasks with no due date.
func (s *Store) GetUndatedNativeTasks() ([]models.Task, error) {
	rows, err := s.db.Query(`
		SELECT id, content, description, project_name, due_date, priority, completed, labels, created_at
		FROM native_tasks
		WHERE completed = 0 AND due_date IS NULL
		ORDER BY priority DESC, created_at ASC
	`)
	if err != nil {
		return nil, err
	}
	defer func() { _ = rows.Close() }()
	return scanNativeTasks(rows)
}

// CreateNativeTask inserts a new native task.
func (s *Store) CreateNativeTask(task models.Task) error {
	labelsJSON, _ := json.Marshal(task.Labels)
	_, err := s.db.Exec(`
		INSERT INTO native_tasks (id, content, description, project_name, due_date, priority, labels, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
	`, task.ID, task.Content, task.Description, task.ProjectName, task.DueDate, task.Priority, string(labelsJSON))
	return err
}

// UpdateNativeTask updates a native task's content and description.
func (s *Store) UpdateNativeTask(id, content, description string) error {
	_, err := s.db.Exec(`
		UPDATE native_tasks SET content = ?, description = ?, updated_at = CURRENT_TIMESTAMP
		WHERE id = ?
	`, content, description, id)
	return err
}

// CompleteNativeTask marks a task as completed.
func (s *Store) CompleteNativeTask(id string) error {
	_, err := s.db.Exec(`
		UPDATE native_tasks SET completed = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?
	`, id)
	return err
}

// RescheduleNativeTask sets a new due date on a task.
func (s *Store) RescheduleNativeTask(id string, dueDate time.Time) error {
	_, err := s.db.Exec(`
		UPDATE native_tasks SET due_date = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
	`, dueDate, id)
	return err
}

// UncompleteNativeTask marks a task as not completed.
func (s *Store) UncompleteNativeTask(id string) error {
	_, err := s.db.Exec(`
		UPDATE native_tasks SET completed = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?
	`, id)
	return err
}

// ImportFromTodoist copies all incomplete tasks from the Todoist cache into native_tasks.
// Skips tasks already present (by ID). Returns the count of newly imported tasks.
func (s *Store) ImportFromTodoist() (int, error) {
	result, err := s.db.Exec(`
		INSERT OR IGNORE INTO native_tasks
			(id, content, description, project_name, due_date, priority, completed, labels, created_at, updated_at)
		SELECT
			id, content, COALESCE(description, ''), COALESCE(project_name, ''),
			due_date, priority, 0, COALESCE(labels, '[]'), created_at, CURRENT_TIMESTAMP
		FROM tasks
		WHERE completed = 0
	`)
	if err != nil {
		return 0, err
	}
	n, _ := result.RowsAffected()
	log.Printf("ImportFromTodoist: imported %d tasks", n)
	return int(n), nil
}

func scanNativeTasks(rows interface{ Next() bool; Scan(...interface{}) error; Err() error }) ([]models.Task, error) {
	var tasks []models.Task
	for rows.Next() {
		var t models.Task
		var labelsJSON string
		var dueDateStr *string
		if err := rows.Scan(&t.ID, &t.Content, &t.Description, &t.ProjectName, &dueDateStr, &t.Priority, &t.Completed, &labelsJSON, &t.CreatedAt); err != nil {
			return nil, err
		}
		if dueDateStr != nil {
			if parsed, err := time.Parse(time.RFC3339, *dueDateStr); err == nil {
				t.DueDate = &parsed
			} else if parsed, err := time.Parse("2006-01-02 15:04:05", *dueDateStr); err == nil {
				t.DueDate = &parsed
			} else if parsed, err := time.Parse("2006-01-02", *dueDateStr); err == nil {
				t.DueDate = &parsed
			}
		}
		if err := json.Unmarshal([]byte(labelsJSON), &t.Labels); err != nil {
			t.Labels = nil
		}
		tasks = append(tasks, t)
	}
	return tasks, rows.Err()
}