This commit is contained in:
ponzischeme89
2026-08-19 21:30:44 +12:00
parent 0782545013
commit 769fe01c84
24 changed files with 1131 additions and 500 deletions
+7 -46
View File
@@ -6,10 +6,9 @@ import (
"time"
)
// Search history is the record of what a household looks for, and it has two readers with
// quite different appetites: a television asking for one viewer's last few queries, and
// the console asking what the house as a whole has been searching. Both read the one
// table, which is why the writer's rules live here beside them.
// Search history is the record of what a household looks for, read by the console as the
// summary of what the house searches for and as the uncollapsed log of what happened just
// now. The writer's rules live here beside those readers.
// SearchDedupeWindow is how long an identical query counts as the same search.
//
@@ -27,8 +26,8 @@ const SearchRetention = 30 * 24 * time.Hour
// RecordSearch stores a normalized query for future per-user ranking analysis.
//
// Case-insensitive within the dedupe window, matching RecentSearches, which collapses
// case-only duplicates when it reads them back.
// Case-insensitive within the dedupe window, so a query retyped with a different
// capitalisation is not a second search.
func (s *Store) RecordSearch(ctx context.Context, userID, query string) error {
_, err := s.pool.Exec(ctx,
`WITH inserted AS (
@@ -49,44 +48,6 @@ func (s *Store) RecordSearch(ctx context.Context, userID, query string) error {
return err
}
// RecentSearches returns a user's distinct queries in most-recently-used order.
// Case-only duplicates collapse to the spelling used most recently.
func (s *Store) RecentSearches(
ctx context.Context,
userID string,
since time.Time,
limit int,
) ([]string, error) {
rows, err := s.pool.Query(ctx, `
SELECT query
FROM (
SELECT DISTINCT ON (lower(query)) query, occurred_at
FROM search_history
WHERE emby_user_id = $1 AND occurred_at >= $2
ORDER BY lower(query), occurred_at DESC
) AS latest
ORDER BY occurred_at DESC
LIMIT $3`,
userID, since, limit)
if err != nil {
return nil, fmt.Errorf("store: recent searches: %w", err)
}
defer rows.Close()
queries := make([]string, 0, limit)
for rows.Next() {
var query string
if err := rows.Scan(&query); err != nil {
return nil, fmt.Errorf("store: scan recent search: %w", err)
}
queries = append(queries, query)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("store: read recent searches: %w", err)
}
return queries, nil
}
// SearchTerm is one query the household searched for, aggregated across everyone.
type SearchTerm struct {
Query string `json:"query"`
@@ -114,8 +75,8 @@ type SearchTotals struct {
// SearchTerms aggregates the household's queries since a point in time, most-searched
// first. Grouped case-insensitively and labelled with the spelling used most recently,
// the same rule RecentSearches applies, so one query cannot appear as two rows because
// somebody's on-screen keyboard capitalised it.
// the same rule the writer's dedupe window applies, so one query cannot appear as two
// rows because somebody's on-screen keyboard capitalised it.
func (s *Store) SearchTerms(ctx context.Context, since time.Time, limit int) ([]SearchTerm, error) {
rows, err := s.pool.Query(ctx, `
SELECT (array_agg(query ORDER BY occurred_at DESC))[1] AS query,