mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 16:15:24 +00:00
Replace per-handler `token string` field with package-level `pkgGatewayToken` via `InitGatewayToken()`, matching the existing pattern for `pkgAPIKeyCache`, `pkgOwnerIDs`, etc. - Remove `token` from ~30 HTTP handler structs and constructors - Simplify `requireAuth(minRole, next)` signature (was 3 params) - Simplify `resolveAuth(r)` signature (was 2 params) - Rename `resolveAuthBearer` → `resolveAuthWithBearer` (clearer) - Add `store.WithRole()`/`RoleFromContext()` to propagate caller role through context - Inject role into context in `requireAuth` and `requireAuthBearer`
91 lines
2.5 KiB
Go
91 lines
2.5 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/channels/media"
|
|
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
|
)
|
|
|
|
const (
|
|
// maxUploadSize is the default max upload size (50MB).
|
|
maxUploadSize int64 = 50 * 1024 * 1024
|
|
)
|
|
|
|
// MediaUploadHandler handles media file uploads for WebSocket clients.
|
|
type MediaUploadHandler struct{}
|
|
|
|
// NewMediaUploadHandler creates a media upload handler.
|
|
func NewMediaUploadHandler() *MediaUploadHandler {
|
|
return &MediaUploadHandler{}
|
|
}
|
|
|
|
// RegisterRoutes registers the upload endpoint.
|
|
func (h *MediaUploadHandler) RegisterRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("POST /v1/media/upload", h.auth(h.handleUpload))
|
|
}
|
|
|
|
func (h *MediaUploadHandler) auth(next http.HandlerFunc) http.HandlerFunc {
|
|
return requireAuth("", next)
|
|
}
|
|
|
|
func (h *MediaUploadHandler) handleUpload(w http.ResponseWriter, r *http.Request) {
|
|
locale := extractLocale(r)
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
|
|
|
|
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgFileTooLarge)})
|
|
return
|
|
}
|
|
|
|
file, header, err := r.FormFile("file")
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgMissingFileField)})
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Sanitize filename: strip path, prevent traversal.
|
|
origName := filepath.Base(header.Filename)
|
|
if origName == "." || origName == "/" || strings.Contains(origName, "..") {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidFilename)})
|
|
return
|
|
}
|
|
|
|
ext := filepath.Ext(origName)
|
|
if ext == "" {
|
|
ext = ".bin"
|
|
}
|
|
|
|
// Save to temp with unique name.
|
|
tmpName := fmt.Sprintf("ws_upload_%d%s", time.Now().UnixNano(), ext)
|
|
tmpPath := filepath.Join(os.TempDir(), tmpName)
|
|
|
|
out, err := os.Create(tmpPath)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, "failed to create temp file")})
|
|
return
|
|
}
|
|
defer out.Close()
|
|
|
|
if _, err := io.Copy(out, file); err != nil {
|
|
os.Remove(tmpPath)
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgInternalError, "failed to save file")})
|
|
return
|
|
}
|
|
|
|
mimeType := media.DetectMIMEType(origName)
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"path": tmpPath,
|
|
"mime_type": mimeType,
|
|
"filename": origName,
|
|
})
|
|
}
|