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