summaryrefslogtreecommitdiff
path: root/internal/store/native_tasks.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/native_tasks.go')
-rw-r--r--internal/store/native_tasks.go60
1 files changed, 50 insertions, 10 deletions
diff --git a/internal/store/native_tasks.go b/internal/store/native_tasks.go
index 5758407..6f8f999 100644
--- a/internal/store/native_tasks.go
+++ b/internal/store/native_tasks.go
@@ -1,12 +1,21 @@
package store
import (
+ "database/sql"
"encoding/json"
+ "errors"
"time"
"task-dashboard/internal/models"
)
+// ErrNativeTaskNotFound is returned by CompleteNativeTask, UncompleteNativeTask,
+// and RescheduleNativeTask when no row matches the given id -- previously these
+// three silently reported success on a 0-row UPDATE (Exec's err is nil even when
+// no rows match), so a stale or wrong id from a caller looked identical to a real
+// completion: the HTTP response was 200, but nothing in the database changed.
+var ErrNativeTaskNotFound = errors.New("native task not found")
+
// GetNativeTasks returns all non-completed native tasks.
func (s *Store) GetNativeTasks() ([]models.Task, error) {
rows, err := s.db.Query(`
@@ -71,31 +80,62 @@ func (s *Store) UpdateNativeTask(id, content, description string) error {
return err
}
-// CompleteNativeTask marks a task as completed.
+// CompleteNativeTask marks a task as completed. Returns ErrNativeTaskNotFound
+// if id doesn't match any row.
func (s *Store) CompleteNativeTask(id string) error {
- _, err := s.db.Exec(`
+ result, err := s.db.Exec(`
UPDATE native_tasks SET completed = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?
`, id)
- return err
+ if err != nil {
+ return err
+ }
+ return checkRowsAffected(result)
}
-// RescheduleNativeTask sets a new due date on a task.
+// RescheduleNativeTask sets a new due date on a task. Returns
+// ErrNativeTaskNotFound if id doesn't match any row.
func (s *Store) RescheduleNativeTask(id string, dueDate time.Time) error {
- _, err := s.db.Exec(`
+ result, err := s.db.Exec(`
UPDATE native_tasks SET due_date = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
`, dueDate, id)
- return err
+ if err != nil {
+ return err
+ }
+ return checkRowsAffected(result)
}
-// UncompleteNativeTask marks a task as not completed.
+// UncompleteNativeTask marks a task as not completed. Returns
+// ErrNativeTaskNotFound if id doesn't match any row.
func (s *Store) UncompleteNativeTask(id string) error {
- _, err := s.db.Exec(`
+ result, err := s.db.Exec(`
UPDATE native_tasks SET completed = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?
`, id)
- return err
+ if err != nil {
+ return err
+ }
+ return checkRowsAffected(result)
+}
+
+// checkRowsAffected returns ErrNativeTaskNotFound if the update matched no
+// rows -- mirrors the RowsAffected() check already used in sqlite.go's
+// ApproveAgentSession/DenyAgentSession for the same "silent 0-row update"
+// class of bug.
+func checkRowsAffected(result sql.Result) error {
+ affected, err := result.RowsAffected()
+ if err != nil {
+ return err
+ }
+ if affected == 0 {
+ return ErrNativeTaskNotFound
+ }
+ return nil
}
-func scanNativeTasks(rows interface{ Next() bool; Scan(...interface{}) error; Err() error }) ([]models.Task, error) {
+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