package api import ( "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. 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 } key := cache.UserKey(sess.EmbyUserID, "person:v1:"+personID) if raw, err := s.cache.Get(ctx, key); err == nil { w.Header().Set("X-Memby-Cache", "hit") writeRaw(w, http.StatusOK, raw) return } person, err := s.emby.Item( ctx, credentials(sess), personID, "Overview,Genres,PrimaryImageAspectRatio", ) if err != nil { s.writeUpstreamError(ctx, w, err, "could not load the person") return } if err := s.cache.Set(ctx, key, person, s.cfg.ItemTTL); err != nil { s.loggerFor(ctx).Warn("person cache write failed", "error", err) } w.Header().Set("X-Memby-Cache", "miss") writeRaw(w, http.StatusOK, person) } // Filmography is kept separate from the biography so life dates can decorate the cast // row without also loading every cast member's credits. 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) if raw, err := s.cache.Get(ctx, key); err == nil { w.Header().Set("X-Memby-Cache", "hit") writeRaw(w, http.StatusOK, raw) return } 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 { s.writeUpstreamError(ctx, w, err, "could not load the person's filmography") return } items := result.Items if items == nil { items = []json.RawMessage{} } body, err := json.Marshal(personFilmographyResponse{Items: items}) if err != nil { writeError(w, http.StatusInternalServerError, "could not encode the filmography") return } if err := s.cache.Set(ctx, key, body, s.cfg.ItemTTL); err != nil { s.loggerFor(ctx).Warn("person filmography cache write failed", "error", err) } w.Header().Set("X-Memby-Cache", "miss") writeRaw(w, http.StatusOK, body) }