2026-08-10 07:11:14 +12:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
2026-08-19 18:08:00 +12:00
|
|
|
"context"
|
2026-08-10 07:11:14 +12:00
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
|
|
"github.com/ponzischeme89/memby/server/internal/cache"
|
|
|
|
|
"github.com/ponzischeme89/memby/server/internal/store"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type personFilmographyResponse struct {
|
|
|
|
|
Items []json.RawMessage `json:"items"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handlePerson returns Emby's person item. PremiereDate and EndDate are the person's
|
|
|
|
|
// birth and death dates; Overview is their biography.
|
2026-08-19 18:08:00 +12:00
|
|
|
//
|
|
|
|
|
// It is cached for the household rather than per viewer, and outside the namespace a
|
|
|
|
|
// playback stop invalidates. Nothing about who Cillian Murphy is depends on who is
|
|
|
|
|
// asking or on what they finished watching ten minutes ago, and filing it under the
|
|
|
|
|
// viewer meant a cast page paid Emby again after every episode anybody in the house
|
|
|
|
|
// completed.
|
2026-08-10 07:11:14 +12:00
|
|
|
func (s *Server) handlePerson(w http.ResponseWriter, r *http.Request, sess store.Session) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
personID := r.PathValue("id")
|
|
|
|
|
if personID == "" {
|
|
|
|
|
writeError(w, http.StatusBadRequest, "person id is required")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-08-19 18:08:00 +12:00
|
|
|
person, hit, err := s.cachedRead(ctx, cache.MetadataKey("person:v2:"+personID), s.cfg.ItemTTL,
|
|
|
|
|
func(ctx context.Context) (json.RawMessage, error) {
|
|
|
|
|
return s.emby.Item(ctx, credentials(sess), personID,
|
|
|
|
|
"Overview,Genres,PrimaryImageAspectRatio")
|
|
|
|
|
})
|
2026-08-10 07:11:14 +12:00
|
|
|
if err != nil {
|
|
|
|
|
s.writeUpstreamError(ctx, w, err, "could not load the person")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-08-19 18:08:00 +12:00
|
|
|
writeCached(w, hit, person)
|
2026-08-10 07:11:14 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filmography is kept separate from the biography so life dates can decorate the cast
|
|
|
|
|
// row without also loading every cast member's credits.
|
2026-08-19 18:08:00 +12:00
|
|
|
//
|
|
|
|
|
// Unlike the biography this one is genuinely per viewer — it asks Emby for user data, so
|
|
|
|
|
// the grid can mark what has been watched — and therefore stays where invalidation can
|
|
|
|
|
// reach it. What it gains instead is coalescing: this is the most expensive query the
|
|
|
|
|
// gateway makes of Emby, a recursive scan of the whole library filtered by person, and
|
|
|
|
|
// three televisions opening the same cast card used to mean three of them.
|
2026-08-10 07:11:14 +12:00
|
|
|
func (s *Server) handlePersonFilmography(w http.ResponseWriter, r *http.Request, sess store.Session) {
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
personID := r.PathValue("id")
|
|
|
|
|
if personID == "" {
|
|
|
|
|
writeError(w, http.StatusBadRequest, "person id is required")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
key := cache.UserKey(sess.EmbyUserID, "person-filmography:v1:"+personID)
|
2026-08-19 18:08:00 +12:00
|
|
|
body, hit, err := s.cachedRead(ctx, key, s.cfg.ItemTTL,
|
|
|
|
|
func(ctx context.Context) (json.RawMessage, error) {
|
|
|
|
|
result, err := s.emby.Items(ctx, credentials(sess), url.Values{
|
|
|
|
|
"PersonIds": {personID},
|
|
|
|
|
"IncludeItemTypes": {"Movie,Series"},
|
|
|
|
|
"Recursive": {"true"},
|
|
|
|
|
"SortBy": {"ProductionYear,SortName"},
|
|
|
|
|
"SortOrder": {"Descending"},
|
|
|
|
|
"Limit": {"60"},
|
|
|
|
|
"Fields": {"Overview,ProductionYear,PrimaryImageAspectRatio"},
|
|
|
|
|
"EnableImages": {"true"},
|
|
|
|
|
"EnableImageTypes": {"Primary"},
|
|
|
|
|
"ImageTypeLimit": {"1"},
|
|
|
|
|
"EnableUserData": {"true"},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
items := result.Items
|
|
|
|
|
if items == nil {
|
|
|
|
|
items = []json.RawMessage{}
|
|
|
|
|
}
|
|
|
|
|
return json.Marshal(personFilmographyResponse{Items: items})
|
|
|
|
|
})
|
2026-08-10 07:11:14 +12:00
|
|
|
if err != nil {
|
|
|
|
|
s.writeUpstreamError(ctx, w, err, "could not load the person's filmography")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-08-19 18:08:00 +12:00
|
|
|
writeCached(w, hit, body)
|
2026-08-10 07:11:14 +12:00
|
|
|
}
|