2026-10-14 20:07:26 -07:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
|
|
|
|
|
"g.fc.run/misc/byre/internal/store"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (s *Server) UploadBlob(w http.ResponseWriter, r *http.Request) {
|
2026-10-15 19:02:33 -07:00
|
|
|
data, err := io.ReadAll(http.MaxBytesReader(w, r.Body, store.MaxBlobBytes))
|
2026-10-14 20:07:26 -07:00
|
|
|
maxErr := &http.MaxBytesError{}
|
|
|
|
|
if errors.As(err, &maxErr) {
|
2026-10-15 19:02:33 -07:00
|
|
|
writeError(w, http.StatusRequestEntityTooLarge, fmt.Errorf("images are limited to %d MiB", store.MaxBlobBytes>>20))
|
2026-10-14 20:07:26 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusBadRequest, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
contentType := http.DetectContentType(data)
|
2026-10-15 19:02:33 -07:00
|
|
|
if !store.ImageTypes[contentType] {
|
2026-10-14 20:07:26 -07:00
|
|
|
writeError(w, http.StatusBadRequest, fmt.Errorf("unsupported image type %s; use PNG, JPEG, GIF or WebP", contentType))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b, err := s.store.CreateBlob(r.Context(), contentType, data, actor(r))
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSON(w, http.StatusCreated, b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) GetBlob(w http.ResponseWriter, r *http.Request, blobID string) {
|
|
|
|
|
b, path, err := s.store.GetBlob(r.Context(), blobID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeStoreError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", b.ContentType)
|
|
|
|
|
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
|
|
|
|
|
http.ServeContent(w, r, "", b.CreatedAt, f)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) images(w http.ResponseWriter, r *http.Request, ids *[]string) ([]string, bool) {
|
|
|
|
|
if ids == nil {
|
|
|
|
|
return nil, true
|
|
|
|
|
}
|
|
|
|
|
for _, id := range *ids {
|
|
|
|
|
_, _, err := s.store.GetBlob(r.Context(), id)
|
|
|
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
|
|
|
writeError(w, http.StatusBadRequest, err)
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeError(w, http.StatusInternalServerError, err)
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return *ids, true
|
|
|
|
|
}
|