Files
pipu-music/services/api/internal/uploads/stream.go
T
mrleo1nid 7d62531c02
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Canceled after 4m13s
ci / module boundaries / boundaries (push) Successful in 8s
ci / api / api (push) Successful in 56s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m7s
fix(uploads): serve private previews through API and show batch cover status
2026-10-29 11:34:55 +03:00

85 lines
2.2 KiB
Go

package uploads
import (
"context"
"net/http"
"os"
"strconv"
"time"
"github.com/google/uuid"
"pipu/api/internal/api/gen"
"pipu/api/internal/platform/apperr"
"pipu/api/internal/platform/httpx"
)
// uploadStream serves private staging files through the API with Range support.
// Nginx runs as another user and cannot read tus files (0750 directories, 0640 files).
type uploadStream struct {
req *http.Request
file string
format string
}
func (s uploadStream) VisitStreamUploadResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", contentType(s.format))
w.Header().Set("Cache-Control", "private, max-age=600")
f, err := os.Open(s.file)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
info, err := f.Stat()
if err != nil {
return err
}
if s.req == nil {
http.ServeContent(w, &http.Request{}, "", info.ModTime(), f)
return nil
}
http.ServeContent(w, s.req, "", info.ModTime(), f)
return nil
}
func (h *Handler) streamURL(id uuid.UUID) string {
exp := time.Now().Add(12 * time.Hour).Unix()
sig := h.signer.Sign("upload", id.String(), strconv.FormatInt(exp, 10))
return "/api/v1/admin/uploads/files/" + id.String() + "/stream?exp=" + strconv.FormatInt(exp, 10) + "&sig=" + sig
}
// StreamUpload реализует gen.StrictServerInterface.
func (h *Handler) StreamUpload(ctx context.Context, req gen.StreamUploadRequestObject) (gen.StreamUploadResponseObject, error) {
if h.signer == nil || !h.signer.Verify(req.Params.Sig, "upload", req.Id.String(), strconv.FormatInt(req.Params.Exp, 10)) || time.Now().Unix() > req.Params.Exp {
return nil, apperr.Forbidden("uploads.link_invalid", "link is invalid or expired")
}
f, err := h.svc.OpenStaging(ctx, req.Id)
if err != nil {
return nil, err
}
format := ""
if f.Format != nil {
format = *f.Format
}
return uploadStream{
req: httpx.RequestFrom(ctx), file: f.Abs, format: format,
}, nil
}
func contentType(format string) string {
switch format {
case "mp3":
return "audio/mpeg"
case "flac":
return "audio/flac"
case "m4a":
return "audio/mp4"
case "ogg", "opus":
return "audio/ogg"
case "wav":
return "audio/wav"
default:
return "application/octet-stream"
}
}