mengstack-api/internal/kernel/storage/upload.go
MengStack Dev df809f1045
Some checks failed
CI / Build & Test (push) Failing after 1m31s
feat: user CRUD API + dashboard stats + kernel infrastructure
- Add user management endpoints (list/create/get/update/delete) with pagination and search
- Add dashboard stats endpoint with tenant/user/online counts and growth metrics
- Add tenant resolver middleware for multi-tenant request scoping
- Add i18n kernel with zh/en message files and AcceptLanguage middleware
- Add WebSocket hub/handler for real-time communication
- Add job scheduler kernel with cron support
- Add plugin sandbox for isolated execution
- Add storage kernel (local filesystem)
- Add event bus kernel for pub/sub
- Add cache kernel abstraction
- Add database migration runner and version upgrade checker
- Add rate limiting middleware with Redis backend
- Add SQL migrations for rbac, audit_logs, settings, notifications, examples
- Extend user repository with list/delete/count operations
- Register all module routes with tenant resolver
2026-10-03 03:42:58 +08:00

72 lines
1.5 KiB
Go

package storage
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
var defaultAllowedTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
"image/svg+xml": true,
"application/pdf": true,
}
var defaultMaxSize int64 = 10 << 20
type UploadConfig struct {
FieldName string
MaxSize int64
AllowedTypes map[string]bool
}
func UploadMiddleware(cfg UploadConfig) gin.HandlerFunc {
if cfg.FieldName == "" {
cfg.FieldName = "file"
}
if cfg.MaxSize <= 0 {
cfg.MaxSize = defaultMaxSize
}
if cfg.AllowedTypes == nil {
cfg.AllowedTypes = defaultAllowedTypes
}
return func(c *gin.Context) {
file, header, err := c.Request.FormFile(cfg.FieldName)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "file field required"})
c.Abort()
return
}
if header.Size > cfg.MaxSize {
file.Close()
c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": fmt.Sprintf("file too large (max %d bytes)", cfg.MaxSize)})
c.Abort()
return
}
contentType := header.Header.Get("Content-Type")
if len(cfg.AllowedTypes) > 0 && !cfg.AllowedTypes[contentType] {
file.Close()
c.JSON(http.StatusUnsupportedMediaType, gin.H{"error": fmt.Sprintf("file type not allowed: %s", contentType)})
c.Abort()
return
}
c.Set("upload_file", &File{
Name: header.Filename,
ContentType: contentType,
Size: header.Size,
Reader: file,
})
c.Next()
file.Close()
}
}