This is an automated email from the ASF dual-hosted git repository.

linkinstar pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/answer.git


The following commit(s) were added to refs/heads/dev by this push:
     new def0559a Fix file record and delete files (branding and avatar) (#1335)
def0559a is described below

commit def0559a5513d711d31bbf066fe4442c7ed30c7d
Author: DanielAuerX <72256996+danielau...@users.noreply.github.com>
AuthorDate: Fri May 23 09:07:14 2025 +0200

    Fix file record and delete files (branding and avatar) (#1335)
    
    - [x] create file_record table
    - [x] avatar and branding files are added to file_record
    - [x] branding files are being deleted
    - [x] avatar files are being deleted
    - [x] reload latest avatar (frontend) after backend state is being
    updated
    
    problems addressed in the pr:
    - clean up job fails, because it cannot access file_record table
    - avatar and branding files are not added to the file_record table
    - avatar and branding files are never deleted
    - after an avatar is being updated/deleted, the old file is still being
    requested due to browser caching. This is causing error logs ("no such
    file or directory") in the backend.
    
    cf. conversation in [pr
    1326](https://github.com/apache/answer/pull/1326)
    
    ---------
    
    Co-authored-by: broccoli <lekker.brocc...@gmail.com>
    Co-authored-by: LinkinStars <linkins...@foxmail.com>
---
 cmd/wire_gen.go                                    |  8 ++--
 internal/base/middleware/avatar.go                 |  4 +-
 internal/controller_admin/siteinfo_controller.go   | 14 ++++++-
 internal/migrations/init_data.go                   |  1 +
 internal/repo/file_record/file_record_repo.go      | 15 +++++++
 internal/repo/site_info/siteinfo_repo.go           | 15 +++++++
 internal/repo/user/user_repo.go                    | 15 +++++++
 internal/service/content/user_service.go           | 45 +++++++++++++++++++-
 .../service/file_record/file_record_service.go     | 38 ++++++++++++++++-
 internal/service/siteinfo/siteinfo_service.go      | 49 ++++++++++++++++++++++
 .../service/siteinfo_common/siteinfo_service.go    | 12 ++++++
 internal/service/uploader/upload.go                | 18 ++++++--
 internal/service/user_common/user.go               | 11 +++++
 13 files changed, 232 insertions(+), 13 deletions(-)

diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index 702f60df..f98deb42 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -181,7 +181,9 @@ func initApplication(debug bool, serverConf *conf.Server, 
dbConf *data.Database,
        metaCommonService := metacommon.NewMetaCommonService(metaRepo)
        questionCommon := questioncommon.NewQuestionCommon(questionRepo, 
answerRepo, voteRepo, followRepo, tagCommonService, userCommon, 
collectionCommon, answerCommon, metaCommonService, configService, 
activityQueueService, revisionRepo, siteInfoCommonService, dataData)
        eventQueueService := event_queue.NewEventQueueService()
-       userService := content.NewUserService(userRepo, userActiveActivityRepo, 
activityRepo, emailService, authService, siteInfoCommonService, 
userRoleRelService, userCommon, userExternalLoginService, 
userNotificationConfigRepo, userNotificationConfigService, questionCommon, 
eventQueueService)
+       fileRecordRepo := file_record.NewFileRecordRepo(dataData)
+       fileRecordService := file_record2.NewFileRecordService(fileRecordRepo, 
revisionRepo, serviceConf, siteInfoCommonService, userCommon)
+       userService := content.NewUserService(userRepo, userActiveActivityRepo, 
activityRepo, emailService, authService, siteInfoCommonService, 
userRoleRelService, userCommon, userExternalLoginService, 
userNotificationConfigRepo, userNotificationConfigService, questionCommon, 
eventQueueService, fileRecordService)
        captchaRepo := captcha.NewCaptchaRepo(dataData)
        captchaService := action.NewCaptchaService(captchaRepo)
        userController := controller.NewUserController(authService, 
userService, captchaService, emailService, siteInfoCommonService, 
userNotificationConfigService)
@@ -239,7 +241,7 @@ func initApplication(debug bool, serverConf *conf.Server, 
dbConf *data.Database,
        reasonService := reason2.NewReasonService(reasonRepo)
        reasonController := controller.NewReasonController(reasonService)
        themeController := controller_admin.NewThemeController()
-       siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, 
siteInfoCommonService, emailService, tagCommonService, configService, 
questionCommon)
+       siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, 
siteInfoCommonService, emailService, tagCommonService, configService, 
questionCommon, fileRecordService)
        siteInfoController := 
controller_admin.NewSiteInfoController(siteInfoService)
        controllerSiteInfoController := 
controller.NewSiteInfoController(siteInfoCommonService)
        notificationCommon := 
notificationcommon.NewNotificationCommon(dataData, notificationRepo, 
userCommon, activityRepo, followRepo, objService, notificationQueueService, 
userExternalLoginRepo, siteInfoCommonService)
@@ -248,8 +250,6 @@ func initApplication(debug bool, serverConf *conf.Server, 
dbConf *data.Database,
        notificationController := 
controller.NewNotificationController(notificationService, rankService)
        dashboardService := dashboard.NewDashboardService(questionRepo, 
answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, 
siteInfoCommonService, serviceConf, reviewService, revisionRepo, dataData)
        dashboardController := 
controller.NewDashboardController(dashboardService)
-       fileRecordRepo := file_record.NewFileRecordRepo(dataData)
-       fileRecordService := file_record2.NewFileRecordService(fileRecordRepo, 
revisionRepo, serviceConf, siteInfoCommonService)
        uploaderService := uploader.NewUploaderService(serviceConf, 
siteInfoCommonService, fileRecordService)
        uploadController := controller.NewUploadController(uploaderService)
        activityActivityRepo := activity.NewActivityRepo(dataData, 
configService)
diff --git a/internal/base/middleware/avatar.go 
b/internal/base/middleware/avatar.go
index 1d246417..98430638 100644
--- a/internal/base/middleware/avatar.go
+++ b/internal/base/middleware/avatar.go
@@ -21,6 +21,7 @@ package middleware
 
 import (
        "fmt"
+       "net/http"
        "net/url"
        "os"
        "path"
@@ -62,7 +63,8 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
                                filePath, err = 
am.uploaderService.AvatarThumbFile(ctx, filename, size)
                                if err != nil {
                                        log.Error(err)
-                                       ctx.Abort()
+                                       ctx.AbortWithStatus(http.StatusNotFound)
+                                       return
                                }
                        }
                        avatarFile, err := os.ReadFile(filePath)
diff --git a/internal/controller_admin/siteinfo_controller.go 
b/internal/controller_admin/siteinfo_controller.go
index cd1b2ab6..8a92daba 100644
--- a/internal/controller_admin/siteinfo_controller.go
+++ b/internal/controller_admin/siteinfo_controller.go
@@ -28,6 +28,7 @@ import (
        "github.com/apache/answer/internal/schema"
        "github.com/apache/answer/internal/service/siteinfo"
        "github.com/gin-gonic/gin"
+       "github.com/segmentfault/pacman/log"
 )
 
 // SiteInfoController site info controller
@@ -274,8 +275,17 @@ func (sc *SiteInfoController) UpdateBranding(ctx 
*gin.Context) {
        if handler.BindAndCheck(ctx, req) {
                return
        }
-       err := sc.siteInfoService.SaveSiteBranding(ctx, req)
-       handler.HandleResponse(ctx, err, nil)
+       currentBranding, getBrandingErr := 
sc.siteInfoService.GetSiteBranding(ctx)
+       if getBrandingErr == nil {
+               cleanUpErr := 
sc.siteInfoService.CleanUpRemovedBrandingFiles(ctx, req, currentBranding)
+               if cleanUpErr != nil {
+                       log.Errorf("failed to clean up removed branding 
file(s): %v", cleanUpErr)
+               }
+       } else {
+               log.Errorf("failed to get current site branding: %v", 
getBrandingErr)
+       }
+       saveErr := sc.siteInfoService.SaveSiteBranding(ctx, req)
+       handler.HandleResponse(ctx, saveErr, nil)
 }
 
 // UpdateSiteWrite update site write info
diff --git a/internal/migrations/init_data.go b/internal/migrations/init_data.go
index 8b853c4d..96151625 100644
--- a/internal/migrations/init_data.go
+++ b/internal/migrations/init_data.go
@@ -74,6 +74,7 @@ var (
                &entity.Badge{},
                &entity.BadgeGroup{},
                &entity.BadgeAward{},
+               &entity.FileRecord{},
                &entity.PluginKVStorage{},
        }
 
diff --git a/internal/repo/file_record/file_record_repo.go 
b/internal/repo/file_record/file_record_repo.go
index ed081be4..ce486c7a 100644
--- a/internal/repo/file_record/file_record_repo.go
+++ b/internal/repo/file_record/file_record_repo.go
@@ -82,3 +82,18 @@ func (fr *fileRecordRepo) UpdateFileRecord(ctx 
context.Context, fileRecord *enti
        }
        return
 }
+
+// GetFileRecordByURL gets a file record by its url
+func (fr *fileRecordRepo) GetFileRecordByURL(ctx context.Context, fileURL 
string) (record *entity.FileRecord, err error) {
+       record = &entity.FileRecord{}
+       session := fr.data.DB.Context(ctx)
+       exists, err := session.Where("file_url = ? AND status = ?", fileURL, 
entity.FileRecordStatusAvailable).Get(record)
+       if err != nil {
+               err = 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+               return
+       }
+       if !exists {
+               return
+       }
+       return record, nil
+}
diff --git a/internal/repo/site_info/siteinfo_repo.go 
b/internal/repo/site_info/siteinfo_repo.go
index 420e483f..5f95b748 100644
--- a/internal/repo/site_info/siteinfo_repo.go
+++ b/internal/repo/site_info/siteinfo_repo.go
@@ -101,3 +101,18 @@ func (sr *siteInfoRepo) setCache(ctx context.Context, 
siteType string, siteInfo
                log.Error(err)
        }
 }
+
+func (sr *siteInfoRepo) IsBrandingFileUsed(ctx context.Context, filePath 
string) (bool, error) {
+       siteInfo := &entity.SiteInfo{}
+       count, err := sr.data.DB.Context(ctx).
+               Table("site_info").
+               Where(builder.Eq{"type": "branding"}).
+               And(builder.Like{"content", "%" + filePath + "%"}).
+               Count(&siteInfo)
+
+       if err != nil {
+               return false, 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+       }
+
+       return count > 0, nil
+}
diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go
index f56e00b8..a85cd79a 100644
--- a/internal/repo/user/user_repo.go
+++ b/internal/repo/user/user_repo.go
@@ -33,6 +33,7 @@ import (
        "github.com/apache/answer/plugin"
        "github.com/segmentfault/pacman/errors"
        "github.com/segmentfault/pacman/log"
+       "xorm.io/builder"
        "xorm.io/xorm"
 )
 
@@ -380,3 +381,17 @@ func decorateByUserCenterUser(original *entity.User, 
ucUser *plugin.UserCenterBa
                original.Status = int(ucUser.Status)
        }
 }
+
+func (ur *userRepo) IsAvatarFileUsed(ctx context.Context, filePath string) 
(bool, error) {
+       user := &entity.User{}
+       count, err := ur.data.DB.Context(ctx).
+               Table("user").
+               Where(builder.Like{"avatar", "%" + filePath + "%"}).
+               Count(&user)
+
+       if err != nil {
+               return false, 
errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+       }
+
+       return count > 0, nil
+}
diff --git a/internal/service/content/user_service.go 
b/internal/service/content/user_service.go
index b59790c1..ece3a86d 100644
--- a/internal/service/content/user_service.go
+++ b/internal/service/content/user_service.go
@@ -23,9 +23,10 @@ import (
        "context"
        "encoding/json"
        "fmt"
+       "time"
+
        "github.com/apache/answer/internal/service/event_queue"
        "github.com/apache/answer/pkg/token"
-       "time"
 
        "github.com/apache/answer/internal/base/constant"
        questioncommon 
"github.com/apache/answer/internal/service/question_common"
@@ -41,6 +42,7 @@ import (
        "github.com/apache/answer/internal/service/activity_common"
        "github.com/apache/answer/internal/service/auth"
        "github.com/apache/answer/internal/service/export"
+       "github.com/apache/answer/internal/service/file_record"
        "github.com/apache/answer/internal/service/role"
        "github.com/apache/answer/internal/service/siteinfo_common"
        usercommon "github.com/apache/answer/internal/service/user_common"
@@ -67,6 +69,7 @@ type UserService struct {
        userNotificationConfigService 
*user_notification_config.UserNotificationConfigService
        questionService               *questioncommon.QuestionCommon
        eventQueueService             event_queue.EventQueueService
+       fileRecordService             *file_record.FileRecordService
 }
 
 func NewUserService(userRepo usercommon.UserRepo,
@@ -82,6 +85,7 @@ func NewUserService(userRepo usercommon.UserRepo,
        userNotificationConfigService 
*user_notification_config.UserNotificationConfigService,
        questionService *questioncommon.QuestionCommon,
        eventQueueService event_queue.EventQueueService,
+       fileRecordService *file_record.FileRecordService,
 ) *UserService {
        return &UserService{
                userCommonService:             userCommonService,
@@ -97,6 +101,7 @@ func NewUserService(userRepo usercommon.UserRepo,
                userNotificationConfigService: userNotificationConfigService,
                questionService:               questionService,
                eventQueueService:             eventQueueService,
+               fileRecordService:             fileRecordService,
        }
 }
 
@@ -355,6 +360,9 @@ func (us *UserService) UpdateInfo(ctx context.Context, req 
*schema.UpdateInfoReq
        }
 
        cond := us.formatUserInfoForUpdateInfo(oldUserInfo, req, siteUsers)
+
+       us.cleanUpRemovedAvatar(ctx, oldUserInfo.Avatar, cond.Avatar)
+
        err = us.userRepo.UpdateInfo(ctx, cond)
        if err != nil {
                return nil, err
@@ -363,6 +371,41 @@ func (us *UserService) UpdateInfo(ctx context.Context, req 
*schema.UpdateInfoReq
        return nil, err
 }
 
+func (us *UserService) cleanUpRemovedAvatar(
+       ctx context.Context,
+       oldAvatarJSON string,
+       newAvatarJSON string,
+) {
+       if oldAvatarJSON == newAvatarJSON {
+               return
+       }
+
+       var oldAvatar, newAvatar schema.AvatarInfo
+
+       _ = json.Unmarshal([]byte(oldAvatarJSON), &oldAvatar)
+       _ = json.Unmarshal([]byte(newAvatarJSON), &newAvatar)
+
+       if len(oldAvatar.Custom) == 0 {
+               return
+       }
+
+       // clean up if old is custom and it's either removed or replaced
+       if oldAvatar.Custom != newAvatar.Custom {
+               fileRecord, err := us.fileRecordService.GetFileRecordByURL(ctx, 
oldAvatar.Custom)
+               if err != nil {
+                       log.Error(err)
+                       return
+               }
+               if fileRecord == nil {
+                       log.Warn("no file record found for old avatar url:", 
oldAvatar.Custom)
+                       return
+               }
+               if err := us.fileRecordService.DeleteAndMoveFileRecord(ctx, 
fileRecord); err != nil {
+                       log.Error(err)
+               }
+       }
+}
+
 func (us *UserService) formatUserInfoForUpdateInfo(
        oldUserInfo *entity.User, req *schema.UpdateInfoRequest, siteUsersConf 
*schema.SiteUsersResp) *entity.User {
        avatar, _ := json.Marshal(req.Avatar)
diff --git a/internal/service/file_record/file_record_service.go 
b/internal/service/file_record/file_record_service.go
index abb98376..29097ba8 100644
--- a/internal/service/file_record/file_record_service.go
+++ b/internal/service/file_record/file_record_service.go
@@ -24,6 +24,7 @@ import (
        "fmt"
        "os"
        "path/filepath"
+       "strings"
        "time"
 
        "github.com/apache/answer/internal/base/constant"
@@ -31,6 +32,7 @@ import (
        "github.com/apache/answer/internal/service/revision"
        "github.com/apache/answer/internal/service/service_config"
        "github.com/apache/answer/internal/service/siteinfo_common"
+       usercommon "github.com/apache/answer/internal/service/user_common"
        "github.com/apache/answer/pkg/checker"
        "github.com/apache/answer/pkg/dir"
        "github.com/apache/answer/pkg/writer"
@@ -44,6 +46,7 @@ type FileRecordRepo interface {
        GetFileRecordPage(ctx context.Context, page, pageSize int, cond 
*entity.FileRecord) (
                fileRecordList []*entity.FileRecord, total int64, err error)
        DeleteFileRecord(ctx context.Context, id int) (err error)
+       GetFileRecordByURL(ctx context.Context, fileURL string) (record 
*entity.FileRecord, err error)
 }
 
 // FileRecordService file record service
@@ -52,6 +55,7 @@ type FileRecordService struct {
        revisionRepo    revision.RevisionRepo
        serviceConfig   *service_config.ServiceConfig
        siteInfoService siteinfo_common.SiteInfoCommonService
+       userService     *usercommon.UserCommon
 }
 
 // NewFileRecordService new file record service
@@ -60,12 +64,14 @@ func NewFileRecordService(
        revisionRepo revision.RevisionRepo,
        serviceConfig *service_config.ServiceConfig,
        siteInfoService siteinfo_common.SiteInfoCommonService,
+       userService *usercommon.UserCommon,
 ) *FileRecordService {
        return &FileRecordService{
                fileRecordRepo:  fileRecordRepo,
                revisionRepo:    revisionRepo,
                serviceConfig:   serviceConfig,
                siteInfoService: siteInfoService,
+               userService:     userService,
        }
 }
 
@@ -104,6 +110,21 @@ func (fs *FileRecordService) CleanOrphanUploadFiles(ctx 
context.Context) {
                        if fileRecord.CreatedAt.AddDate(0, 0, 
2).After(time.Now()) {
                                continue
                        }
+                       if isBrandingOrAvatarFile(fileRecord.FilePath) {
+                               if strings.Contains(fileRecord.FilePath, 
constant.BrandingSubPath+"/") {
+                                       if 
fs.siteInfoService.IsBrandingFileUsed(ctx, fileRecord.FilePath) {
+                                               continue
+                                       }
+                               } else if strings.Contains(fileRecord.FilePath, 
constant.AvatarSubPath+"/") {
+                                       if fs.userService.IsAvatarFileUsed(ctx, 
fileRecord.FilePath) {
+                                               continue
+                                       }
+                               }
+                               if err := fs.DeleteAndMoveFileRecord(ctx, 
fileRecord); err != nil {
+                                       log.Error(err)
+                               }
+                               continue
+                       }
                        if checker.IsNotZeroString(fileRecord.ObjectID) {
                                _, exist, err := 
fs.revisionRepo.GetLastRevisionByObjectID(ctx, fileRecord.ObjectID)
                                if err != nil {
@@ -129,7 +150,7 @@ func (fs *FileRecordService) CleanOrphanUploadFiles(ctx 
context.Context) {
                                }
                        }
                        // Delete and move the file record
-                       if err := fs.deleteAndMoveFileRecord(ctx, fileRecord); 
err != nil {
+                       if err := fs.DeleteAndMoveFileRecord(ctx, fileRecord); 
err != nil {
                                log.Error(err)
                        }
                }
@@ -137,6 +158,10 @@ func (fs *FileRecordService) CleanOrphanUploadFiles(ctx 
context.Context) {
        }
 }
 
+func isBrandingOrAvatarFile(filePath string) bool {
+       return strings.Contains(filePath, constant.BrandingSubPath+"/") || 
strings.Contains(filePath, constant.AvatarSubPath+"/")
+}
+
 func (fs *FileRecordService) PurgeDeletedFiles(ctx context.Context) {
        deletedPath := filepath.Join(fs.serviceConfig.UploadPath, 
constant.DeletedSubPath)
        log.Infof("purge deleted files: %s", deletedPath)
@@ -152,7 +177,7 @@ func (fs *FileRecordService) PurgeDeletedFiles(ctx 
context.Context) {
        return
 }
 
-func (fs *FileRecordService) deleteAndMoveFileRecord(ctx context.Context, 
fileRecord *entity.FileRecord) error {
+func (fs *FileRecordService) DeleteAndMoveFileRecord(ctx context.Context, 
fileRecord *entity.FileRecord) error {
        // Delete the file record
        if err := fs.fileRecordRepo.DeleteFileRecord(ctx, fileRecord.ID); err 
!= nil {
                return fmt.Errorf("delete file record error: %v", err)
@@ -170,3 +195,12 @@ func (fs *FileRecordService) deleteAndMoveFileRecord(ctx 
context.Context, fileRe
        log.Debugf("delete and move file: %s", fileRecord.FileURL)
        return nil
 }
+
+func (fs *FileRecordService) GetFileRecordByURL(ctx context.Context, fileURL 
string) (record *entity.FileRecord, err error) {
+       record, err = fs.fileRecordRepo.GetFileRecordByURL(ctx, fileURL)
+       if err != nil {
+               log.Errorf("error retrieving file record by URL: %v", err)
+               return
+       }
+       return
+}
diff --git a/internal/service/siteinfo/siteinfo_service.go 
b/internal/service/siteinfo/siteinfo_service.go
index 5141faee..a0f4891c 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -22,6 +22,7 @@ package siteinfo
 import (
        "context"
        "encoding/json"
+       errpkg "errors"
        "fmt"
        "strings"
 
@@ -33,6 +34,7 @@ import (
        "github.com/apache/answer/internal/schema"
        "github.com/apache/answer/internal/service/config"
        "github.com/apache/answer/internal/service/export"
+       "github.com/apache/answer/internal/service/file_record"
        questioncommon 
"github.com/apache/answer/internal/service/question_common"
        "github.com/apache/answer/internal/service/siteinfo_common"
        tagcommon "github.com/apache/answer/internal/service/tag_common"
@@ -49,6 +51,7 @@ type SiteInfoService struct {
        tagCommonService      *tagcommon.TagCommonService
        configService         *config.ConfigService
        questioncommon        *questioncommon.QuestionCommon
+       fileRecordService     *file_record.FileRecordService
 }
 
 func NewSiteInfoService(
@@ -58,6 +61,7 @@ func NewSiteInfoService(
        tagCommonService *tagcommon.TagCommonService,
        configService *config.ConfigService,
        questioncommon *questioncommon.QuestionCommon,
+       fileRecordService *file_record.FileRecordService,
 
 ) *SiteInfoService {
        plugin.RegisterGetSiteURLFunc(func() string {
@@ -76,6 +80,7 @@ func NewSiteInfoService(
                tagCommonService:      tagCommonService,
                configService:         configService,
                questioncommon:        questioncommon,
+               fileRecordService:     fileRecordService,
        }
 }
 
@@ -438,3 +443,47 @@ func (s *SiteInfoService) UpdatePrivilegesConfig(ctx 
context.Context, req *schem
        }
        return
 }
+
+func (s *SiteInfoService) CleanUpRemovedBrandingFiles(
+       ctx context.Context,
+       newBranding *schema.SiteBrandingReq,
+       currentBranding *schema.SiteBrandingResp,
+) error {
+       var allErrors []error
+       currentFiles := map[string]string{
+               "logo":        currentBranding.Logo,
+               "mobile_logo": currentBranding.MobileLogo,
+               "square_icon": currentBranding.SquareIcon,
+               "favicon":     currentBranding.Favicon,
+       }
+
+       newFiles := map[string]string{
+               "logo":        newBranding.Logo,
+               "mobile_logo": newBranding.MobileLogo,
+               "square_icon": newBranding.SquareIcon,
+               "favicon":     newBranding.Favicon,
+       }
+
+       for key, currentFile := range currentFiles {
+               newFile := newFiles[key]
+               if currentFile != "" && currentFile != newFile {
+                       fileRecord, err := 
s.fileRecordService.GetFileRecordByURL(ctx, currentFile)
+                       if err != nil {
+                               allErrors = append(allErrors, err)
+                               continue
+                       }
+                       if fileRecord == nil {
+                               err := errpkg.New("file record is nil for key " 
+ key)
+                               allErrors = append(allErrors, err)
+                               continue
+                       }
+                       if err := 
s.fileRecordService.DeleteAndMoveFileRecord(ctx, fileRecord); err != nil {
+                               allErrors = append(allErrors, err)
+                       }
+               }
+       }
+       if len(allErrors) > 0 {
+               return errpkg.Join(allErrors...)
+       }
+       return nil
+}
diff --git a/internal/service/siteinfo_common/siteinfo_service.go 
b/internal/service/siteinfo_common/siteinfo_service.go
index f715bc9b..0c896c2b 100644
--- a/internal/service/siteinfo_common/siteinfo_service.go
+++ b/internal/service/siteinfo_common/siteinfo_service.go
@@ -35,6 +35,7 @@ import (
 type SiteInfoRepo interface {
        SaveByType(ctx context.Context, siteType string, data *entity.SiteInfo) 
(err error)
        GetByType(ctx context.Context, siteType string) (siteInfo 
*entity.SiteInfo, exist bool, err error)
+       IsBrandingFileUsed(ctx context.Context, filePath string) (bool, error)
 }
 
 // siteInfoCommonService site info common service
@@ -56,6 +57,7 @@ type SiteInfoCommonService interface {
        GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err 
error)
        GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error)
        GetSiteInfoByType(ctx context.Context, siteType string, resp 
interface{}) (err error)
+       IsBrandingFileUsed(ctx context.Context, filePath string) bool
 }
 
 // NewSiteInfoCommonService new site info common service
@@ -233,3 +235,13 @@ func (s *siteInfoCommonService) GetSiteInfoByType(ctx 
context.Context, siteType
        _ = json.Unmarshal([]byte(siteInfo.Content), resp)
        return nil
 }
+
+func (s *siteInfoCommonService) IsBrandingFileUsed(ctx context.Context, 
filePath string) bool {
+       used, err := s.siteInfoRepo.IsBrandingFileUsed(ctx, filePath)
+       if err != nil {
+               log.Errorf("error checking if branding file is used: %v", err)
+               // will try again with the next clean up
+               return true
+       }
+       return used
+}
diff --git a/internal/service/uploader/upload.go 
b/internal/service/uploader/upload.go
index d5cc2dfb..2ae5369d 100644
--- a/internal/service/uploader/upload.go
+++ b/internal/service/uploader/upload.go
@@ -127,7 +127,13 @@ func (us *uploaderService) UploadAvatarFile(ctx 
*gin.Context, userID string) (ur
 
        newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
        avatarFilePath := path.Join(constant.AvatarSubPath, newFilename)
-       return us.uploadImageFile(ctx, fileHeader, avatarFilePath)
+       url, err = us.uploadImageFile(ctx, fileHeader, avatarFilePath)
+       if err != nil {
+               return "", err
+       }
+       us.fileRecordService.AddFileRecord(ctx, userID, avatarFilePath, url, 
string(plugin.UserAvatar))
+       return url, nil
+
 }
 
 func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, 
size int) (url string, err error) {
@@ -149,7 +155,7 @@ func (us *uploaderService) AvatarThumbFile(ctx 
*gin.Context, fileName string, si
        filePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, 
constant.AvatarSubPath, fileName)
        avatarFile, err = os.ReadFile(filePath)
        if err != nil {
-               return "", 
errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+               return "", errors.NotFound(reason.UnknownError).WithError(err)
        }
        reader := bytes.NewReader(avatarFile)
        img, err := imaging.Decode(reader)
@@ -282,7 +288,13 @@ func (us *uploaderService) UploadBrandingFile(ctx 
*gin.Context, userID string) (
 
        newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
        avatarFilePath := path.Join(constant.BrandingSubPath, newFilename)
-       return us.uploadImageFile(ctx, fileHeader, avatarFilePath)
+       url, err = us.uploadImageFile(ctx, fileHeader, avatarFilePath)
+       if err != nil {
+               return "", err
+       }
+       us.fileRecordService.AddFileRecord(ctx, userID, avatarFilePath, url, 
string(plugin.AdminBranding))
+       return url, nil
+
 }
 
 func (us *uploaderService) uploadImageFile(ctx *gin.Context, file 
*multipart.FileHeader, fileSubPath string) (
diff --git a/internal/service/user_common/user.go 
b/internal/service/user_common/user.go
index 7f50eafe..3df99261 100644
--- a/internal/service/user_common/user.go
+++ b/internal/service/user_common/user.go
@@ -60,6 +60,7 @@ type UserRepo interface {
        GetByEmail(ctx context.Context, email string) (userInfo *entity.User, 
exist bool, err error)
        GetUserCount(ctx context.Context) (count int64, err error)
        SearchUserListByName(ctx context.Context, name string, limit int, 
onlyStaff bool) (userList []*entity.User, err error)
+       IsAvatarFileUsed(ctx context.Context, filePath string) (bool, error)
 }
 
 // UserCommon user service
@@ -245,3 +246,13 @@ func (us *UserCommon) CacheLoginUserInfo(ctx 
context.Context, userID string, use
        }
        return accessToken, userCacheInfo, nil
 }
+
+func (us *UserCommon) IsAvatarFileUsed(ctx context.Context, filePath string) 
bool {
+       used, err := us.userRepo.IsAvatarFileUsed(ctx, filePath)
+       if err != nil {
+               log.Errorf("error checking if branding file is used: %v", err)
+               // will try again with the next clean up
+               return true
+       }
+       return used
+}

Reply via email to