add transaction picture upload api

This commit is contained in:
MaysWind
2024-08-30 00:33:48 +08:00
parent fe442f27f2
commit 73c69c3761
18 changed files with 462 additions and 10 deletions

View File

@@ -125,5 +125,13 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] transaction template table maintained successfully")
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionPictureInfo))
if err != nil {
return err
}
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] transaction picture table maintained successfully")
return nil
}

View File

@@ -161,6 +161,14 @@ func startWebServer(c *core.CliContext) error {
}
}
if config.EnableTransactionPictures {
pictureRoute := router.Group("/pictures")
pictureRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
{
pictureRoute.GET("/:fileName", bindImage(api.TransactionPictures.TransactionPictureGetHandler))
}
}
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
if config.Mode == settings.MODE_DEVELOPMENT {
@@ -309,6 +317,11 @@ func startWebServer(c *core.CliContext) error {
apiV1Route.POST("/transactions/modify.json", bindApi(api.Transactions.TransactionModifyHandler))
apiV1Route.POST("/transactions/delete.json", bindApi(api.Transactions.TransactionDeleteHandler))
// Transaction Pictures
if config.EnableTransactionPictures {
apiV1Route.POST("/transaction/pictures/upload.json", bindApi(api.TransactionPictures.TransactionPictureUploadHandler))
}
// Transaction Categories
apiV1Route.GET("/transaction/categories/list.json", bindApi(api.TransactionCategories.CategoryListHandler))
apiV1Route.GET("/transaction/categories/get.json", bindApi(api.TransactionCategories.CategoryGetHandler))

View File

@@ -199,6 +199,9 @@ enable_forget_password = true
# Set to true to require email must be verified when use forget password
forget_password_require_email_verify = false
# Set to true to allow users to upload transaction pictures
enable_transaction_picture = true
# Set to true to allow users to create scheduled transaction
enable_scheduled_transaction = true

View File

@@ -1,12 +1,16 @@
package api
import (
"fmt"
"github.com/mayswind/ezbookkeeping/pkg/avatars"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
const internalTransactionPictureUrlFormat = "%spictures/%d.%s"
// ApiUsingConfig represents an api that need to use config
type ApiUsingConfig struct {
container *settings.ConfigContainer
@@ -17,6 +21,12 @@ func (a *ApiUsingConfig) CurrentConfig() *settings.Config {
return a.container.Current
}
// GetTransactionPictureInfoResponse returns the view-object of transaction picture basic info according to the transaction picture model
func (a *ApiUsingConfig) GetTransactionPictureInfoResponse(picture *models.TransactionPictureInfo) *models.TransactionPictureInfoBasicResponse {
originalUrl := fmt.Sprintf(internalTransactionPictureUrlFormat, a.CurrentConfig().RootUrl, picture.PictureId, picture.PictureExtension)
return picture.ToTransactionPictureInfoBasicResponse(originalUrl)
}
// GetAfterRegisterNotificationContent returns the notification content displayed each time users register
func (a *ApiUsingConfig) GetAfterRegisterNotificationContent(userLanguage string, clientLanguage string) string {
language := userLanguage

View File

@@ -0,0 +1,153 @@
package api
import (
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/services"
"github.com/mayswind/ezbookkeeping/pkg/settings"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
// TransactionPicturesApi represents transaction pictures api
type TransactionPicturesApi struct {
ApiUsingConfig
ApiUsingDuplicateChecker
users *services.UserService
pictures *services.TransactionPictureService
}
// Initialize a transaction api singleton instance
var (
TransactionPictures = &TransactionPicturesApi{
ApiUsingConfig: ApiUsingConfig{
container: settings.Container,
},
ApiUsingDuplicateChecker: ApiUsingDuplicateChecker{
container: duplicatechecker.Container,
},
users: services.Users,
pictures: services.TransactionPictures,
}
)
// TransactionPictureUploadHandler saves transaction picture by request parameters for current user
func (a *TransactionPicturesApi) TransactionPictureUploadHandler(c *core.WebContext) (any, *errs.Error) {
uid := c.GetCurrentUid()
form, err := c.MultipartForm()
if err != nil {
log.Errorf(c, "[transaction_pictures.TransactionPictureUploadHandler] failed to get multi-part form data for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrParameterInvalid
}
pictureFiles := form.File["picture"]
if len(pictureFiles) < 1 {
log.Warnf(c, "[transaction_pictures.TransactionPictureUploadHandler] there is no transaction picture in request for user \"uid:%d\"", uid)
return nil, errs.ErrNoTransactionPicture
}
if pictureFiles[0].Size < 1 {
log.Warnf(c, "[transaction_pictures.TransactionPictureUploadHandler] the size of transaction picture in request is zero for user \"uid:%d\"", uid)
return nil, errs.ErrTransactionPictureIsEmpty
}
fileExtension := utils.GetFileNameExtension(pictureFiles[0].Filename)
if utils.GetImageContentType(fileExtension) == "" {
log.Warnf(c, "[transaction_pictures.TransactionPictureUploadHandler] the file extension \"%s\" of transaction picture in request is not supported for user \"uid:%d\"", fileExtension, uid)
return nil, errs.ErrImageTypeNotSupported
}
pictureFile, err := pictureFiles[0].Open()
if err != nil {
log.Errorf(c, "[transaction_pictures.TransactionPictureUploadHandler] failed to get transaction picture file from request for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.ErrOperationFailed
}
pictureInfo := a.createNewPictureInfoModel(uid, fileExtension, c.ClientIP())
clientSessionIds := form.Value["clientSessionId"]
clientSessionId := ""
if len(clientSessionIds) > 0 {
clientSessionId = clientSessionIds[0]
}
if a.CurrentConfig().EnableDuplicateSubmissionsCheck && clientSessionId != "" {
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_PICTURE, uid, clientSessionId)
if found {
log.Infof(c, "[transaction_pictures.TransactionPictureUploadHandler] another transaction picture \"id:%s\" has been uploaded for user \"uid:%d\"", remark, uid)
pictureId, err := utils.StringToInt64(remark)
if err == nil {
pictureInfo, err = a.pictures.GetPictureInfoByPictureId(c, uid, pictureId)
if err != nil {
log.Errorf(c, "[transaction_pictures.TransactionPictureUploadHandler] failed to get existed transaction picture \"id:%d\" for user \"uid:%d\", because %s", pictureId, uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
pictureInfoResp := a.GetTransactionPictureInfoResponse(pictureInfo)
return pictureInfoResp, nil
}
}
}
err = a.pictures.UploadPicture(c, pictureInfo, pictureFile)
if err != nil {
log.Errorf(c, "[transaction_pictures.TransactionPictureUploadHandler] failed to update transaction picture for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
a.SetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_PICTURE, uid, clientSessionId, utils.Int64ToString(pictureInfo.PictureId))
pictureInfoResp := a.GetTransactionPictureInfoResponse(pictureInfo)
return pictureInfoResp, nil
}
// TransactionPictureGetHandler returns transaction picture data for current user
func (a *TransactionPicturesApi) TransactionPictureGetHandler(c *core.WebContext) ([]byte, string, *errs.Error) {
fileName := c.Param("fileName")
fileExtension := utils.GetFileNameExtension(fileName)
contentType := utils.GetImageContentType(fileExtension)
if contentType == "" {
return nil, "", errs.ErrImageTypeNotSupported
}
fileBaseName := utils.GetFileNameWithoutExtension(fileName)
pictureId, err := utils.StringToInt64(fileBaseName)
if err != nil {
return nil, "", errs.ErrTransactionPictureIdInvalid
}
uid := c.GetCurrentUid()
pictureData, err := a.pictures.GetPictureByPictureId(c, uid, pictureId, fileExtension)
if err != nil {
if !errs.IsCustomError(err) {
log.Errorf(c, "[transaction_pictures.TransactionPictureUploadHandler] failed to get transaction picture, because %s", err.Error())
}
return nil, "", errs.Or(err, errs.ErrOperationFailed)
}
return pictureData, contentType, nil
}
func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo {
return &models.TransactionPictureInfo{
Uid: uid,
PictureExtension: fileExtension,
CreatedIp: clientIp,
}
}

View File

@@ -10,4 +10,5 @@ const (
DUPLICATE_CHECKER_TYPE_NEW_CATEGORY DuplicateCheckerType = 2
DUPLICATE_CHECKER_TYPE_NEW_TRANSACTION DuplicateCheckerType = 3
DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE DuplicateCheckerType = 4
DUPLICATE_CHECKER_TYPE_NEW_PICTURE DuplicateCheckerType = 5
)

View File

@@ -36,6 +36,7 @@ const (
NormalSubcategoryDataManagement = 8
NormalSubcategoryMapProxy = 9
NormalSubcategoryTemplate = 10
NormalSubcategoryPicture = 11
)
// Error represents the specific error returned to user

View File

@@ -0,0 +1,13 @@
package errs
import "net/http"
// Error codes related to transaction pictures
var (
ErrTransactionPictureIdInvalid = NewNormalError(NormalSubcategoryPicture, 0, http.StatusBadRequest, "transaction picture id is invalid")
ErrTransactionPictureNotFound = NewNormalError(NormalSubcategoryPicture, 1, http.StatusBadRequest, "transaction picture not found")
ErrNoTransactionPicture = NewNormalError(NormalSubcategoryPicture, 2, http.StatusBadRequest, "no transaction picture")
ErrTransactionPictureIsEmpty = NewNormalError(NormalSubcategoryPicture, 3, http.StatusBadRequest, "transaction picture is empty")
ErrTransactionPictureNoExists = NewNormalError(NormalSubcategoryPicture, 4, http.StatusNotFound, "transaction picture not exists")
ErrTransactionPictureExtensionInvalid = NewNormalError(NormalSubcategoryPicture, 5, http.StatusNotFound, "transaction picture file extension invalid")
)

View File

@@ -19,6 +19,7 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
buildBooleanSetting("r", config.EnableUserRegister),
buildBooleanSetting("f", config.EnableUserForgetPassword),
buildBooleanSetting("v", config.EnableUserVerifyEmail),
buildBooleanSetting("p", config.EnableTransactionPictures),
buildBooleanSetting("s", config.EnableScheduledTransaction),
buildBooleanSetting("e", config.EnableDataExport),
buildStringSetting("m", strings.Replace(config.MapProvider, "_", "-", -1)),

View File

@@ -0,0 +1,28 @@
package models
// TransactionPictureInfo represents transaction picture file info stored in database
type TransactionPictureInfo struct {
Uid int64 `xorm:"INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) INDEX(IDX_transaction_picture_uid_deleted_picture_id) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) INDEX(IDX_transaction_picture_uid_deleted_picture_id) NOT NULL"`
TransactionId int64 `xorm:"INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) NOT NULL"`
PictureId int64 `xorm:"PK INDEX(IDX_transaction_picture_uid_deleted_transaction_id_picture_id) INDEX(IDX_transaction_picture_uid_deleted_picture_id)"`
PictureExtension string `xorm:"VARCHAR(10) NOT NULL"`
CreatedIp string `xorm:"VARCHAR(39)"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
}
// TransactionPictureInfoBasicResponse represents a view-object of transaction picture basic info
type TransactionPictureInfoBasicResponse struct {
PictureId int64 `json:"pictureId,string"`
OriginalUrl string `json:"originalUrl"`
}
// ToTransactionPictureInfoBasicResponse returns a view-object according to database model
func (p *TransactionPictureInfo) ToTransactionPictureInfoBasicResponse(originalUrl string) *TransactionPictureInfoBasicResponse {
return &TransactionPictureInfoBasicResponse{
PictureId: p.PictureId,
OriginalUrl: originalUrl,
}
}

View File

@@ -2,12 +2,14 @@ package services
import (
"fmt"
"path/filepath"
"github.com/mayswind/ezbookkeeping/pkg/datastore"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/mail"
"github.com/mayswind/ezbookkeeping/pkg/settings"
"github.com/mayswind/ezbookkeeping/pkg/storage"
"github.com/mayswind/ezbookkeeping/pkg/utils"
"github.com/mayswind/ezbookkeeping/pkg/uuid"
)
@@ -115,6 +117,30 @@ func (s *ServiceUsingStorage) DeleteAvatar(uid int64, fileExtension string) erro
return s.container.DeleteAvatar(s.getUserAvatarPath(uid, fileExtension))
}
// ExistsTransactionPicture returns whether the transaction picture exists from the current transaction picture object storage
func (s *ServiceUsingStorage) ExistsTransactionPicture(uid int64, pictureId int64, fileExtension string) (bool, error) {
return s.container.ExistsTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
}
// ReadTransactionPicture returns the transaction picture from the current transaction picture object storage
func (s *ServiceUsingStorage) ReadTransactionPicture(uid int64, pictureId int64, fileExtension string) (storage.ObjectInStorage, error) {
return s.container.ReadTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
}
// SaveTransactionPicture returns whether save the transaction picture into the current transaction picture object storage successfully
func (s *ServiceUsingStorage) SaveTransactionPicture(uid int64, pictureId int64, object storage.ObjectInStorage, fileExtension string) error {
return s.container.SaveTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension), object)
}
// DeleteTransactionPicture returns whether delete the transaction picture from the current transaction picture object storage successfully
func (s *ServiceUsingStorage) DeleteTransactionPicture(uid int64, pictureId int64, fileExtension string) error {
return s.container.DeleteTransactionPicture(s.getTransactionPicturePath(uid, pictureId, fileExtension))
}
func (s *ServiceUsingStorage) getUserAvatarPath(uid int64, fileExtension string) string {
return fmt.Sprintf("%d.%s", uid, fileExtension)
}
func (s *ServiceUsingStorage) getTransactionPicturePath(uid int64, pictureId int64, fileExtension string) string {
return filepath.Join(utils.Int64ToString(uid), fmt.Sprintf("%d.%s", pictureId, fileExtension))
}

View File

@@ -0,0 +1,140 @@
package services
import (
"io"
"mime/multipart"
"os"
"time"
"xorm.io/xorm"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/datastore"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/storage"
"github.com/mayswind/ezbookkeeping/pkg/uuid"
)
// TransactionPictureService represents transaction picture service
type TransactionPictureService struct {
ServiceUsingDB
ServiceUsingUuid
ServiceUsingStorage
}
// Initialize a transaction picture service singleton instance
var (
TransactionPictures = &TransactionPictureService{
ServiceUsingDB: ServiceUsingDB{
container: datastore.Container,
},
ServiceUsingUuid: ServiceUsingUuid{
container: uuid.Container,
},
ServiceUsingStorage: ServiceUsingStorage{
container: storage.Container,
},
}
)
// GetPictureInfoByPictureId returns a transaction picture info model according to transaction picture id
func (s *TransactionPictureService) GetPictureInfoByPictureId(c core.Context, uid int64, pictureId int64) (*models.TransactionPictureInfo, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if pictureId <= 0 {
return nil, errs.ErrTransactionPictureIdInvalid
}
pictureInfo := &models.TransactionPictureInfo{}
has, err := s.UserDataDB(uid).NewSession(c).ID(pictureId).Where("uid=? AND deleted=?", uid, false).Get(pictureInfo)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrTransactionPictureNotFound
}
return pictureInfo, nil
}
// GetPictureByPictureId returns the transaction picture data according to transaction picture id
func (s *TransactionPictureService) GetPictureByPictureId(c core.Context, uid int64, pictureId int64, fileExtension string) ([]byte, error) {
if uid <= 0 {
return nil, errs.ErrUserIdInvalid
}
if pictureId <= 0 {
return nil, errs.ErrTransactionPictureIdInvalid
}
pictureInfo := &models.TransactionPictureInfo{}
has, err := s.UserDataDB(uid).NewSession(c).ID(pictureId).Where("uid=? AND deleted=?", uid, false).Get(pictureInfo)
if err != nil {
return nil, err
} else if !has {
return nil, errs.ErrTransactionPictureNotFound
}
if pictureInfo.PictureExtension == "" {
return nil, errs.ErrTransactionPictureNotFound
}
if pictureInfo.PictureExtension != fileExtension {
return nil, errs.ErrTransactionPictureExtensionInvalid
}
pictureFile, err := s.ReadTransactionPicture(pictureInfo.Uid, pictureInfo.PictureId, pictureInfo.PictureExtension)
if os.IsNotExist(err) {
return nil, errs.ErrTransactionPictureNoExists
}
if err != nil {
return nil, err
}
defer pictureFile.Close()
pictureData, err := io.ReadAll(pictureFile)
if err != nil {
return nil, err
}
return pictureData, nil
}
// UploadPicture uploads the transaction picture for specified user
func (s *TransactionPictureService) UploadPicture(c core.Context, pictureInfo *models.TransactionPictureInfo, pictureFile multipart.File) error {
if pictureInfo.Uid <= 0 {
return errs.ErrUserIdInvalid
}
defer pictureFile.Close()
pictureInfo.PictureId = s.GenerateUuid(uuid.UUID_TYPE_USER)
if pictureInfo.PictureId < 1 {
return errs.ErrSystemIsBusy
}
pictureInfo.TransactionId = 0
pictureInfo.Deleted = false
pictureInfo.CreatedUnixTime = time.Now().Unix()
pictureInfo.UpdatedUnixTime = time.Now().Unix()
err := s.SaveTransactionPicture(pictureInfo.Uid, pictureInfo.PictureId, pictureFile, pictureInfo.PictureExtension)
if err != nil {
return err
}
return s.UserDataDB(pictureInfo.Uid).DoTransaction(c, func(sess *xorm.Session) error {
_, err := sess.Insert(pictureInfo)
return err
})
}

View File

@@ -272,6 +272,7 @@ type Config struct {
EnableUserForceVerifyEmail bool
EnableUserForgetPassword bool
ForgetPasswordRequireVerifyEmail bool
EnableTransactionPictures bool
EnableScheduledTransaction bool
AvatarProvider core.UserAvatarProviderType
@@ -741,6 +742,7 @@ func loadUserConfiguration(config *Config, configFile *ini.File, sectionName str
config.EnableUserForceVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "enable_force_email_verify", false)
config.EnableUserForgetPassword = getConfigItemBoolValue(configFile, sectionName, "enable_forget_password", false)
config.ForgetPasswordRequireVerifyEmail = getConfigItemBoolValue(configFile, sectionName, "forget_password_require_email_verify", false)
config.EnableTransactionPictures = getConfigItemBoolValue(configFile, sectionName, "enable_transaction_picture", false)
config.EnableScheduledTransaction = getConfigItemBoolValue(configFile, sectionName, "enable_scheduled_transaction", false)
if getConfigItemStringValue(configFile, sectionName, "avatar_provider") == string(core.USER_AVATAR_PROVIDER_INTERNAL) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"net/http"
"strings"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
@@ -40,6 +41,7 @@ func NewMinIOObjectStorage(config *settings.Config, pathPrefix string) (*MinIOOb
}
storage.rootPath = storage.getFinalPath(pathPrefix)
storage.rootPath = strings.ReplaceAll(storage.rootPath, "\\", "/")
ctx := context.Background()
exists, err := minIOClient.BucketExists(ctx, minIOConfig.Bucket)
@@ -104,5 +106,7 @@ func (s *MinIOObjectStorage) getFinalPath(path string) string {
path = path[1:]
}
path = strings.ReplaceAll(path, "\\", "/")
return rootPath + path
}

View File

@@ -6,10 +6,12 @@ import (
)
const avatarPathPrefix = "avatar"
const transactionPicturePathPrefix = "transaction"
// StorageContainer contains the current object storage
type StorageContainer struct {
AvatarCurrentStorage ObjectStorage
AvatarCurrentStorage ObjectStorage
TransactionPictureCurrentStorage ObjectStorage
}
// Initialize a object storage container singleton instance
@@ -19,19 +21,23 @@ var (
// InitializeStorageContainer initializes the current object storage according to the config
func InitializeStorageContainer(config *settings.Config) error {
if config.StorageType == settings.LocalFileSystemObjectStorageType {
avatarStorage, err := NewLocalFileSystemObjectStorage(config, avatarPathPrefix)
Container.AvatarCurrentStorage = avatarStorage
return err
} else if config.StorageType == settings.MinIOStorageType {
avatarStorage, err := NewMinIOObjectStorage(config, avatarPathPrefix)
Container.AvatarCurrentStorage = avatarStorage
avatarStorage, err := newObjectStorage(config, avatarPathPrefix)
if err != nil {
return err
}
return errs.ErrInvalidStorageType
Container.AvatarCurrentStorage = avatarStorage
transactionPictureStorage, err := newObjectStorage(config, transactionPicturePathPrefix)
if err != nil {
return err
}
Container.TransactionPictureCurrentStorage = transactionPictureStorage
return nil
}
// ExistsAvatar returns whether the avatar file exists from the current avatar object storage
@@ -53,3 +59,33 @@ func (s *StorageContainer) SaveAvatar(path string, object ObjectInStorage) error
func (s *StorageContainer) DeleteAvatar(path string) error {
return s.AvatarCurrentStorage.Delete(path)
}
// ExistsTransactionPicture returns whether the transaction picture file exists from the current transaction picture object storage
func (s *StorageContainer) ExistsTransactionPicture(path string) (bool, error) {
return s.TransactionPictureCurrentStorage.Exists(path)
}
// ReadTransactionPicture returns the transaction picture file from the current transaction picture object storage
func (s *StorageContainer) ReadTransactionPicture(path string) (ObjectInStorage, error) {
return s.TransactionPictureCurrentStorage.Read(path)
}
// SaveTransactionPicture returns whether save the transaction picture file into the current transaction picture object storage successfully
func (s *StorageContainer) SaveTransactionPicture(path string, object ObjectInStorage) error {
return s.TransactionPictureCurrentStorage.Save(path, object)
}
// DeleteTransactionPicture returns whether delete the transaction picture file from the current transaction picture object storage successfully
func (s *StorageContainer) DeleteTransactionPicture(path string) error {
return s.TransactionPictureCurrentStorage.Delete(path)
}
func newObjectStorage(config *settings.Config, pathPrefix string) (ObjectStorage, error) {
if config.StorageType == settings.LocalFileSystemObjectStorageType {
return NewLocalFileSystemObjectStorage(config, pathPrefix)
} else if config.StorageType == settings.MinIOStorageType {
return NewMinIOObjectStorage(config, pathPrefix)
}
return nil, errs.ErrInvalidStorageType
}

View File

@@ -13,4 +13,5 @@ const (
UUID_TYPE_TAG UuidType = 5
UUID_TYPE_TAG_INDEX UuidType = 6
UUID_TYPE_TEMPLATE UuidType = 7
UUID_TYPE_PICTURE UuidType = 8
)

View File

@@ -1088,6 +1088,12 @@
"scheduled transaction is not enabled": "Scheduled transaction is not enabled",
"scheduled transaction frequency is invalid": "Scheduled transaction frequency is invalid",
"transaction template has too many tags": "There are too many tags in this transaction template",
"transaction picture id is invalid": "Transaction picture ID is invalid",
"transaction picture not found": "Transaction picture is not found",
"no transaction picture": "There is no transaction picture file",
"transaction picture is empty": "Transaction picture file is empty",
"transaction picture not exists": "Transaction picture does not exist",
"transaction picture file extension invalid": "Transaction picture file extension is invalid",
"query items cannot be blank": "There are no query items",
"query items too much": "There are too many query items",
"query items have invalid item": "There is invalid item in query items",

View File

@@ -1088,6 +1088,12 @@
"scheduled transaction is not enabled": "定时交易没有启用",
"scheduled transaction frequency is invalid": "定时交易周期无效",
"transaction template has too many tags": "交易模板中的标签过多",
"transaction picture id is invalid": "交易图片ID无效",
"transaction picture not found": "交易图片不存在",
"no transaction picture": "没有交易图片文件",
"transaction picture is empty": "交易图片文件为空",
"transaction picture not exists": "交易图片不存在",
"transaction picture file extension invalid": "交易图片文件扩展名无效",
"query items cannot be blank": "请求项目不能为空",
"query items too much": "请求项目过多",
"query items have invalid item": "请求项目中有非法项目",