Skip to content

Commit def0559

Browse files
DanielAuerXbroccoliLinkinStars
authored
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](#1326) --------- Co-authored-by: broccoli <[email protected]> Co-authored-by: LinkinStars <[email protected]>
1 parent 4182d9f commit def0559

File tree

13 files changed

+232
-13
lines changed

13 files changed

+232
-13
lines changed

cmd/wire_gen.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/base/middleware/avatar.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package middleware
2121

2222
import (
2323
"fmt"
24+
"net/http"
2425
"net/url"
2526
"os"
2627
"path"
@@ -62,7 +63,8 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
6263
filePath, err = am.uploaderService.AvatarThumbFile(ctx, filename, size)
6364
if err != nil {
6465
log.Error(err)
65-
ctx.Abort()
66+
ctx.AbortWithStatus(http.StatusNotFound)
67+
return
6668
}
6769
}
6870
avatarFile, err := os.ReadFile(filePath)

internal/controller_admin/siteinfo_controller.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/apache/answer/internal/schema"
2929
"github.com/apache/answer/internal/service/siteinfo"
3030
"github.com/gin-gonic/gin"
31+
"github.com/segmentfault/pacman/log"
3132
)
3233

3334
// SiteInfoController site info controller
@@ -274,8 +275,17 @@ func (sc *SiteInfoController) UpdateBranding(ctx *gin.Context) {
274275
if handler.BindAndCheck(ctx, req) {
275276
return
276277
}
277-
err := sc.siteInfoService.SaveSiteBranding(ctx, req)
278-
handler.HandleResponse(ctx, err, nil)
278+
currentBranding, getBrandingErr := sc.siteInfoService.GetSiteBranding(ctx)
279+
if getBrandingErr == nil {
280+
cleanUpErr := sc.siteInfoService.CleanUpRemovedBrandingFiles(ctx, req, currentBranding)
281+
if cleanUpErr != nil {
282+
log.Errorf("failed to clean up removed branding file(s): %v", cleanUpErr)
283+
}
284+
} else {
285+
log.Errorf("failed to get current site branding: %v", getBrandingErr)
286+
}
287+
saveErr := sc.siteInfoService.SaveSiteBranding(ctx, req)
288+
handler.HandleResponse(ctx, saveErr, nil)
279289
}
280290

281291
// UpdateSiteWrite update site write info

internal/migrations/init_data.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ var (
7474
&entity.Badge{},
7575
&entity.BadgeGroup{},
7676
&entity.BadgeAward{},
77+
&entity.FileRecord{},
7778
&entity.PluginKVStorage{},
7879
}
7980

internal/repo/file_record/file_record_repo.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,18 @@ func (fr *fileRecordRepo) UpdateFileRecord(ctx context.Context, fileRecord *enti
8282
}
8383
return
8484
}
85+
86+
// GetFileRecordByURL gets a file record by its url
87+
func (fr *fileRecordRepo) GetFileRecordByURL(ctx context.Context, fileURL string) (record *entity.FileRecord, err error) {
88+
record = &entity.FileRecord{}
89+
session := fr.data.DB.Context(ctx)
90+
exists, err := session.Where("file_url = ? AND status = ?", fileURL, entity.FileRecordStatusAvailable).Get(record)
91+
if err != nil {
92+
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
93+
return
94+
}
95+
if !exists {
96+
return
97+
}
98+
return record, nil
99+
}

internal/repo/site_info/siteinfo_repo.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,18 @@ func (sr *siteInfoRepo) setCache(ctx context.Context, siteType string, siteInfo
101101
log.Error(err)
102102
}
103103
}
104+
105+
func (sr *siteInfoRepo) IsBrandingFileUsed(ctx context.Context, filePath string) (bool, error) {
106+
siteInfo := &entity.SiteInfo{}
107+
count, err := sr.data.DB.Context(ctx).
108+
Table("site_info").
109+
Where(builder.Eq{"type": "branding"}).
110+
And(builder.Like{"content", "%" + filePath + "%"}).
111+
Count(&siteInfo)
112+
113+
if err != nil {
114+
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
115+
}
116+
117+
return count > 0, nil
118+
}

internal/repo/user/user_repo.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/apache/answer/plugin"
3434
"github.com/segmentfault/pacman/errors"
3535
"github.com/segmentfault/pacman/log"
36+
"xorm.io/builder"
3637
"xorm.io/xorm"
3738
)
3839

@@ -380,3 +381,17 @@ func decorateByUserCenterUser(original *entity.User, ucUser *plugin.UserCenterBa
380381
original.Status = int(ucUser.Status)
381382
}
382383
}
384+
385+
func (ur *userRepo) IsAvatarFileUsed(ctx context.Context, filePath string) (bool, error) {
386+
user := &entity.User{}
387+
count, err := ur.data.DB.Context(ctx).
388+
Table("user").
389+
Where(builder.Like{"avatar", "%" + filePath + "%"}).
390+
Count(&user)
391+
392+
if err != nil {
393+
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
394+
}
395+
396+
return count > 0, nil
397+
}

internal/service/content/user_service.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import (
2323
"context"
2424
"encoding/json"
2525
"fmt"
26+
"time"
27+
2628
"github.com/apache/answer/internal/service/event_queue"
2729
"github.com/apache/answer/pkg/token"
28-
"time"
2930

3031
"github.com/apache/answer/internal/base/constant"
3132
questioncommon "github.com/apache/answer/internal/service/question_common"
@@ -41,6 +42,7 @@ import (
4142
"github.com/apache/answer/internal/service/activity_common"
4243
"github.com/apache/answer/internal/service/auth"
4344
"github.com/apache/answer/internal/service/export"
45+
"github.com/apache/answer/internal/service/file_record"
4446
"github.com/apache/answer/internal/service/role"
4547
"github.com/apache/answer/internal/service/siteinfo_common"
4648
usercommon "github.com/apache/answer/internal/service/user_common"
@@ -67,6 +69,7 @@ type UserService struct {
6769
userNotificationConfigService *user_notification_config.UserNotificationConfigService
6870
questionService *questioncommon.QuestionCommon
6971
eventQueueService event_queue.EventQueueService
72+
fileRecordService *file_record.FileRecordService
7073
}
7174

7275
func NewUserService(userRepo usercommon.UserRepo,
@@ -82,6 +85,7 @@ func NewUserService(userRepo usercommon.UserRepo,
8285
userNotificationConfigService *user_notification_config.UserNotificationConfigService,
8386
questionService *questioncommon.QuestionCommon,
8487
eventQueueService event_queue.EventQueueService,
88+
fileRecordService *file_record.FileRecordService,
8589
) *UserService {
8690
return &UserService{
8791
userCommonService: userCommonService,
@@ -97,6 +101,7 @@ func NewUserService(userRepo usercommon.UserRepo,
97101
userNotificationConfigService: userNotificationConfigService,
98102
questionService: questionService,
99103
eventQueueService: eventQueueService,
104+
fileRecordService: fileRecordService,
100105
}
101106
}
102107

@@ -355,6 +360,9 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq
355360
}
356361

357362
cond := us.formatUserInfoForUpdateInfo(oldUserInfo, req, siteUsers)
363+
364+
us.cleanUpRemovedAvatar(ctx, oldUserInfo.Avatar, cond.Avatar)
365+
358366
err = us.userRepo.UpdateInfo(ctx, cond)
359367
if err != nil {
360368
return nil, err
@@ -363,6 +371,41 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq
363371
return nil, err
364372
}
365373

374+
func (us *UserService) cleanUpRemovedAvatar(
375+
ctx context.Context,
376+
oldAvatarJSON string,
377+
newAvatarJSON string,
378+
) {
379+
if oldAvatarJSON == newAvatarJSON {
380+
return
381+
}
382+
383+
var oldAvatar, newAvatar schema.AvatarInfo
384+
385+
_ = json.Unmarshal([]byte(oldAvatarJSON), &oldAvatar)
386+
_ = json.Unmarshal([]byte(newAvatarJSON), &newAvatar)
387+
388+
if len(oldAvatar.Custom) == 0 {
389+
return
390+
}
391+
392+
// clean up if old is custom and it's either removed or replaced
393+
if oldAvatar.Custom != newAvatar.Custom {
394+
fileRecord, err := us.fileRecordService.GetFileRecordByURL(ctx, oldAvatar.Custom)
395+
if err != nil {
396+
log.Error(err)
397+
return
398+
}
399+
if fileRecord == nil {
400+
log.Warn("no file record found for old avatar url:", oldAvatar.Custom)
401+
return
402+
}
403+
if err := us.fileRecordService.DeleteAndMoveFileRecord(ctx, fileRecord); err != nil {
404+
log.Error(err)
405+
}
406+
}
407+
}
408+
366409
func (us *UserService) formatUserInfoForUpdateInfo(
367410
oldUserInfo *entity.User, req *schema.UpdateInfoRequest, siteUsersConf *schema.SiteUsersResp) *entity.User {
368411
avatar, _ := json.Marshal(req.Avatar)

internal/service/file_record/file_record_service.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import (
2424
"fmt"
2525
"os"
2626
"path/filepath"
27+
"strings"
2728
"time"
2829

2930
"github.com/apache/answer/internal/base/constant"
3031
"github.com/apache/answer/internal/entity"
3132
"github.com/apache/answer/internal/service/revision"
3233
"github.com/apache/answer/internal/service/service_config"
3334
"github.com/apache/answer/internal/service/siteinfo_common"
35+
usercommon "github.com/apache/answer/internal/service/user_common"
3436
"github.com/apache/answer/pkg/checker"
3537
"github.com/apache/answer/pkg/dir"
3638
"github.com/apache/answer/pkg/writer"
@@ -44,6 +46,7 @@ type FileRecordRepo interface {
4446
GetFileRecordPage(ctx context.Context, page, pageSize int, cond *entity.FileRecord) (
4547
fileRecordList []*entity.FileRecord, total int64, err error)
4648
DeleteFileRecord(ctx context.Context, id int) (err error)
49+
GetFileRecordByURL(ctx context.Context, fileURL string) (record *entity.FileRecord, err error)
4750
}
4851

4952
// FileRecordService file record service
@@ -52,6 +55,7 @@ type FileRecordService struct {
5255
revisionRepo revision.RevisionRepo
5356
serviceConfig *service_config.ServiceConfig
5457
siteInfoService siteinfo_common.SiteInfoCommonService
58+
userService *usercommon.UserCommon
5559
}
5660

5761
// NewFileRecordService new file record service
@@ -60,12 +64,14 @@ func NewFileRecordService(
6064
revisionRepo revision.RevisionRepo,
6165
serviceConfig *service_config.ServiceConfig,
6266
siteInfoService siteinfo_common.SiteInfoCommonService,
67+
userService *usercommon.UserCommon,
6368
) *FileRecordService {
6469
return &FileRecordService{
6570
fileRecordRepo: fileRecordRepo,
6671
revisionRepo: revisionRepo,
6772
serviceConfig: serviceConfig,
6873
siteInfoService: siteInfoService,
74+
userService: userService,
6975
}
7076
}
7177

@@ -104,6 +110,21 @@ func (fs *FileRecordService) CleanOrphanUploadFiles(ctx context.Context) {
104110
if fileRecord.CreatedAt.AddDate(0, 0, 2).After(time.Now()) {
105111
continue
106112
}
113+
if isBrandingOrAvatarFile(fileRecord.FilePath) {
114+
if strings.Contains(fileRecord.FilePath, constant.BrandingSubPath+"/") {
115+
if fs.siteInfoService.IsBrandingFileUsed(ctx, fileRecord.FilePath) {
116+
continue
117+
}
118+
} else if strings.Contains(fileRecord.FilePath, constant.AvatarSubPath+"/") {
119+
if fs.userService.IsAvatarFileUsed(ctx, fileRecord.FilePath) {
120+
continue
121+
}
122+
}
123+
if err := fs.DeleteAndMoveFileRecord(ctx, fileRecord); err != nil {
124+
log.Error(err)
125+
}
126+
continue
127+
}
107128
if checker.IsNotZeroString(fileRecord.ObjectID) {
108129
_, exist, err := fs.revisionRepo.GetLastRevisionByObjectID(ctx, fileRecord.ObjectID)
109130
if err != nil {
@@ -129,14 +150,18 @@ func (fs *FileRecordService) CleanOrphanUploadFiles(ctx context.Context) {
129150
}
130151
}
131152
// Delete and move the file record
132-
if err := fs.deleteAndMoveFileRecord(ctx, fileRecord); err != nil {
153+
if err := fs.DeleteAndMoveFileRecord(ctx, fileRecord); err != nil {
133154
log.Error(err)
134155
}
135156
}
136157
page++
137158
}
138159
}
139160

161+
func isBrandingOrAvatarFile(filePath string) bool {
162+
return strings.Contains(filePath, constant.BrandingSubPath+"/") || strings.Contains(filePath, constant.AvatarSubPath+"/")
163+
}
164+
140165
func (fs *FileRecordService) PurgeDeletedFiles(ctx context.Context) {
141166
deletedPath := filepath.Join(fs.serviceConfig.UploadPath, constant.DeletedSubPath)
142167
log.Infof("purge deleted files: %s", deletedPath)
@@ -152,7 +177,7 @@ func (fs *FileRecordService) PurgeDeletedFiles(ctx context.Context) {
152177
return
153178
}
154179

155-
func (fs *FileRecordService) deleteAndMoveFileRecord(ctx context.Context, fileRecord *entity.FileRecord) error {
180+
func (fs *FileRecordService) DeleteAndMoveFileRecord(ctx context.Context, fileRecord *entity.FileRecord) error {
156181
// Delete the file record
157182
if err := fs.fileRecordRepo.DeleteFileRecord(ctx, fileRecord.ID); err != nil {
158183
return fmt.Errorf("delete file record error: %v", err)
@@ -170,3 +195,12 @@ func (fs *FileRecordService) deleteAndMoveFileRecord(ctx context.Context, fileRe
170195
log.Debugf("delete and move file: %s", fileRecord.FileURL)
171196
return nil
172197
}
198+
199+
func (fs *FileRecordService) GetFileRecordByURL(ctx context.Context, fileURL string) (record *entity.FileRecord, err error) {
200+
record, err = fs.fileRecordRepo.GetFileRecordByURL(ctx, fileURL)
201+
if err != nil {
202+
log.Errorf("error retrieving file record by URL: %v", err)
203+
return
204+
}
205+
return
206+
}

0 commit comments

Comments
 (0)