summaryrefslogtreecommitdiff
path: root/internal/store
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store')
-rw-r--r--internal/store/labels.go46
-rw-r--r--internal/store/labels_test.go84
-rw-r--r--internal/store/native_tasks_test.go8
3 files changed, 138 insertions, 0 deletions
diff --git a/internal/store/labels.go b/internal/store/labels.go
new file mode 100644
index 0000000..3c5f5eb
--- /dev/null
+++ b/internal/store/labels.go
@@ -0,0 +1,46 @@
+package store
+
+import (
+ "encoding/json"
+
+ "task-dashboard/internal/models"
+)
+
+// SetTaskLabels replaces a task's label set entirely (not an add/remove
+// single-label API), matching UpdateNativeTask's replace-whole-value style.
+// Returns ErrNativeTaskNotFound if id doesn't match any row.
+func (s *Store) SetTaskLabels(id string, labels []string) error {
+ labelsJSON, _ := json.Marshal(labels)
+ result, err := s.db.Exec(`
+ UPDATE native_tasks SET labels = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
+ `, string(labelsJSON), id)
+ if err != nil {
+ return err
+ }
+ return checkRowsAffected(result)
+}
+
+// GetLabelColors returns every label that has been assigned a color.
+func (s *Store) GetLabelColors() ([]models.LabelColor, error) {
+ rows, err := s.db.Query(`SELECT name, color FROM labels ORDER BY name ASC`)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = rows.Close() }()
+
+ var colors []models.LabelColor
+ for rows.Next() {
+ var c models.LabelColor
+ if err := rows.Scan(&c.Name, &c.Color); err != nil {
+ return nil, err
+ }
+ colors = append(colors, c)
+ }
+ return colors, rows.Err()
+}
+
+// SetLabelColor assigns (or reassigns) a label's display color.
+func (s *Store) SetLabelColor(name, color string) error {
+ _, err := s.db.Exec(`INSERT OR REPLACE INTO labels (name, color) VALUES (?, ?)`, name, color)
+ return err
+}
diff --git a/internal/store/labels_test.go b/internal/store/labels_test.go
new file mode 100644
index 0000000..4e97084
--- /dev/null
+++ b/internal/store/labels_test.go
@@ -0,0 +1,84 @@
+package store
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestSetTaskLabels_UpdatesLabels(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+
+ if err := s.SetTaskLabels("real-1", []string{"chore", "urgent"}); err != nil {
+ t.Fatalf("SetTaskLabels: %v", err)
+ }
+
+ task, err := s.GetNativeTaskByID("real-1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(task.Labels) != 2 || task.Labels[0] != "chore" || task.Labels[1] != "urgent" {
+ t.Errorf("Labels = %v, want [chore urgent]", task.Labels)
+ }
+}
+
+func TestSetTaskLabels_EmptyList_ClearsLabels(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+ if err := s.SetTaskLabels("real-1", []string{"chore"}); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := s.SetTaskLabels("real-1", []string{}); err != nil {
+ t.Fatalf("SetTaskLabels (clear): %v", err)
+ }
+
+ task, err := s.GetNativeTaskByID("real-1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(task.Labels) != 0 {
+ t.Errorf("Labels = %v, want empty", task.Labels)
+ }
+}
+
+func TestSetTaskLabels_UnknownID_ReturnsErrNotFound(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+ err := s.SetTaskLabels("does-not-exist", []string{"chore"})
+ if !errors.Is(err, ErrNativeTaskNotFound) {
+ t.Fatalf("expected ErrNativeTaskNotFound, got %v", err)
+ }
+}
+
+func TestSetLabelColor_ThenGetLabelColors_ReturnsIt(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+
+ if err := s.SetLabelColor("chore", "#F59E0B"); err != nil {
+ t.Fatalf("SetLabelColor: %v", err)
+ }
+
+ colors, err := s.GetLabelColors()
+ if err != nil {
+ t.Fatalf("GetLabelColors: %v", err)
+ }
+ if len(colors) != 1 || colors[0].Name != "chore" || colors[0].Color != "#F59E0B" {
+ t.Fatalf("colors = %+v, want [{chore #F59E0B}]", colors)
+ }
+}
+
+func TestSetLabelColor_SameName_Overwrites(t *testing.T) {
+ s := newNativeTasksTestStore(t)
+ if err := s.SetLabelColor("chore", "#F59E0B"); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := s.SetLabelColor("chore", "#EF4444"); err != nil {
+ t.Fatalf("SetLabelColor (overwrite): %v", err)
+ }
+
+ colors, err := s.GetLabelColors()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(colors) != 1 || colors[0].Color != "#EF4444" {
+ t.Fatalf("colors = %+v, want a single entry with the overwritten color", colors)
+ }
+}
diff --git a/internal/store/native_tasks_test.go b/internal/store/native_tasks_test.go
index 0117bf5..cf9ea49 100644
--- a/internal/store/native_tasks_test.go
+++ b/internal/store/native_tasks_test.go
@@ -57,6 +57,14 @@ func newNativeTasksTestStore(t *testing.T) *Store {
`); err != nil {
t.Fatal(err)
}
+ if _, err := db.Exec(`
+ CREATE TABLE labels (
+ name TEXT PRIMARY KEY,
+ color TEXT NOT NULL
+ )
+ `); err != nil {
+ t.Fatal(err)
+ }
if _, err := db.Exec(`INSERT INTO native_tasks (id, content) VALUES ('real-1', 'Real task')`); err != nil {
t.Fatal(err)
}