Skip to content

Commit

Permalink
Merge branch 'feat/1.4.2/file' into test
Browse files Browse the repository at this point in the history
  • Loading branch information
LinkinStars committed Nov 22, 2024
2 parents 85f792f + 8616862 commit d99bfcc
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 45 deletions.
6 changes: 6 additions & 0 deletions internal/base/constant/site_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ const (
const (
EmailConfigKey = "email.config"
)

const (
DefaultMaxImageMegapixel = 40 * 1000 * 1000
DefaultMaxImageSize = 4 * 1024 * 1024
DefaultMaxAttachmentSize = 8 * 1024 * 1024
)
6 changes: 5 additions & 1 deletion internal/controller/upload_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
const (
// file is uploaded by markdown(or something else) editor
fileFromPost = "post"
// file is used to upload the post attachment
fileFromPostAttachment = "post_attachment"
// file is used to change the user's avatar
fileFromAvatar = "avatar"
// file is logo/icon images
Expand All @@ -56,7 +58,7 @@ func NewUploadController(uploaderService uploader.UploaderService) *UploadContro
// @Tags Upload
// @Accept multipart/form-data
// @Security ApiKeyAuth
// @Param source formData string true "identify the source of the file upload" Enums(post, avatar, branding)
// @Param source formData string true "identify the source of the file upload" Enums(post, post_attachment, avatar, branding)
// @Param file formData file true "file"
// @Success 200 {object} handler.RespBody{data=string}
// @Router /answer/api/v1/file [post]
Expand All @@ -74,6 +76,8 @@ func (uc *UploadController) UploadFile(ctx *gin.Context) {
url, err = uc.uploaderService.UploadPostFile(ctx)
case fileFromBranding:
url, err = uc.uploaderService.UploadBrandingFile(ctx)
case fileFromPostAttachment:
url, err = uc.uploaderService.UploadPostAttachment(ctx)
default:
handler.HandleResponse(ctx, errors.BadRequest(reason.UploadFileSourceUnsupported), nil)
return
Expand Down
6 changes: 5 additions & 1 deletion internal/migrations/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@ func (m *Mentor) initSiteInfoPrivilegeRank() {

func (m *Mentor) initSiteInfoWrite() {
writeData := map[string]interface{}{
"restrict_answer": true,
"restrict_answer": true,
"max_image_size": 4,
"max_attachment_size": 8,
"max_image_megapixel": 40,
"authorized_extensions": []string{"jpg", "jpeg", "png", "gif", "webp"},
}
writeDataBytes, _ := json.Marshal(writeData)
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Expand Down
38 changes: 38 additions & 0 deletions internal/migrations/v24.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,50 @@ package migrations

import (
"context"
"encoding/json"
"fmt"
"github.com/apache/incubator-answer/internal/base/constant"
"github.com/apache/incubator-answer/internal/entity"
"github.com/apache/incubator-answer/internal/schema"

"xorm.io/xorm"
)

func addQuestionLinkedCount(ctx context.Context, x *xorm.Engine) error {
writeSiteInfo := &entity.SiteInfo{
Type: constant.SiteTypeWrite,
}
exist, err := x.Context(ctx).Get(writeSiteInfo)
if err != nil {
return fmt.Errorf("get config failed: %w", err)
}
if exist {
type OldSiteWriteReq struct {
RestrictAnswer bool `json:"restrict_answer"`
RequiredTag bool `json:"required_tag"`
RecommendTags []*schema.SiteWriteTag `json:"recommend_tags"`
ReservedTags []*schema.SiteWriteTag `json:"reserved_tags"`
MaxImageSize int `json:"max_image_size"`
MaxAttachmentSize int `json:"max_attachment_size"`
MaxImageMegapixel int `json:"max_image_megapixel"`
AuthorizedImageExtensions []string `json:"authorized_image_extensions"`
AuthorizedAttachmentExtensions []string `json:"authorized_attachment_extensions"`
}
content := &OldSiteWriteReq{}
_ = json.Unmarshal([]byte(writeSiteInfo.Content), content)
content.MaxImageSize = 4
content.MaxAttachmentSize = 8
content.MaxImageMegapixel = 40
content.AuthorizedImageExtensions = []string{"jpg", "jpeg", "png", "gif", "webp"}
content.AuthorizedAttachmentExtensions = []string{}
data, _ := json.Marshal(content)
writeSiteInfo.Content = string(data)
_, err = x.Context(ctx).ID(writeSiteInfo.ID).Cols("content").Update(writeSiteInfo)
if err != nil {
return fmt.Errorf("update site info failed: %w", err)
}
}

type Question struct {
LinkedCount int `xorm:"not null default 0 INT(11) linked_count"`
}
Expand Down
36 changes: 31 additions & 5 deletions internal/schema/siteinfo_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,37 @@ type SiteBrandingReq struct {

// SiteWriteReq site write request
type SiteWriteReq struct {
RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"`
RequiredTag bool `validate:"omitempty" json:"required_tag"`
RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"`
ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"`
UserID string `json:"-"`
RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"`
RequiredTag bool `validate:"omitempty" json:"required_tag"`
RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"`
ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"`
MaxImageSize int `validate:"omitempty,gt=0" json:"max_image_size"`
MaxAttachmentSize int `validate:"omitempty,gt=0" json:"max_attachment_size"`
MaxImageMegapixel int `validate:"omitempty,gt=0" json:"max_image_megapixel"`
AuthorizedImageExtensions []string `validate:"omitempty" json:"authorized_image_extensions"`
AuthorizedAttachmentExtensions []string `validate:"omitempty" json:"authorized_attachment_extensions"`
UserID string `json:"-"`
}

func (s *SiteWriteResp) GetMaxImageSize() int64 {
if s.MaxImageSize <= 0 {
return constant.DefaultMaxImageSize
}
return int64(s.MaxImageSize) * 1024 * 1024
}

func (s *SiteWriteResp) GetMaxAttachmentSize() int64 {
if s.MaxAttachmentSize <= 0 {
return constant.DefaultMaxAttachmentSize
}
return int64(s.MaxAttachmentSize) * 1024 * 1024
}

func (s *SiteWriteResp) GetMaxImageMegapixel() int {
if s.MaxImageMegapixel <= 0 {
return constant.DefaultMaxImageMegapixel
}
return s.MaxImageMegapixel * 1000 * 1000
}

// SiteWriteTag site write response tag
Expand Down
79 changes: 69 additions & 10 deletions internal/service/uploader/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var (
type UploaderService interface {
UploadAvatarFile(ctx *gin.Context) (url string, err error)
UploadPostFile(ctx *gin.Context) (url string, err error)
UploadPostAttachment(ctx *gin.Context) (url string, err error)
UploadBrandingFile(ctx *gin.Context) (url string, err error)
AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error)
}
Expand Down Expand Up @@ -118,7 +119,7 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err e

newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
avatarFilePath := path.Join(avatarSubPath, newFilename)
return us.uploadFile(ctx, fileHeader, avatarFilePath)
return us.uploadImageFile(ctx, fileHeader, avatarFilePath)
}

func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error) {
Expand Down Expand Up @@ -183,21 +184,56 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context) (
return url, nil
}

// max size
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
siteWrite, err := us.siteInfoService.GetSiteWrite(ctx)
if err != nil {
return "", err
}

ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize())
file, fileHeader, err := ctx.Request.FormFile("file")
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
defer file.Close()
if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteWrite.AuthorizedImageExtensions) {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}

fileExt := strings.ToLower(path.Ext(fileHeader.Filename))
if _, ok := plugin.DefaultFileTypeCheckMapping[plugin.UserPost][fileExt]; !ok {
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
avatarFilePath := path.Join(postSubPath, newFilename)
return us.uploadImageFile(ctx, fileHeader, avatarFilePath)
}

func (us *uploaderService) UploadPostAttachment(ctx *gin.Context) (
url string, err error) {
url, err = us.tryToUploadByPlugin(ctx, plugin.UserPostAttachment)
if err != nil {
return "", err
}
if len(url) > 0 {
return url, nil
}

resp, err := us.siteInfoService.GetSiteWrite(ctx)
if err != nil {
return "", err
}

ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, resp.GetMaxAttachmentSize())
file, fileHeader, err := ctx.Request.FormFile("file")
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
defer file.Close()
if checker.IsUnAuthorizedExtension(fileHeader.Filename, resp.AuthorizedAttachmentExtensions) {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}

fileExt := strings.ToLower(path.Ext(fileHeader.Filename))
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
avatarFilePath := path.Join(postSubPath, newFilename)
return us.uploadFile(ctx, fileHeader, avatarFilePath)
return us.uploadAttachmentFile(ctx, fileHeader, avatarFilePath)
}

func (us *uploaderService) UploadBrandingFile(ctx *gin.Context) (
Expand All @@ -210,8 +246,12 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context) (
return url, nil
}

// max size
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
siteWrite, err := us.siteInfoService.GetSiteWrite(ctx)
if err != nil {
return "", err
}

ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize())
file, fileHeader, err := ctx.Request.FormFile("file")
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
Expand All @@ -224,15 +264,19 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context) (

newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
avatarFilePath := path.Join(brandingSubPath, newFilename)
return us.uploadFile(ctx, fileHeader, avatarFilePath)
return us.uploadImageFile(ctx, fileHeader, avatarFilePath)
}

func (us *uploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
url string, err error) {
siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
return "", err
}
siteWrite, err := us.siteInfoService.GetSiteWrite(ctx)
if err != nil {
return "", err
}
filePath := path.Join(us.serviceConfig.UploadPath, fileSubPath)
if err := ctx.SaveUploadedFile(file, filePath); err != nil {
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
Expand All @@ -244,7 +288,7 @@ func (us *uploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHead
}
defer src.Close()

if !checker.IsSupportedImageFile(filePath) {
if !checker.DecodeAndCheckImageFile(filePath, siteWrite.GetMaxImageMegapixel()) {
return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat)
}

Expand All @@ -256,6 +300,21 @@ func (us *uploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHead
return url, nil
}

func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
url string, err error) {
siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
return "", err
}
filePath := path.Join(us.serviceConfig.UploadPath, fileSubPath)
if err := ctx.SaveUploadedFile(file, filePath); err != nil {
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}

url = fmt.Sprintf("%s/uploads/%s", siteGeneral.SiteUrl, fileSubPath)
return url, nil
}

func (us *uploaderService) tryToUploadByPlugin(ctx *gin.Context, source plugin.UploadSource) (
url string, err error) {
_ = plugin.CallStorage(func(fn plugin.Storage) error {
Expand Down
Loading

0 comments on commit d99bfcc

Please sign in to comment.