package handlers import ( "context" "io" "net/http" "time" "lookbook/internal/data/item" ) // HandleProxyVideo proxies video requests for embed items to avoid // cross-origin issues with external video CDNs (e.g., Twitter's video.twimg.com). // GET /proxy/video/{id} func HandleProxyVideo(rc *RequestContext, w http.ResponseWriter, r *http.Request) error { pubID := r.PathValue("id") if pubID == "" { http.NotFound(w, r) return nil } ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel() it, err := item.QFindByPubID(ctx, rc.DB, pubID) if err != nil { return err } if it == nil || !it.EmbedVideoURL.Valid || it.EmbedVideoURL.V == "" { http.NotFound(w, r) return nil } videoURL := it.EmbedVideoURL.V req, err := http.NewRequestWithContext(ctx, "GET", videoURL, nil) if err != nil { return err } req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Lookbook/1.0)") // Pass through Range header for video seeking if rangeHeader := r.Header.Get("Range"); rangeHeader != "" { req.Header.Set("Range", rangeHeader) } client := &http.Client{Timeout: 2 * time.Minute} resp, err := client.Do(req) if err != nil { http.Error(w, "Failed to fetch video", http.StatusBadGateway) return nil } defer resp.Body.Close() // Pass through relevant headers if ct := resp.Header.Get("Content-Type"); ct != "" { w.Header().Set("Content-Type", ct) } if cl := resp.Header.Get("Content-Length"); cl != "" { w.Header().Set("Content-Length", cl) } if cr := resp.Header.Get("Content-Range"); cr != "" { w.Header().Set("Content-Range", cr) } if ar := resp.Header.Get("Accept-Ranges"); ar != "" { w.Header().Set("Accept-Ranges", ar) } // Cache for 1 day in the browser w.Header().Set("Cache-Control", "public, max-age=86400") w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) return nil }