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
85 lines
2.2 KiB
Go
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"
|
|
}
|
|
}
|