diff --git a/.devharness/operations.db b/.devharness/operations.db new file mode 100644 index 0000000..e9f76f5 Binary files /dev/null and b/.devharness/operations.db differ diff --git a/handlers/items.go b/handlers/items.go index 16c0f49..3238bac 100644 --- a/handlers/items.go +++ b/handlers/items.go @@ -2,7 +2,10 @@ package handlers import ( "encoding/json" + "fmt" "net/http" + "sort" + "strconv" "sync" ) @@ -19,20 +22,50 @@ var ( storeMu sync.RWMutex ) -// GetItems returns all items. +// GetItems returns items with pagination support. +// Query params: page (default 1), limit (default 20, max 100). +// Response header: X-Total-Count with total item count. func GetItems(w http.ResponseWriter, r *http.Request) { storeMu.RLock() defer storeMu.RUnlock() - // BUG: when store is nil (fresh start), json.Marshal(nil map values) - // causes unexpected behavior — should return [] but may panic or 500. + // Parse pagination params + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + if page < 1 { + page = 1 + } + limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + if limit < 1 { + limit = 20 + } + if limit > 100 { + limit = 100 + } + + // Collect and sort items for stable pagination items := make([]Item, 0, len(store)) for _, item := range store { items = append(items, item) } + sort.Slice(items, func(i, j int) bool { + return items[i].ID < items[j].ID + }) + + // Apply pagination + total := len(items) + start := (page - 1) * limit + if start > total { + start = total + } + end := start + limit + if end > total { + end = total + } + paged := items[start:end] w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(items); err != nil { + w.Header().Set("X-Total-Count", fmt.Sprintf("%d", total)) + if err := json.NewEncoder(w).Encode(paged); err != nil { http.Error(w, `{"error":"internal server error"}`, http.StatusInternalServerError) } }