[feature/backend] overall enhancement of image uploading
All checks were successful
Build Backend / Build Docker Image (push) Successful in 5m3s
All checks were successful
Build Backend / Build Docker Image (push) Successful in 5m3s
This commit is contained in:
parent
6e1be3d513
commit
3e6181e578
13 changed files with 740 additions and 314 deletions
|
@ -25,8 +25,9 @@ func NewHandler(cfg *config.Config, service service.Service) *Handler {
|
|||
}
|
||||
|
||||
// RegisterRoutes registers all the routes
|
||||
func (h *Handler) RegisterRoutes(r *gin.Engine) {
|
||||
api := r.Group("/api/v1")
|
||||
func (h *Handler) RegisterRoutes(router *gin.Engine) {
|
||||
// API routes
|
||||
api := router.Group("/api/v1")
|
||||
{
|
||||
// Auth routes
|
||||
auth := api.Group("/auth")
|
||||
|
@ -93,6 +94,9 @@ func (h *Handler) RegisterRoutes(r *gin.Engine) {
|
|||
media.DELETE("/:id", h.DeleteMedia)
|
||||
}
|
||||
}
|
||||
|
||||
// Public media files
|
||||
router.GET("/media/:year/:month/:filename", h.GetMediaFile)
|
||||
}
|
||||
|
||||
// Category handlers
|
||||
|
@ -246,10 +250,10 @@ func (h *Handler) GetPost(c *gin.Context) {
|
|||
contents := make([]gin.H, 0, len(post.Edges.Contents))
|
||||
for _, content := range post.Edges.Contents {
|
||||
contents = append(contents, gin.H{
|
||||
"language_code": content.LanguageCode,
|
||||
"title": content.Title,
|
||||
"language_code": content.LanguageCode,
|
||||
"title": content.Title,
|
||||
"content_markdown": content.ContentMarkdown,
|
||||
"summary": content.Summary,
|
||||
"summary": content.Summary,
|
||||
})
|
||||
}
|
||||
response["edges"].(gin.H)["contents"] = contents
|
||||
|
@ -294,7 +298,7 @@ func (h *Handler) CreatePost(c *gin.Context) {
|
|||
}
|
||||
|
||||
type AddPostContentRequest struct {
|
||||
LanguageCode string `json:"language_code" binding:"required"`
|
||||
LanguageCode string `json:"language_code" binding:"required"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
ContentMarkdown string `json:"content_markdown" binding:"required"`
|
||||
Summary string `json:"summary" binding:"required"`
|
||||
|
@ -326,10 +330,10 @@ func (h *Handler) AddPostContent(c *gin.Context) {
|
|||
"title": content.Title,
|
||||
"content_markdown": content.ContentMarkdown,
|
||||
"language_code": content.LanguageCode,
|
||||
"summary": content.Summary,
|
||||
"summary": content.Summary,
|
||||
"meta_keywords": content.MetaKeywords,
|
||||
"meta_description": content.MetaDescription,
|
||||
"edges": gin.H{},
|
||||
"edges": gin.H{},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -535,11 +539,3 @@ func (h *Handler) AddDailyContent(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusCreated, content)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func stringPtr(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Media handlers
|
||||
|
@ -33,17 +35,29 @@ func (h *Handler) ListMedia(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, media)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": media,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) UploadMedia(c *gin.Context) {
|
||||
// Get user ID from context (set by auth middleware)
|
||||
userID, exists := c.Get("user_id")
|
||||
userIDStr, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert user ID to int
|
||||
userID, err := strconv.Atoi(userIDStr.(string))
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("user_id", fmt.Sprintf("%v", userIDStr)).
|
||||
Msg("Failed to convert user ID to int")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get file from form
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
|
@ -51,38 +65,107 @@ func (h *Handler) UploadMedia(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// 文件大小限制
|
||||
if file.Size > 10*1024*1024 { // 10MB
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "File size exceeds the limit (10MB)"})
|
||||
// 获取文件类型和扩展名
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if contentType == "" {
|
||||
// 如果 Content-Type 为空,尝试从文件扩展名判断
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg":
|
||||
contentType = "image/jpeg"
|
||||
case ".png":
|
||||
contentType = "image/png"
|
||||
case ".gif":
|
||||
contentType = "image/gif"
|
||||
case ".webp":
|
||||
contentType = "image/webp"
|
||||
case ".mp4":
|
||||
contentType = "video/mp4"
|
||||
case ".webm":
|
||||
contentType = "video/webm"
|
||||
case ".mp3":
|
||||
contentType = "audio/mpeg"
|
||||
case ".ogg":
|
||||
contentType = "audio/ogg"
|
||||
case ".wav":
|
||||
contentType = "audio/wav"
|
||||
case ".pdf":
|
||||
contentType = "application/pdf"
|
||||
case ".doc":
|
||||
contentType = "application/msword"
|
||||
case ".docx":
|
||||
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 Content-Type 确定文件类型和限制
|
||||
var maxSize int64
|
||||
var allowedTypes []string
|
||||
var fileType string
|
||||
|
||||
limits := h.cfg.Storage.Upload.Limits
|
||||
switch {
|
||||
case strings.HasPrefix(contentType, "image/"):
|
||||
maxSize = int64(limits.Image.MaxSize) * 1024 * 1024
|
||||
allowedTypes = limits.Image.AllowedTypes
|
||||
fileType = "image"
|
||||
case strings.HasPrefix(contentType, "video/"):
|
||||
maxSize = int64(limits.Video.MaxSize) * 1024 * 1024
|
||||
allowedTypes = limits.Video.AllowedTypes
|
||||
fileType = "video"
|
||||
case strings.HasPrefix(contentType, "audio/"):
|
||||
maxSize = int64(limits.Audio.MaxSize) * 1024 * 1024
|
||||
allowedTypes = limits.Audio.AllowedTypes
|
||||
fileType = "audio"
|
||||
case strings.HasPrefix(contentType, "application/"):
|
||||
maxSize = int64(limits.Document.MaxSize) * 1024 * 1024
|
||||
allowedTypes = limits.Document.AllowedTypes
|
||||
fileType = "document"
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Unsupported file type",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 文件类型限制
|
||||
allowedTypes := map[string]bool{
|
||||
"image/jpeg": true,
|
||||
"image/png": true,
|
||||
"image/gif": true,
|
||||
"video/mp4": true,
|
||||
"video/webm": true,
|
||||
"audio/mpeg": true,
|
||||
"audio/ogg": true,
|
||||
"application/pdf": true,
|
||||
// 检查文件类型是否允许
|
||||
typeAllowed := false
|
||||
for _, allowed := range allowedTypes {
|
||||
if contentType == allowed {
|
||||
typeAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
contentType := file.Header.Get("Content-Type")
|
||||
if _, ok := allowedTypes[contentType]; !ok {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file type"})
|
||||
if !typeAllowed {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("Unsupported %s type: %s", fileType, contentType),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
if file.Size > maxSize {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("File size exceeds the limit (%d MB) for %s files", limits.Image.MaxSize, fileType),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Upload file
|
||||
media, err := h.service.Upload(c.Request.Context(), file, userID.(int))
|
||||
media, err := h.service.Upload(c.Request.Context(), file, userID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to upload media")
|
||||
log.Error().Err(err).
|
||||
Str("filename", file.Filename).
|
||||
Str("content_type", contentType).
|
||||
Int("user_id", userID).
|
||||
Msg("Failed to upload media")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to upload media"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, media)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"data": media,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetMedia(c *gin.Context) {
|
||||
|
@ -101,7 +184,7 @@ func (h *Handler) GetMedia(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Get file content
|
||||
reader, info, err := h.service.GetFile(c.Request.Context(), id)
|
||||
reader, info, err := h.service.GetFile(c.Request.Context(), media.StorageID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get media file")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get media file"})
|
||||
|
@ -122,16 +205,18 @@ func (h *Handler) GetMedia(c *gin.Context) {
|
|||
}
|
||||
|
||||
func (h *Handler) GetMediaFile(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid media ID"})
|
||||
return
|
||||
}
|
||||
year := c.Param("year")
|
||||
month := c.Param("month")
|
||||
filename := c.Param("filename")
|
||||
|
||||
// Get file content
|
||||
reader, info, err := h.service.GetFile(c.Request.Context(), id)
|
||||
reader, info, err := h.service.GetFile(c.Request.Context(), filename) // 直接使用完整的文件名
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get media file")
|
||||
log.Error().Err(err).
|
||||
Str("year", year).
|
||||
Str("month", month).
|
||||
Str("filename", filename).
|
||||
Msg("Failed to get media file")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get media file"})
|
||||
return
|
||||
}
|
||||
|
@ -151,20 +236,33 @@ func (h *Handler) GetMediaFile(c *gin.Context) {
|
|||
|
||||
func (h *Handler) DeleteMedia(c *gin.Context) {
|
||||
// Get user ID from context (set by auth middleware)
|
||||
userID, exists := c.Get("user_id")
|
||||
userIDStr, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert user ID to int
|
||||
userID, err := strconv.Atoi(userIDStr.(string))
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("user_id", fmt.Sprintf("%v", userIDStr)).
|
||||
Msg("Failed to convert user ID to int")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid media ID"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.DeleteMedia(c.Request.Context(), id, userID.(int)); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to delete media")
|
||||
if err := h.service.DeleteMedia(c.Request.Context(), id, userID); err != nil {
|
||||
log.Error().Err(err).
|
||||
Int("media_id", id).
|
||||
Int("user_id", userID).
|
||||
Msg("Failed to delete media")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete media"})
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue