Items-list sparklines, retro CSS, pinned tooling, deploy docs
- Bulk-load recent price points per item and render a sparkline in the items list (new LoadRecentPriceHistory query avoids N+1). - Add retro.css visual layer and refreshed login/items/layout styling. - Swap the logo from webp to avif. - Pin htmx/Chart.js/Tailwind/templ versions in the Makefile with vendor / tools / update-deps targets; README documents the dependency-bump flow and the hardened systemd deploy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -798,6 +798,49 @@ func (s *Store) InsertPricePoint(ctx context.Context, p *models.PricePoint) erro
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadRecentPriceHistory bulk-loads the last `perItem` price points (oldest →
|
||||
// newest) for each item id. Returns a map keyed by item id; items with no
|
||||
// history are absent from the map. Used by the items list page to render a
|
||||
// sparkline per row without N+1 queries.
|
||||
func (s *Store) LoadRecentPriceHistory(ctx context.Context, ids []int64, perItem int) (map[int64][]models.PricePoint, error) {
|
||||
out := make(map[int64][]models.PricePoint, len(ids))
|
||||
if len(ids) == 0 || perItem <= 0 {
|
||||
return out, nil
|
||||
}
|
||||
placeholders := make([]string, len(ids))
|
||||
args := make([]any, 0, len(ids)+1)
|
||||
for i, id := range ids {
|
||||
placeholders[i] = "?"
|
||||
args = append(args, id)
|
||||
}
|
||||
args = append(args, perItem)
|
||||
q := `
|
||||
WITH ranked AS (
|
||||
SELECT item_id, price, store, polled_at,
|
||||
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY polled_at DESC) AS rn
|
||||
FROM price_history
|
||||
WHERE item_id IN (` + strings.Join(placeholders, ",") + `)
|
||||
)
|
||||
SELECT item_id, price, store, polled_at FROM ranked WHERE rn <= ?
|
||||
ORDER BY item_id, polled_at ASC
|
||||
`
|
||||
rows, err := s.DB.QueryContext(ctx, q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var p models.PricePoint
|
||||
var store sql.NullString
|
||||
if err := rows.Scan(&p.ItemID, &p.Price, &store, &p.PolledAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Store = s.dec(store.String)
|
||||
out[p.ItemID] = append(out[p.ItemID], p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) ListPriceHistory(ctx context.Context, itemID int64) ([]models.PricePoint, error) {
|
||||
rows, err := s.DB.QueryContext(ctx,
|
||||
`SELECT id, item_id, price, store, polled_at FROM price_history WHERE item_id = ? ORDER BY polled_at ASC`,
|
||||
|
||||
Reference in New Issue
Block a user