From ec45580c839cb94135c6c11c4aecaea255c8f2b7 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 16 Jul 2026 06:24:06 +0000 Subject: Add availability_blocks CRUD to the store layer --- internal/store/availability.go | 49 ++++++++++++++++++++++++++ internal/store/availability_test.go | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 internal/store/availability.go (limited to 'internal/store') 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) + } +} -- cgit v1.2.3