Skip to content

Commit

Permalink
feat(service): implement file cleanup and deletion functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
LinkinStars committed Jan 10, 2025
1 parent 47c6662 commit 5cffab8
Show file tree
Hide file tree
Showing 24 changed files with 504 additions and 40 deletions.
12 changes: 8 additions & 4 deletions cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions configs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ swaggerui:
address: ':80'
service_config:
upload_path: "/data/uploads"
clean_up_uploads: true
clean_orphan_uploads_period_hours: 48
purge_deleted_files_period_days: 30
ui:
public_url: '/'
api_url: '/'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ require (
golang.org/x/crypto v0.27.0
golang.org/x/image v0.20.0
golang.org/x/net v0.29.0
golang.org/x/text v0.18.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.33.0
Expand Down Expand Up @@ -160,7 +161,6 @@ require (
golang.org/x/arch v0.10.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.25.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
Expand Down
1 change: 1 addition & 0 deletions internal/base/constant/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ const (
PostSubPath = "post"
BrandingSubPath = "branding"
FilesPostSubPath = "files/post"
DeletedSubPath = "deleted"
)
43 changes: 36 additions & 7 deletions internal/base/cron/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,45 @@ import (
"fmt"

"github.com/apache/answer/internal/service/content"
"github.com/apache/answer/internal/service/file_record"
"github.com/apache/answer/internal/service/service_config"
"github.com/apache/answer/internal/service/siteinfo_common"
"github.com/robfig/cron/v3"
"github.com/segmentfault/pacman/log"
)

// ScheduledTaskManager scheduled task manager
type ScheduledTaskManager struct {
siteInfoService siteinfo_common.SiteInfoCommonService
questionService *content.QuestionService
siteInfoService siteinfo_common.SiteInfoCommonService
questionService *content.QuestionService
fileRecordService *file_record.FileRecordService
serviceConfig *service_config.ServiceConfig
}

// NewScheduledTaskManager new scheduled task manager
func NewScheduledTaskManager(
siteInfoService siteinfo_common.SiteInfoCommonService,
questionService *content.QuestionService,
fileRecordService *file_record.FileRecordService,
serviceConfig *service_config.ServiceConfig,
) *ScheduledTaskManager {
manager := &ScheduledTaskManager{
siteInfoService: siteInfoService,
questionService: questionService,
siteInfoService: siteInfoService,
questionService: questionService,
fileRecordService: fileRecordService,
serviceConfig: serviceConfig,
}
return manager
}

func (s *ScheduledTaskManager) Run() {
fmt.Println("start cron")
log.Infof("cron job manager start")

s.questionService.SitemapCron(context.Background())
c := cron.New()
_, err := c.AddFunc("0 */1 * * *", func() {
ctx := context.Background()
fmt.Println("sitemap cron execution")
log.Infof("sitemap cron execution")
s.questionService.SitemapCron(ctx)
})
if err != nil {
Expand All @@ -62,12 +71,32 @@ func (s *ScheduledTaskManager) Run() {

_, err = c.AddFunc("0 */1 * * *", func() {
ctx := context.Background()
fmt.Println("refresh hottest cron execution")
log.Infof("refresh hottest cron execution")
s.questionService.RefreshHottestCron(ctx)
})
if err != nil {
log.Error(err)
}

if s.serviceConfig.CleanUpUploads {
log.Infof("clean up uploads cron enabled")

conf := s.serviceConfig
_, err = c.AddFunc(fmt.Sprintf("0 */%d * * *", conf.CleanOrphanUploadsPeriodHours), func() {
log.Infof("clean orphan upload files cron execution")
s.fileRecordService.CleanOrphanUploadFiles(context.Background())
})
if err != nil {
log.Error(err)
}

_, err = c.AddFunc(fmt.Sprintf("0 0 */%d * *", conf.PurgeDeletedFilesPeriodDays), func() {
log.Infof("purge deleted files cron execution")
s.fileRecordService.PurgeDeletedFiles(context.Background())
})
if err != nil {
log.Error(err)
}
}
c.Start()
}
9 changes: 5 additions & 4 deletions internal/controller/upload_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,20 @@ func (uc *UploadController) UploadFile(ctx *gin.Context) {
)

source := ctx.PostForm("source")
userID := middleware.GetLoginUserIDFromContext(ctx)
switch source {
case fileFromAvatar:
url, err = uc.uploaderService.UploadAvatarFile(ctx)
url, err = uc.uploaderService.UploadAvatarFile(ctx, userID)
case fileFromPost:
url, err = uc.uploaderService.UploadPostFile(ctx)
url, err = uc.uploaderService.UploadPostFile(ctx, userID)
case fileFromBranding:
if !middleware.GetIsAdminFromContext(ctx) {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
return
}
url, err = uc.uploaderService.UploadBrandingFile(ctx)
url, err = uc.uploaderService.UploadBrandingFile(ctx, userID)
case fileFromPostAttachment:
url, err = uc.uploaderService.UploadPostAttachment(ctx)
url, err = uc.uploaderService.UploadPostAttachment(ctx, userID)
default:
handler.HandleResponse(ctx, errors.BadRequest(reason.UploadFileSourceUnsupported), nil)
return
Expand Down
45 changes: 45 additions & 0 deletions internal/entity/file_record_entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package entity

import "time"

const (
FileRecordStatusAvailable = 1
FileRecordStatusDeleted = 10
)

// FileRecord file record
type FileRecord struct {
ID int `xorm:"not null pk autoincr INT(10) id"`
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP created TIMESTAMP created_at"`
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP updated TIMESTAMP updated_at"`
UserID string `xorm:"not null default 0 BIGINT(20) user_id"`
FilePath string `xorm:"not null VARCHAR(256) file_path"`
FileURL string `xorm:"not null VARCHAR(1024) file_url"`
ObjectID string `xorm:"not null default 0 INDEX BIGINT(20) object_id"`
Source string `xorm:"not null VARCHAR(128) source"`
Status int `xorm:"not null default 0 TINYINT(4) status"`
}

// TableName file record table name
func (FileRecord) TableName() string {
return "file_record"
}
1 change: 1 addition & 0 deletions internal/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ var migrations = []Migration{
NewMigration("v1.4.0", "add badge/badge_group/badge_award table", addBadges, true),
NewMigration("v1.4.1", "add question link", addQuestionLink, true),
NewMigration("v1.4.2", "add the number of question links", addQuestionLinkedCount, true),
NewMigration("v1.4.5", "add file record", addFileRecord, true),
}

func GetMigrations() []Migration {
Expand Down
31 changes: 31 additions & 0 deletions internal/migrations/v25.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package migrations

import (
"context"

"github.com/apache/answer/internal/entity"
"xorm.io/xorm"
)

func addFileRecord(ctx context.Context, x *xorm.Engine) error {
return x.Context(ctx).Sync(new(entity.FileRecord))
}
19 changes: 18 additions & 1 deletion internal/repo/answer/answer_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,24 @@ func (ar *answerRepo) updateSearch(ctx context.Context, answerID string) (err er
}

func (ar *answerRepo) DeletePermanentlyAnswers(ctx context.Context) error {
_, err := ar.data.DB.Context(ctx).Where("status = ?", entity.AnswerStatusDeleted).Delete(&entity.Answer{})
// get all deleted answers ids
ids := make([]string, 0)
err := ar.data.DB.Context(ctx).Select("id").Table(new(entity.Answer).TableName()).
Where("status = ?", entity.AnswerStatusDeleted).Find(&ids)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if len(ids) == 0 {
return nil
}

// delete all revisions permanently
_, err = ar.data.DB.Context(ctx).In("object_id", ids).Delete(&entity.Revision{})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}

_, err = ar.data.DB.Context(ctx).Where("status = ?", entity.AnswerStatusDeleted).Delete(&entity.Answer{})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
Expand Down
84 changes: 84 additions & 0 deletions internal/repo/file_record/file_record_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package file_record

import (
"context"

"github.com/apache/answer/internal/base/pager"
"github.com/apache/answer/internal/service/file_record"

"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/entity"
"github.com/segmentfault/pacman/errors"
)

// fileRecordRepo fileRecord repository
type fileRecordRepo struct {
data *data.Data
}

// NewFileRecordRepo new repository
func NewFileRecordRepo(data *data.Data) file_record.FileRecordRepo {
return &fileRecordRepo{
data: data,
}
}

// AddFileRecord add file record
func (fr *fileRecordRepo) AddFileRecord(ctx context.Context, fileRecord *entity.FileRecord) (err error) {
_, err = fr.data.DB.Context(ctx).Insert(fileRecord)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}

// GetFileRecordPage get fileRecord page
func (fr *fileRecordRepo) GetFileRecordPage(ctx context.Context, page, pageSize int, cond *entity.FileRecord) (
fileRecordList []*entity.FileRecord, total int64, err error) {
fileRecordList = make([]*entity.FileRecord, 0)

session := fr.data.DB.Context(ctx)
total, err = pager.Help(page, pageSize, &fileRecordList, cond, session)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}

// DeleteFileRecord delete file record
func (fr *fileRecordRepo) DeleteFileRecord(ctx context.Context, id int) (err error) {
_, err = fr.data.DB.Context(ctx).ID(id).Cols("status").Update(&entity.FileRecord{Status: entity.FileRecordStatusDeleted})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}

// UpdateFileRecord update file record
func (fr *fileRecordRepo) UpdateFileRecord(ctx context.Context, fileRecord *entity.FileRecord) (err error) {
_, err = fr.data.DB.Context(ctx).ID(fileRecord.ID).Update(fileRecord)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
Loading

0 comments on commit 5cffab8

Please sign in to comment.