package api import ( "errors" "net/http" "strings" "time" "github.com/go-chi/chi/v5" "github.com/cdnutter/playlink/internal/art" "github.com/cdnutter/playlink/internal/engine" ) func (s *Server) handleArtSettings(w http.ResponseWriter, r *http.Request) { cfg := s.eng.ArtSettings(r.Context()) writeJSON(w, 200, map[string]any{"settings": cfg, "all_sources": art.AllSources}) } func (s *Server) handlePutArtSettings(w http.ResponseWriter, r *http.Request) { var cfg engine.ArtSettings if !readJSON(w, r, &cfg) { return } if err := s.eng.SetArtSettings(r.Context(), cfg); err != nil { writeErr(w, 400, err) return } s.handleArtSettings(w, r) } func (s *Server) handleArtSearch(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() query := art.Query{Artist: q.Get("artist"), Album: q.Get("album"), Text: q.Get("q")} if strings.TrimSpace(query.Artist+query.Album+query.Text) == "" { writeErr(w, 400, errors.New("enter an artist, album or search text")) return } var sources []string if src := q.Get("sources"); src != "" { sources = strings.Split(src, ",") } res, errs, err := s.eng.SearchArt(r.Context(), query, sources) if err != nil { writeErr(w, 500, err) return } writeJSON(w, 200, map[string]any{"results": res, "errors": errs}) } func (s *Server) handleArtPrepare(w http.ResponseWriter, r *http.Request) { var body struct { URL string `json:"url"` } if !readJSON(w, r, &body) { return } p, err := s.eng.PrepareArtURL(r.Context(), body.URL) if err != nil { writeErr(w, 400, err) return } writeJSON(w, 200, p) } func (s *Server) handleArtUpload(w http.ResponseWriter, r *http.Request) { f, _, err := r.FormFile("image") if err != nil { writeErr(w, 400, errors.New("choose an image file")) return } defer f.Close() p, err := s.eng.PrepareArtUpload(r.Context(), f) if err != nil { writeErr(w, 400, err) return } writeJSON(w, 200, p) } func (s *Server) handleArtPrepared(w http.ResponseWriter, r *http.Request) { p, err := s.eng.PreparedArtPath(chi.URLParam(r, "token")) if err != nil { writeErr(w, 404, err) return } w.Header().Set("Content-Type", "image/jpeg") w.Header().Set("Cache-Control", "private, max-age=3600") http.ServeFile(w, r, p) } func (s *Server) handleCover(w http.ResponseWriter, r *http.Request) { id, _ := idParam(r) q := r.URL.Query() b, err := s.eng.Cover(r.Context(), id, q.Get("side"), q.Get("path")) if err != nil { opErr(w, err) return } if len(b) == 0 { w.WriteHeader(http.StatusNoContent) return } w.Header().Set("Content-Type", http.DetectContentType(b)) w.Header().Set("Cache-Control", "no-cache") http.ServeContent(w, r, "cover", time.Time{}, strings.NewReader(string(b))) } func (s *Server) handleAlbum(w http.ResponseWriter, r *http.Request) { id, _ := idParam(r) q := r.URL.Query() a, err := s.eng.AlbumSongs(r.Context(), id, q.Get("side"), q.Get("path")) if err != nil { opErr(w, err) return } writeJSON(w, 200, a) } func (s *Server) handleArtAuto(w http.ResponseWriter, r *http.Request) { id, _ := idParam(r) var m engine.ArtMatch if !readJSON(w, r, &m) { return } if !s.preAction(w, r, id, "auto-match album art") { return } j, err := s.eng.StartArtAutoMatch(id, m) if err != nil { opErr(w, err) return } writeJSON(w, 202, j) } // handleArtThumb shows a search result's thumbnail through the server, so // the page keeps its strict content policy and the browser never contacts // the image hosts directly. Fetching uses the local-network-safe client. func (s *Server) handleArtThumb(w http.ResponseWriter, r *http.Request) { b, err := s.eng.FetchArtThumb(r.Context(), r.URL.Query().Get("u")) if err != nil { writeErr(w, 502, err) return } ct := http.DetectContentType(b) if !strings.HasPrefix(ct, "image/") { writeErr(w, 502, errors.New("not an image")) return } w.Header().Set("Content-Type", ct) w.Header().Set("Cache-Control", "private, max-age=86400") w.Write(b) }