Skip to content

feature: saving files as blob in database #1326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 8 additions & 25 deletions cmd/wire_gen.go

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

7 changes: 7 additions & 0 deletions internal/base/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/apache/answer/internal/service/service_config"
"github.com/apache/answer/pkg/writer"
"github.com/segmentfault/pacman/contrib/conf/viper"
"github.com/segmentfault/pacman/log"
"gopkg.in/yaml.v3"
)

Expand All @@ -50,13 +51,15 @@ type envConfigOverrides struct {
SwaggerHost string
SwaggerAddressPort string
SiteAddr string
StorageMode string
}

func loadEnvs() (envOverrides *envConfigOverrides) {
return &envConfigOverrides{
SwaggerHost: os.Getenv("SWAGGER_HOST"),
SwaggerAddressPort: os.Getenv("SWAGGER_ADDRESS_PORT"),
SiteAddr: os.Getenv("SITE_ADDR"),
StorageMode: os.Getenv("FILE_STORAGE_MODE"),
}
}

Expand Down Expand Up @@ -93,6 +96,10 @@ func (c *AllConfig) SetEnvironmentOverrides() {
if envs.SwaggerAddressPort != "" {
c.Swaggerui.Address = envs.SwaggerAddressPort
}
if envs.StorageMode == "db" {
c.ServiceConfig.UseDbFileStorage = true
log.Info("saving files as blob in db")
}
}

// ReadConfig read config
Expand Down
1 change: 1 addition & 0 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ var ProviderSetController = wire.NewSet(
NewEmbedController,
NewBadgeController,
NewRenderController,
NewFileController,
)
36 changes: 36 additions & 0 deletions internal/controller/file_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package controller

import (
"strconv"

"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/repo/file"
"github.com/gin-gonic/gin"
)

type FileController struct {
fileRepo file.FileRepo
}

func NewFileController(fileRepo file.FileRepo) *FileController {
return &FileController{fileRepo: fileRepo}
}

func (bc *FileController) GetFile(ctx *gin.Context) {
id := ctx.Param("id")
download := ctx.DefaultQuery("download", "")

blob, err := bc.fileRepo.GetByID(ctx.Request.Context(), id)
if err != nil || blob == nil {
handler.HandleResponse(ctx, err, "file not found")
return
}

ctx.Header("Content-Type", blob.MimeType)
ctx.Header("Content-Length", strconv.FormatInt(blob.Size, 10))
if download != "" {
ctx.Header("Content-Disposition", "attachment; filename=\""+download+"\"")
}

ctx.Data(200, blob.MimeType, blob.Content)
}
18 changes: 18 additions & 0 deletions internal/entity/file_entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package entity

import (
"time"
)

type File struct {
ID string `xorm:"pk varchar(36)"`
FileName string `xorm:"varchar(255) not null"`
MimeType string `xorm:"varchar(100)"`
Size int64 `xorm:"bigint"`
Content []byte `xorm:"blob"`
CreatedAt time.Time `xorm:"created"`
}

func (File) TableName() string {
return "file"
}
1 change: 1 addition & 0 deletions internal/migrations/init_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ var (
&entity.BadgeAward{},
&entity.FileRecord{},
&entity.PluginKVStorage{},
&entity.File{},
}

roles = []*entity.Role{
Expand Down
53 changes: 53 additions & 0 deletions internal/repo/file/file_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package file

import (
"context"
"database/sql"

"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"
)

type FileRepo interface {
Save(ctx context.Context, file *entity.File) error
GetByID(ctx context.Context, id string) (*entity.File, error)
Delete(ctx context.Context, id string) (err error)
}

type fileRepo struct {
data *data.Data
}

func NewFileRepo(data *data.Data) FileRepo {
return &fileRepo{data: data}
}

func (r *fileRepo) Save(ctx context.Context, file *entity.File) error {
_, err := r.data.DB.Context(ctx).Insert(file)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}

func (r *fileRepo) GetByID(ctx context.Context, id string) (*entity.File, error) {
var blob entity.File
ok, err := r.data.DB.Context(ctx).ID(id).Get(&blob)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if !ok {
return nil, sql.ErrNoRows
}
return &blob, nil
}

func (r *fileRepo) Delete(ctx context.Context, id string) (err error) {
_, err = r.data.DB.Context(ctx).ID(id).Delete(&entity.File{})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
11 changes: 11 additions & 0 deletions internal/router/answer_api_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type AnswerAPIRouter struct {
metaController *controller.MetaController
badgeController *controller.BadgeController
adminBadgeController *controller_admin.BadgeController
fileController *controller.FileController
}

func NewAnswerAPIRouter(
Expand Down Expand Up @@ -90,6 +91,7 @@ func NewAnswerAPIRouter(
metaController *controller.MetaController,
badgeController *controller.BadgeController,
adminBadgeController *controller_admin.BadgeController,
fileController *controller.FileController,
) *AnswerAPIRouter {
return &AnswerAPIRouter{
langController: langController,
Expand Down Expand Up @@ -122,6 +124,7 @@ func NewAnswerAPIRouter(
metaController: metaController,
badgeController: badgeController,
adminBadgeController: adminBadgeController,
fileController: fileController,
}
}

Expand All @@ -148,6 +151,9 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(authUserMiddleware *

// plugins
r.GET("/plugin/status", a.pluginController.GetAllPluginStatus)

// file branding
r.GET("/file/branding/:id", a.fileController.GetFile)
}

func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
Expand All @@ -171,6 +177,10 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
r.GET("/personal/question/page", a.questionController.PersonalQuestionPage)
r.GET("/question/link", a.questionController.GetQuestionLink)

//file
r.GET("/file/post/:id", a.fileController.GetFile)
r.GET("/file/avatar/:id", a.fileController.GetFile)

// comment
r.GET("/comment/page", a.commentController.GetCommentWithPage)
r.GET("/personal/comment/page", a.commentController.GetCommentPersonalWithPage)
Expand Down Expand Up @@ -310,6 +320,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {

// meta
r.PUT("/meta/reaction", a.metaController.AddOrUpdateReaction)

}

func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
Expand Down
14 changes: 14 additions & 0 deletions internal/service/file_record/file_record_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/file"
"github.com/apache/answer/internal/service/revision"
"github.com/apache/answer/internal/service/service_config"
"github.com/apache/answer/internal/service/siteinfo_common"
Expand Down Expand Up @@ -56,6 +57,7 @@ type FileRecordService struct {
serviceConfig *service_config.ServiceConfig
siteInfoService siteinfo_common.SiteInfoCommonService
userService *usercommon.UserCommon
fileRepo file.FileRepo
}

// NewFileRecordService new file record service
Expand All @@ -65,13 +67,15 @@ func NewFileRecordService(
serviceConfig *service_config.ServiceConfig,
siteInfoService siteinfo_common.SiteInfoCommonService,
userService *usercommon.UserCommon,
fileRepo file.FileRepo,
) *FileRecordService {
return &FileRecordService{
fileRecordRepo: fileRecordRepo,
revisionRepo: revisionRepo,
serviceConfig: serviceConfig,
siteInfoService: siteInfoService,
userService: userService,
fileRepo: fileRepo,
}
}

Expand Down Expand Up @@ -183,6 +187,16 @@ func (fs *FileRecordService) DeleteAndMoveFileRecord(ctx context.Context, fileRe
return fmt.Errorf("delete file record error: %v", err)
}

if fs.serviceConfig.UseDbFileStorage {
fileURL := fileRecord.FileURL
parts := strings.Split(fileURL, "/")
fileId := parts[len(parts)-1]
if err := fs.fileRepo.Delete(ctx, fileId); err != nil {
return fmt.Errorf("failed to delete file: %v", err)
}
return nil
}

// Move the file to the deleted directory
oldFilename := filepath.Base(fileRecord.FilePath)
oldFilePath := filepath.Join(fs.serviceConfig.UploadPath, fileRecord.FilePath)
Expand Down
4 changes: 3 additions & 1 deletion internal/service/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package service

import (
"github.com/apache/answer/internal/repo/file"
"github.com/apache/answer/internal/service/action"
"github.com/apache/answer/internal/service/activity"
"github.com/apache/answer/internal/service/activity_common"
Expand All @@ -40,7 +41,7 @@ import (
"github.com/apache/answer/internal/service/follow"
"github.com/apache/answer/internal/service/importer"
"github.com/apache/answer/internal/service/meta"
"github.com/apache/answer/internal/service/meta_common"
metacommon "github.com/apache/answer/internal/service/meta_common"
"github.com/apache/answer/internal/service/notice_queue"
"github.com/apache/answer/internal/service/notification"
notficationcommon "github.com/apache/answer/internal/service/notification_common"
Expand Down Expand Up @@ -128,4 +129,5 @@ var ProviderSetService = wire.NewSet(
badge.NewBadgeGroupService,
importer.NewImporterService,
file_record.NewFileRecordService,
file.NewFileRepo,
)
1 change: 1 addition & 0 deletions internal/service/service_config/service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ type ServiceConfig struct {
CleanUpUploads bool `json:"clean_up_uploads" mapstructure:"clean_up_uploads" yaml:"clean_up_uploads"`
CleanOrphanUploadsPeriodHours int `json:"clean_orphan_uploads_period_hours" mapstructure:"clean_orphan_uploads_period_hours" yaml:"clean_orphan_uploads_period_hours"`
PurgeDeletedFilesPeriodDays int `json:"purge_deleted_files_period_days" mapstructure:"purge_deleted_files_period_days" yaml:"purge_deleted_files_period_days"`
UseDbFileStorage bool `json:"use_db_file_storage" mapstructure:"use_db_file_storage" yaml:"use_db_file_storage"`
}
Loading