package handler import ( "io" "net/http" "backend/internal/config" "backend/internal/service" "backend/internal/storage" "github.com/gin-gonic/gin" ) type ImageHandler struct { cfg *config.Config imageAccess *service.ImageAccessService store *storage.Client } func NewImageHandler(cfg *config.Config, imageAccess *service.ImageAccessService, store *storage.Client) *ImageHandler { return &ImageHandler{ cfg: cfg, imageAccess: imageAccess, store: store, } } // Serve gates access (public showcase images, or a logged-in cookie — a regular // user only their own images, an admin anyone's) and then PROXIES the object // from RustFS. Nothing is read from local disk; the RustFS endpoint is never // exposed to the client. func (h *ImageHandler) Serve(c *gin.Context) { user := c.Param("user") name := c.Param("name") rel, err := h.imageAccess.Resolve(user, name) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid path"}) return } public, err := h.imageAccess.IsPublic(c.Request.Context(), rel) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to authorize image"}) return } if !public { authorized, err := h.imageAccess.IsAuthorized( c.Request.Context(), readCookie(c, h.cfg.SessionCookieName), user, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "failed to authorize image"}) return } if !authorized { c.JSON(http.StatusUnauthorized, gin.H{"detail": "需要登录后访问"}) return } } // Forward Range so the browser can seek within videos. resp, err := h.store.Get(c.Request.Context(), rel, c.GetHeader("Range")) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"detail": "failed to fetch object"}) return } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { c.JSON(http.StatusNotFound, gin.H{"detail": "not found"}) return } for _, hdr := range []string{"Content-Type", "Content-Length", "Accept-Ranges", "Content-Range", "Last-Modified", "ETag", "Cache-Control"} { if v := resp.Header.Get(hdr); v != "" { c.Header(hdr, v) } } c.Status(resp.StatusCode) _, _ = io.Copy(c.Writer, resp.Body) } func readCookie(c *gin.Context, name string) string { v, err := c.Cookie(name) if err != nil { return "" } return v }