0.3.49
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user