package api import ( "net/http" "strings" "github.com/ponzischeme89/memby/server/internal/store" ) // The console's window on what the household has actually asked for. // // The overview already reports RequestUsage — a count and a last-asked time per viewer — // which answers "who uses the feature" and nothing about "what did they ask for". This is // the table underneath that number: every stored ask, newest first, with the person // attached and the same derived status the viewer's own page shows, so an operator can see // that three people are waiting on the same film or that a request has been stuck // searching for a week. const adminRequestsLimit = store.MediaRequestSweepLimit // adminMediaRequest is one row of that table: an ask, who made it, and what has become of // it. The status half is derived per read by decorateRequests, exactly as it is for the // viewer, so the console and the television never disagree about a title's state. type adminMediaRequest struct { UserID string `json:"userId"` Username string `json:"username,omitempty"` myRequest } type adminRequestsResponse struct { Requests []adminMediaRequest `json:"requests"` } func (s *Server) handleAdminRequests(w http.ResponseWriter, r *http.Request) { ctx := r.Context() owned, err := s.store.AllMediaRequests(ctx, adminRequestsLimit) if err != nil { s.loggerFor(ctx).Error("admin media requests read failed", "error", err) writeError(w, http.StatusInternalServerError, "could not read media requests") return } // An optional filter to what one viewer asked for, applied here rather than in SQL: the // list is already bounded and small, and AllMediaRequests is the one read the ready // sweep also uses. if userID := strings.TrimSpace(r.URL.Query().Get("userId")); userID != "" { filtered := owned[:0] for _, req := range owned { if req.UserID == userID { filtered = append(filtered, req) } } owned = filtered } stored := make([]store.MediaRequest, len(owned)) for i := range owned { stored[i] = owned[i].MediaRequest } // One decoration pass for the whole household. The *arr catalogues it consults are // cached for the day and shared, so this is the same cost as one viewer opening their // page. The console is the operator's tool, so it always sees the full progress // vocabulary. cards := s.decorateRequests(ctx, stored, true) // A name an ask cannot be attributed to costs the column and nothing else — the id // still tells one requester from another, and the titles are the point of the page. names := map[string]string{} if users, err := s.store.KnownUsers(ctx); err == nil { for _, user := range users { if user.Username != "" { names[user.ID] = user.Username } } } else { s.loggerFor(ctx).Warn("media request owners unresolved", "error", err) } rows := make([]adminMediaRequest, len(owned)) for i := range owned { rows[i] = adminMediaRequest{ UserID: owned[i].UserID, Username: names[owned[i].UserID], myRequest: cards[i], } } writeJSON(w, http.StatusOK, adminRequestsResponse{Requests: rows}) }