Files
memby/server/internal/store/requests.go
T

94 lines
3.3 KiB
Go
Raw Normal View History

2026-08-12 14:13:19 +12:00
package store
import (
"context"
"fmt"
"strings"
"time"
)
// MediaRequest is one viewer's ask, as recorded. It carries no status: see the schema
// comment on media_requests for why the state is derived per read rather than stored.
type MediaRequest struct {
MediaType string `json:"mediaType"`
ForeignID int `json:"foreignId"`
Title string `json:"title"`
Year int `json:"year,omitempty"`
PosterURL string `json:"posterUrl,omitempty"`
RequestedAt time.Time `json:"requestedAt"`
}
// MediaRequestLimit caps what one viewer's page will read back. A household that has been
// asking for things for two years should not turn the page into an unbounded query, and
// nobody scrolls past the most recent hundred by remote.
const MediaRequestLimit = 100
// SaveMediaRequest records an ask, or refreshes one already held.
//
// The repeat is deliberately an update rather than a no-op: asking again is how somebody
// says they still want it, and the page is ordered by when they asked.
func (s *Store) SaveMediaRequest(ctx context.Context, userID string, req MediaRequest) error {
userID = strings.TrimSpace(userID)
if userID == "" || req.ForeignID <= 0 {
return fmt.Errorf("store: media request needs a user and a foreign id")
}
_, err := s.pool.Exec(ctx, `
INSERT INTO media_requests
(emby_user_id, media_type, foreign_id, title, year, poster_url, requested_at)
VALUES ($1, $2, $3, $4, $5, $6, now())
ON CONFLICT (emby_user_id, media_type, foreign_id) DO UPDATE
SET title = EXCLUDED.title,
year = EXCLUDED.year,
poster_url = EXCLUDED.poster_url,
requested_at = now()`,
userID, req.MediaType, req.ForeignID, req.Title, req.Year, req.PosterURL)
if err != nil {
return fmt.Errorf("store: save media request: %w", err)
}
return nil
}
// MediaRequests returns one viewer's asks, most recent first.
func (s *Store) MediaRequests(ctx context.Context, userID string) ([]MediaRequest, error) {
rows, err := s.pool.Query(ctx, `
SELECT media_type, foreign_id, title, year, poster_url, requested_at
FROM media_requests
WHERE emby_user_id = $1
ORDER BY requested_at DESC
LIMIT $2`, strings.TrimSpace(userID), MediaRequestLimit)
if err != nil {
return nil, fmt.Errorf("store: read media requests: %w", err)
}
defer rows.Close()
requests := []MediaRequest{}
for rows.Next() {
var req MediaRequest
if err := rows.Scan(
&req.MediaType, &req.ForeignID, &req.Title, &req.Year, &req.PosterURL, &req.RequestedAt,
); err != nil {
return nil, fmt.Errorf("store: scan media request: %w", err)
}
requests = append(requests, req)
}
return requests, rows.Err()
}
// DeleteMediaRequest removes a viewer's ask from their own page.
//
// It deliberately does not touch Radarr or Sonarr. The title may well have been downloaded
// by now, and other people may have asked for it too — this only says the viewer no longer
// wants it listed among theirs.
func (s *Store) DeleteMediaRequest(
ctx context.Context, userID, mediaType string, foreignID int,
) error {
_, err := s.pool.Exec(ctx, `
DELETE FROM media_requests
WHERE emby_user_id = $1 AND media_type = $2 AND foreign_id = $3`,
strings.TrimSpace(userID), mediaType, foreignID)
if err != nil {
return fmt.Errorf("store: delete media request: %w", err)
}
return nil
}