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 @@
+