summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-16 06:24:06 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-16 06:24:06 +0000
commitec45580c839cb94135c6c11c4aecaea255c8f2b7 (patch)
tree3d38b13aa82aab26482d4904b40c9c53990371d8 /internal
parentc21f891954f98d6df3769d5063f9528240b5b20a (diff)
Add availability_blocks CRUD to the store layer
Diffstat (limited to 'internal')
-rw-r--r--internal/store/availability.go49
-rw-r--r--internal/store/availability_test.go68
2 files changed, 117 insertions, 0 deletions
diff --git a/internal/store/availability.go b/internal/store/availability.go
new file mode 100644
index 0000000..0adf664
--- /dev/null
+++ b/internal/store/availability.go
@@ -0,0 +1,49 @@
+package store
+
+import (
+ "task-dashboard/internal/models"
+)
+
+// CreateAvailabilityBlock inserts a new weekly availability block and returns it.
+func (s *Store) CreateAvailabilityBlock(weekday int, startTime, endTime, label string) (*models.AvailabilityBlock, error) {
+ id := newTaskID()
+ if _, err := s.db.Exec(`
+ INSERT INTO availability_blocks (id, weekday, start_time, end_time, label) VALUES (?, ?, ?, ?, ?)
+ `, id, weekday, startTime, endTime, label); err != nil {
+ return nil, err
+ }
+ return &models.AvailabilityBlock{ID: id, Weekday: weekday, StartTime: startTime, EndTime: endTime, Label: label}, nil
+}
+
+// GetAvailabilityBlocks returns every availability block, ordered by weekday
+// then start time.
+func (s *Store) GetAvailabilityBlocks() ([]models.AvailabilityBlock, error) {
+ rows, err := s.db.Query(`
+ SELECT id, weekday, start_time, end_time, label FROM availability_blocks ORDER BY weekday ASC, start_time ASC
+ `)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = rows.Close() }()
+
+ var blocks []models.AvailabilityBlock
+ for rows.Next() {
+ var b models.AvailabilityBlock
+ if err := rows.Scan(&b.ID, &b.Weekday, &b.StartTime, &b.EndTime, &b.Label); err != nil {
+ return nil, err
+ }
+ blocks = append(blocks, b)
+ }
+ return blocks, rows.Err()
+}
+
+// DeleteAvailabilityBlock removes an availability block. Returns
+// ErrNativeTaskNotFound if id doesn't match any row (reusing the sentinel
+// already shared across native-task/project not-found cases).
+func (s *Store) DeleteAvailabilityBlock(id string) error {
+ result, err := s.db.Exec(`DELETE FROM availability_blocks WHERE id = ?`, id)
+ if err != nil {
+ return err
+ }
+ return checkRowsAffected(result)
+}
diff --git a/internal/store/availability_test.go b/internal/store/availability_test.go
index 97ce90e..ef3500c 100644
--- a/internal/store/availability_test.go
+++ b/internal/store/availability_test.go
@@ -2,6 +2,7 @@ package store
import (
"database/sql"
+ "errors"
"path/filepath"
"testing"
@@ -31,3 +32,70 @@ func newAvailabilityTestStore(t *testing.T) *Store {
}
return &Store{db: db}
}
+
+func TestCreateAvailabilityBlock_ReturnsCreatedBlock(t *testing.T) {
+ s := newAvailabilityTestStore(t)
+
+ block, err := s.CreateAvailabilityBlock(1, "18:00", "20:00", "evening focus")
+ if err != nil {
+ t.Fatalf("CreateAvailabilityBlock: %v", err)
+ }
+ if block.ID == "" {
+ t.Error("expected a generated ID")
+ }
+ if block.Weekday != 1 || block.StartTime != "18:00" || block.EndTime != "20:00" || block.Label != "evening focus" {
+ t.Errorf("block = %+v, unexpected field values", block)
+ }
+}
+
+func TestGetAvailabilityBlocks_ReturnsAllOrderedByWeekday(t *testing.T) {
+ s := newAvailabilityTestStore(t)
+
+ if _, err := s.CreateAvailabilityBlock(3, "09:00", "10:00", ""); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := s.CreateAvailabilityBlock(1, "18:00", "20:00", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ blocks, err := s.GetAvailabilityBlocks()
+ if err != nil {
+ t.Fatalf("GetAvailabilityBlocks: %v", err)
+ }
+ if len(blocks) != 2 {
+ t.Fatalf("len(blocks) = %d, want 2", len(blocks))
+ }
+ if blocks[0].Weekday != 1 || blocks[1].Weekday != 3 {
+ t.Errorf("expected weekday-ascending order, got %d then %d", blocks[0].Weekday, blocks[1].Weekday)
+ }
+}
+
+func TestDeleteAvailabilityBlock_RemovesIt(t *testing.T) {
+ s := newAvailabilityTestStore(t)
+
+ block, err := s.CreateAvailabilityBlock(2, "07:00", "08:00", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := s.DeleteAvailabilityBlock(block.ID); err != nil {
+ t.Fatalf("DeleteAvailabilityBlock: %v", err)
+ }
+
+ blocks, err := s.GetAvailabilityBlocks()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(blocks) != 0 {
+ t.Errorf("expected no blocks after delete, got %d", len(blocks))
+ }
+}
+
+func TestDeleteAvailabilityBlock_UnknownID_ReturnsErrNotFound(t *testing.T) {
+ s := newAvailabilityTestStore(t)
+
+ err := s.DeleteAvailabilityBlock("does-not-exist")
+ if !errors.Is(err, ErrNativeTaskNotFound) {
+ t.Errorf("err = %v, want ErrNativeTaskNotFound", err)
+ }
+}