add transaction picture upload api
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
153
pkg/api/transaction_pictures.go
Normal file
153
pkg/api/transaction_pictures.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
NormalSubcategoryDataManagement = 8
|
||||
NormalSubcategoryMapProxy = 9
|
||||
NormalSubcategoryTemplate = 10
|
||||
NormalSubcategoryPicture = 11
|
||||
)
|
||||
|
||||
// Error represents the specific error returned to user
|
||||
|
||||
13
pkg/errs/transaction_picture.go
Normal file
13
pkg/errs/transaction_picture.go
Normal 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")
|
||||
)
|
||||
@@ -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)),
|
||||
|
||||
28
pkg/models/transaction_picture_info.go
Normal file
28
pkg/models/transaction_picture_info.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
140
pkg/services/transaction_pictures.go
Normal file
140
pkg/services/transaction_pictures.go
Normal 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
|
||||
})
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
)
|
||||
|
||||
const avatarPathPrefix = "avatar"
|
||||
const transactionPicturePathPrefix = "transaction"
|
||||
|
||||
// StorageContainer contains the current object storage
|
||||
type StorageContainer struct {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "请求项目中有非法项目",
|
||||
|
||||
Reference in New Issue
Block a user