From 7fbfa7143463cb8be97e6a15e255967b07688e72 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sat, 31 Aug 2024 23:57:28 +0800 Subject: [PATCH] support transaction pictures --- cmd/webserver.go | 1 + pkg/api/transaction_pictures.go | 21 ++ pkg/api/transactions.go | 16 +- pkg/errs/transaction_picture.go | 1 + pkg/models/transaction_picture_info.go | 5 + pkg/services/transaction_pictures.go | 30 +++ src/lib/server_settings.js | 4 + src/lib/services.js | 27 +++ src/lib/transaction.js | 5 + src/locales/en.json | 5 + src/locales/zh_Hans.json | 5 + src/mobile-main.js | 6 + src/stores/transaction.js | 36 ++- .../transactions/list/dialogs/EditDialog.vue | 216 ++++++++++++++++- src/views/mobile/transactions/EditPage.vue | 227 +++++++++++++++++- 15 files changed, 595 insertions(+), 10 deletions(-) diff --git a/cmd/webserver.go b/cmd/webserver.go index 3c420a81..ce9e4163 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -320,6 +320,7 @@ func startWebServer(c *core.CliContext) error { // Transaction Pictures if config.EnableTransactionPictures { apiV1Route.POST("/transaction/pictures/upload.json", bindApi(api.TransactionPictures.TransactionPictureUploadHandler)) + apiV1Route.POST("/transaction/pictures/remove_unused.json", bindApi(api.TransactionPictures.TransactionPictureRemoveUnusedHandler)) } // Transaction Categories diff --git a/pkg/api/transaction_pictures.go b/pkg/api/transaction_pictures.go index 33381fc0..d36e2bea 100644 --- a/pkg/api/transaction_pictures.go +++ b/pkg/api/transaction_pictures.go @@ -144,6 +144,27 @@ func (a *TransactionPicturesApi) TransactionPictureGetHandler(c *core.WebContext return pictureData, contentType, nil } +// TransactionPictureRemoveUnusedHandler removes unused transaction picture by request parameters for current user +func (a *TransactionPicturesApi) TransactionPictureRemoveUnusedHandler(c *core.WebContext) (any, *errs.Error) { + var pictureDeleteReq models.TransactionPictureUnusedDeleteRequest + err := c.ShouldBindJSON(&pictureDeleteReq) + + if err != nil { + log.Warnf(c, "[transaction_pictures.TransactionPictureRemoveUnusedHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + err = a.pictures.RemoveUnusedTransactionPicture(c, uid, pictureDeleteReq.Id) + + if err != nil { + log.Errorf(c, "[transaction_pictures.TransactionPictureRemoveUnusedHandler] failed to remove unused transaction picture for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + return true, nil +} + func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo { return &models.TransactionPictureInfo{ Uid: uid, diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index c4dd3154..50731f91 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -589,7 +589,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs. } } - if transactionGetReq.WithPictures { + if transactionGetReq.WithPictures && a.CurrentConfig().EnableTransactionPictures { pictureInfos, err = a.transactionPictures.GetPictureInfosByTransactionId(c, uid, transaction.TransactionId) if err != nil { @@ -622,7 +622,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs. transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) } - if transactionGetReq.WithPictures { + if transactionGetReq.WithPictures && a.CurrentConfig().EnableTransactionPictures { transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos) } @@ -653,6 +653,10 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er return nil, errs.ErrTransactionPictureIdInvalid } + if len(pictureIds) > 10 { + return nil, errs.ErrTransactionPictureTooMuch + } + if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER { log.Warnf(c, "[transactions.TransactionCreateHandler] transaction type is invalid") return nil, errs.ErrTransactionTypeInvalid @@ -775,6 +779,10 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er return nil, errs.ErrTransactionPictureIdInvalid } + if len(pictureIds) > 10 { + return nil, errs.ErrTransactionPictureTooMuch + } + uid := c.GetCurrentUid() user, err := a.users.GetUserById(c, uid) @@ -1214,7 +1222,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u } } - if withPictures { + if withPictures && a.CurrentConfig().EnableTransactionPictures { pictureInfoMap, err = a.transactionPictures.GetPictureInfosByTransactionIds(c, uid, utils.ToUniqueInt64Slice(a.transactions.GetTransactionIds(transactions))) if err != nil { @@ -1256,7 +1264,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap) } - if withPictures { + if withPictures && a.CurrentConfig().EnableTransactionPictures { pictureInfos, exists := pictureInfoMap[transaction.TransactionId] if exists { diff --git a/pkg/errs/transaction_picture.go b/pkg/errs/transaction_picture.go index 9a13002d..904f6f1c 100644 --- a/pkg/errs/transaction_picture.go +++ b/pkg/errs/transaction_picture.go @@ -10,4 +10,5 @@ var ( 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") + ErrTransactionPictureTooMuch = NewNormalError(NormalSubcategoryPicture, 6, http.StatusBadRequest, "transaction pictures too much") ) diff --git a/pkg/models/transaction_picture_info.go b/pkg/models/transaction_picture_info.go index 57cf48cf..00478545 100644 --- a/pkg/models/transaction_picture_info.go +++ b/pkg/models/transaction_picture_info.go @@ -15,6 +15,11 @@ type TransactionPictureInfo struct { DeletedUnixTime int64 } +// TransactionPictureUnusedDeleteRequest represents all parameters of unused transaction picture deleting request +type TransactionPictureUnusedDeleteRequest struct { + Id int64 `json:"id,string" binding:"required,min=1"` +} + // TransactionPictureInfoBasicResponse represents a view-object of transaction picture basic info type TransactionPictureInfoBasicResponse struct { PictureId int64 `json:"pictureId,string"` diff --git a/pkg/services/transaction_pictures.go b/pkg/services/transaction_pictures.go index f426db08..922ae994 100644 --- a/pkg/services/transaction_pictures.go +++ b/pkg/services/transaction_pictures.go @@ -211,6 +211,36 @@ func (s *TransactionPictureService) UploadPicture(c core.Context, pictureInfo *m }) } +// RemoveUnusedTransactionPicture removes the unused transaction picture of specified user +func (s *TransactionPictureService) RemoveUnusedTransactionPicture(c core.Context, uid int64, pictureId int64) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + if pictureId <= 0 { + return errs.ErrTransactionPictureIdInvalid + } + + now := time.Now().Unix() + + updateModel := &models.TransactionPictureInfo{ + Deleted: true, + DeletedUnixTime: now, + } + + return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error { + deletedRows, err := sess.ID(pictureId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", uid, false, models.TransactionPictureNewPictureTransactionId).Update(updateModel) + + if err != nil { + return err + } else if deletedRows < 1 { + return errs.ErrTransactionPictureNotFound + } + + return err + }) +} + // GetPictureInfoMapByList returns a transaction picture info list map by a list func (s *TransactionPictureService) GetPictureInfoMapByList(pictureInfos []*models.TransactionPictureInfo) map[int64]*models.TransactionPictureInfo { pictureInfoMap := make(map[int64]*models.TransactionPictureInfo) diff --git a/src/lib/server_settings.js b/src/lib/server_settings.js index 00328820..b2cbcac5 100644 --- a/src/lib/server_settings.js +++ b/src/lib/server_settings.js @@ -41,6 +41,10 @@ export function isUserVerifyEmailEnabled() { return getServerSetting('v') === '1'; } +export function isTransactionPicturesEnabled() { + return getServerSetting('p') === '1'; +} + export function isUserScheduledTransactionEnabled() { return getServerSetting('s') === '1'; } diff --git a/src/lib/services.js b/src/lib/services.js index 314d0eaa..1fadd195 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -443,6 +443,11 @@ export default { clientSessionId: clientSessionId }); }, + removeUnusedTransactionPicture: ({ id }) => { + return axios.post('v1/transaction/pictures/remove_unused.json', { + id + }); + }, getAllTransactionCategories: () => { return axios.get('v1/transaction/categories/list.json'); }, @@ -651,5 +656,27 @@ export default { } else { return avatarUrl + '?' + params.join('&'); } + }, + getTransactionPictureUrlWithToken(pictureUrl, disableBrowserCache) { + if (!pictureUrl) { + return pictureUrl; + } + + const params = []; + params.push('token=' + userState.getToken()); + + if (disableBrowserCache) { + if (isBoolean(disableBrowserCache)) { + params.push('_nocache=' + generateRandomUUID()); + } else { + params.push('_nocache=' + disableBrowserCache); + } + } + + if (pictureUrl.indexOf('?') >= 0) { + return pictureUrl + '&' + params.join('&'); + } else { + return pictureUrl + '?' + params.join('&'); + } } }; diff --git a/src/lib/transaction.js b/src/lib/transaction.js index d4ad9edc..6e9dd924 100644 --- a/src/lib/transaction.js +++ b/src/lib/transaction.js @@ -165,6 +165,11 @@ export function setTransactionModelByTransaction(transaction, transaction2, allC transaction.hideAmount = transaction2.hideAmount; transaction.tagIds = transaction2.tagIds || []; + + if (setContextData) { + transaction.pictures = transaction2.pictures || []; + } + transaction.comment = transaction2.comment; if (setContextData) { diff --git a/src/locales/en.json b/src/locales/en.json index 01767f25..b62c9acd 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1094,6 +1094,7 @@ "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", + "transaction pictures too much": "There are too many transaction pictures", "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", @@ -1466,7 +1467,11 @@ "Unable to save transaction": "Unable to save transaction", "You have added a new transaction": "You have added a new transaction", "You have saved this transaction": "You have saved this transaction", + "Add Picture": "Add Picture", + "Remove Picture": "Remove Picture", + "Are you sure you want to remove this transaction picture?": "Are you sure you want to remove this transaction picture?", "Unable to upload transaction picture": "Unable to upload transaction picture", + "Unable to remove transaction picture": "Unable to remove transaction picture", "Search transaction description": "Search transaction description", "Unable to retrieve transaction list": "Unable to retrieve transaction list", "Custom Date Range": "Custom Date Range", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index e7acb197..dc73812c 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1094,6 +1094,7 @@ "transaction picture is empty": "交易图片文件为空", "transaction picture not exists": "交易图片不存在", "transaction picture file extension invalid": "交易图片文件扩展名无效", + "transaction pictures too much": "交易图片过多", "query items cannot be blank": "请求项目不能为空", "query items too much": "请求项目过多", "query items have invalid item": "请求项目中有非法项目", @@ -1466,7 +1467,11 @@ "Unable to save transaction": "无法保存交易", "You have added a new transaction": "您已经添加新交易", "You have saved this transaction": "您已经保存该交易", + "Add Picture": "添加图片", + "Remove Picture": "删除图片", + "Are you sure you want to remove this transaction picture?": "您确定要删除这张交易图片?", "Unable to upload transaction picture": "无法上传交易图片", + "Unable to remove transaction picture": "无法删除交易图片", "Search transaction description": "搜索交易描述", "Unable to retrieve transaction list": "无法获取交易列表", "Custom Date Range": "自定义日期范围", diff --git a/src/mobile-main.js b/src/mobile-main.js index f1fb7437..445c78b4 100644 --- a/src/mobile-main.js +++ b/src/mobile-main.js @@ -34,6 +34,8 @@ import Framework7Tooltip from 'framework7/components/tooltip'; import Framework7Skeleton from 'framework7/components/skeleton'; import Framework7Treeview from 'framework7/components/treeview'; import Framework7Typography from 'framework7/components/typography'; +import Framework7Swiper from 'framework7/components/swiper'; +import Framework7PhotoBrowser from 'framework7/components/photo-browser'; import Framework7Vue, { registerComponents } from 'framework7-vue/bundle'; import 'framework7/css'; @@ -68,6 +70,8 @@ import 'framework7/components/tooltip/css'; import 'framework7/components/skeleton/css'; import 'framework7/components/treeview/css'; import 'framework7/components/typography/css'; +import 'framework7/components/swiper/css'; +import 'framework7/components/photo-browser/css'; import 'framework7-icons'; import 'line-awesome/dist/line-awesome/css/line-awesome.css'; @@ -158,6 +162,8 @@ Framework7.use([ Framework7Skeleton, Framework7Treeview, Framework7Typography, + Framework7Swiper, + Framework7PhotoBrowser, Framework7Vue ]); diff --git a/src/stores/transaction.js b/src/stores/transaction.js index 6d1b6674..e6e9cc50 100644 --- a/src/stores/transaction.js +++ b/src/stores/transaction.js @@ -451,6 +451,7 @@ export const useTransactionsStore = defineStore('transactions', { destinationAmount: 0, hideAmount: false, tagIds: [], + pictures: [], comment: '', geoLocation: null }; @@ -873,7 +874,7 @@ export const useTransactionsStore = defineStore('transactions', { } } - transaction.pictureIds = pictureIds; + submitTransaction.pictureIds = pictureIds; } if (isEdit) { @@ -1027,6 +1028,39 @@ export const useTransactionsStore = defineStore('transactions', { }); }); }, + removeUnusedTransactionPicture({ pictureInfo }) { + return new Promise((resolve, reject) => { + services.removeUnusedTransactionPicture({ + id: pictureInfo.pictureId + }).then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to remove transaction picture' }); + return; + } + + resolve(data.result); + }).catch(error => { + logger.error('failed to remove transaction picture', error); + + if (error.response && error.response.data && error.response.data.errorMessage) { + reject({ error: error.response.data }); + } else if (!error.processed) { + reject({ message: 'Unable to remove transaction picture' }); + } else { + reject(error); + } + }); + }); + }, + getTransactionPictureUrl(pictureInfo, disableBrowserCache) { + if (!pictureInfo || !pictureInfo.originalUrl) { + return null; + } + + return services.getTransactionPictureUrlWithToken(pictureInfo.originalUrl, disableBrowserCache); + }, collapseMonthInTransactionList({ month, collapse }) { if (month) { month.opened = !collapse; diff --git a/src/views/desktop/transactions/list/dialogs/EditDialog.vue b/src/views/desktop/transactions/list/dialogs/EditDialog.vue index 20000355..ba72cc64 100644 --- a/src/views/desktop/transactions/list/dialogs/EditDialog.vue +++ b/src/views/desktop/transactions/list/dialogs/EditDialog.vue @@ -61,6 +61,9 @@ {{ $t('Location on Map') }} + + {{ $t('Pictures') }} + @@ -314,6 +317,38 @@ + + + + + + + +
+ + + +
+
+
+ + + {{ $t('Add Picture') }} + + + + +
+
@@ -342,6 +377,7 @@ +