add MinIO media URL refresh endpoint
Add POST /api/v1/files/refresh-url to issue fresh presigned URLs from object keys, minio:// references, or stale presigned URLs so clients can refresh media links before render. Made-with: Cursor
This commit is contained in:
parent
78f231f222
commit
0d02eb1a24
|
|
@ -14,6 +14,10 @@ type Client struct {
|
|||
bucketName string
|
||||
}
|
||||
|
||||
func (c *Client) BucketName() string {
|
||||
return c.bucketName
|
||||
}
|
||||
|
||||
func NewClient(endpoint, accessKey, secretKey string, useSSL bool, bucketName string) (*Client, error) {
|
||||
mc, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ func (s *Service) GetURL(ctx context.Context, objectKey string, expiry time.Dura
|
|||
return s.client.GetFileURL(ctx, objectKey, expiry)
|
||||
}
|
||||
|
||||
func (s *Service) BucketName() string {
|
||||
return s.client.BucketName()
|
||||
}
|
||||
|
||||
// Delete removes a file from MinIO.
|
||||
func (s *Service) Delete(ctx context.Context, objectKey string) error {
|
||||
s.logger.Info("Deleting file from MinIO", zap.String("object_key", objectKey))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -31,6 +32,10 @@ type uploadMediaByURLReq struct {
|
|||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type refreshFileURLReq struct {
|
||||
Reference string `json:"reference"`
|
||||
}
|
||||
|
||||
// resolveFileURL converts a stored file path to a usable URL.
|
||||
// If the path starts with "minio://", it generates a presigned URL.
|
||||
// Otherwise it returns the path as-is (e.g. "/static/...").
|
||||
|
|
@ -82,6 +87,93 @@ func (h *Handler) GetFileURL(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (h *Handler) extractObjectKeyFromReference(reference string) (string, error) {
|
||||
ref := strings.TrimSpace(reference)
|
||||
if ref == "" {
|
||||
return "", fmt.Errorf("reference is required")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ref, "minio://") {
|
||||
key := strings.TrimPrefix(ref, "minio://")
|
||||
if key == "" {
|
||||
return "", fmt.Errorf("invalid minio reference")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(ref)
|
||||
if err == nil && u.Scheme != "" && u.Host != "" {
|
||||
path := strings.TrimPrefix(u.Path, "/")
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("invalid file URL")
|
||||
}
|
||||
bucket := strings.TrimSpace(h.minioSvc.BucketName())
|
||||
if bucket != "" {
|
||||
prefix := bucket + "/"
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
path = strings.TrimPrefix(path, prefix)
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("invalid file URL")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// RefreshFileURL generates a new presigned URL from an object key, minio:// URI, or stale presigned URL.
|
||||
// @Summary Refresh presigned URL for a file
|
||||
// @Tags files
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body refreshFileURLReq true "reference (object key, minio://..., or existing presigned URL)"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Router /api/v1/files/refresh-url [post]
|
||||
func (h *Handler) RefreshFileURL(c *fiber.Ctx) error {
|
||||
if h.minioSvc == nil {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
|
||||
Message: "File storage service is not available",
|
||||
})
|
||||
}
|
||||
|
||||
var req refreshFileURLReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid request body",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
objectKey, err := h.extractObjectKeyFromReference(req.Reference)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid file reference",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
freshURL, err := h.minioSvc.GetURL(c.Context(), objectKey, 1*time.Hour)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to refresh file URL",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "File URL refreshed",
|
||||
Data: map[string]interface{}{
|
||||
"object_key": objectKey,
|
||||
"url": freshURL,
|
||||
"expires_in": int((1 * time.Hour).Seconds()),
|
||||
},
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// UploadMedia uploads an image/audio/video file and returns its URL and key.
|
||||
// @Summary Upload media file
|
||||
// @Tags files
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
// File storage (MinIO)
|
||||
groupV1.Get("/files/url", a.authMiddleware, h.GetFileURL)
|
||||
groupV1.Post("/files/refresh-url", a.authMiddleware, h.RefreshFileURL)
|
||||
groupV1.Post("/files/upload", a.authMiddleware, h.UploadMedia)
|
||||
groupV1.Post("/files/audio", a.authMiddleware, h.UploadAudio)
|
||||
groupV1.Post("/questions/audio-answer", a.authMiddleware, h.SubmitAudioAnswer)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user