allow user to limit time scope for editing transaction

This commit is contained in:
MaysWind
2021-03-11 01:16:30 +08:00
parent d3d1bc59b2
commit bd26796184
13 changed files with 292 additions and 73 deletions
+68 -5
View File
@@ -17,6 +17,7 @@ type TransactionsApi struct {
transactionCategories *services.TransactionCategoryService
transactionTags *services.TransactionTagService
accounts *services.AccountService
users *services.UserService
}
// Initialize a transaction api singleton instance
@@ -26,6 +27,7 @@ var (
transactionCategories: services.TransactionCategories,
transactionTags: services.TransactionTags,
accounts: services.Accounts,
users: services.Users,
}
)
@@ -40,6 +42,16 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(uid)
if err != nil {
if !errs.IsCustomError(err) {
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
var allCategoryIds []int64
if transactionListReq.CategoryId > 0 {
@@ -119,7 +131,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (interface{},
transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId)
}
transactionEditable := transaction.IsEditable(allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
transactionEditable := transaction.IsEditable(user, transactionListReq.UtcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResps.Items[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
}
@@ -144,6 +156,16 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(uid)
if err != nil {
if !errs.IsCustomError(err) {
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
var allCategoryIds []int64
if transactionListReq.CategoryId > 0 {
@@ -213,7 +235,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (interfac
transaction = a.transactions.GetRelatedTransferTransaction(transaction, transaction.RelatedId)
}
transactionEditable := transaction.IsEditable(allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
transactionEditable := transaction.IsEditable(user, transactionListReq.UtcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResps[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
}
@@ -232,6 +254,16 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(uid)
if err != nil {
if !errs.IsCustomError(err) {
log.ErrorfWithRequestId(c, "[transactions.TransactionGetHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionGetReq.Id)
if err != nil {
@@ -272,7 +304,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.Context) (interface{}, *
return nil, errs.ErrOperationFailed
}
transactionEditable := transaction.IsEditable(accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
transactionEditable := transaction.IsEditable(user, transactionGetReq.UtcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
@@ -320,7 +352,22 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(uid)
if err != nil {
if !errs.IsCustomError(err) {
log.ErrorfWithRequestId(c, "[transactions.TransactionCreateHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
transaction := a.createNewTransactionModel(uid, &transactionCreateReq)
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset)
if !transactionEditable {
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
}
err = a.transactions.CreateTransaction(transaction, tagIds)
@@ -331,7 +378,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.Context) (interface{}
log.InfofWithRequestId(c, "[transactions.TransactionCreateHandler] user \"uid:%d\" has created a new transaction \"id:%d\" successfully", uid, transaction.TransactionId)
transactionResp := transaction.ToTransactionInfoResponse(nil, true)
transactionResp := transaction.ToTransactionInfoResponse(tagIds, transactionEditable)
return transactionResp, nil
}
@@ -354,6 +401,16 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
}
uid := c.GetCurrentUid()
user, err := a.users.GetUserById(uid)
if err != nil {
if !errs.IsCustomError(err) {
log.ErrorfWithRequestId(c, "[transactions.TransactionModifyHandler] failed to get user, because %s", err.Error())
}
return nil, errs.ErrUserNotFound
}
transaction, err := a.transactions.GetTransactionByTransactionId(uid, transactionModifyReq.Id)
if err != nil {
@@ -409,6 +466,12 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
addTransactionTagIds = tagIds
}
transactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset)
if !transactionEditable {
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
}
err = a.transactions.ModifyTransaction(newTransaction, addTransactionTagIds, removeTransactionTagIds)
if err != nil {
@@ -419,7 +482,7 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
log.InfofWithRequestId(c, "[transactions.TransactionModifyHandler] user \"uid:%d\" has updated transaction \"id:%d\" successfully", uid, transactionModifyReq.Id)
newTransaction.Type = transaction.Type
newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, true)
newTransactionResp := newTransaction.ToTransactionInfoResponse(tagIds, transactionEditable)
return newTransactionResp, nil
}
+15 -6
View File
@@ -45,12 +45,13 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
userRegisterReq.Nickname = strings.TrimSpace(userRegisterReq.Nickname)
user := &models.User{
Username: userRegisterReq.Username,
Email: userRegisterReq.Email,
Nickname: userRegisterReq.Nickname,
Password: userRegisterReq.Password,
DefaultCurrency: userRegisterReq.DefaultCurrency,
FirstDayOfWeek: userRegisterReq.FirstDayOfWeek,
Username: userRegisterReq.Username,
Email: userRegisterReq.Email,
Nickname: userRegisterReq.Nickname,
Password: userRegisterReq.Password,
DefaultCurrency: userRegisterReq.DefaultCurrency,
FirstDayOfWeek: userRegisterReq.FirstDayOfWeek,
TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL,
}
err = a.users.CreateUser(user)
@@ -167,6 +168,14 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
userNew.FirstDayOfWeek = models.WEEKDAY_INVALID
}
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
anythingUpdate = true
} else {
userNew.TransactionEditScope = models.TRANSACTION_EDIT_SCOPE_INVALID
}
if !anythingUpdate {
return nil, errs.ErrNothingWillBeUpdated
}
+2
View File
@@ -18,4 +18,6 @@ var (
ErrCannotAddTransactionToHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 11, http.StatusBadRequest, "cannot add transaction to hidden account")
ErrCannotModifyTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 12, http.StatusBadRequest, "cannot modify transaction of hidden account")
ErrCannotDeleteTransactionInHiddenAccount = NewNormalError(NormalSubcategoryTransaction, 13, http.StatusBadRequest, "cannot delete transaction in hidden account")
ErrCannotCreateTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 14, http.StatusBadRequest, "cannot add transaction with this transaction time")
ErrCannotModifyTransactionWithThisTransactionTime = NewNormalError(NormalSubcategoryTransaction, 15, http.StatusBadRequest, "cannot edit transaction with this transaction time")
)
+11 -2
View File
@@ -62,6 +62,7 @@ type TransactionCreateRequest struct {
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
UtcOffset int `json:"utcOffset" binding:"required,min=-720,max=840"`
}
// TransactionModifyRequest represents all parameters of transaction modification request
@@ -75,6 +76,7 @@ type TransactionModifyRequest struct {
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
TagIds []string `json:"tagIds"`
Comment string `json:"comment" binding:"max=255"`
UtcOffset int `json:"utcOffset" binding:"required,min=-720,max=840"`
}
// TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request
@@ -86,6 +88,7 @@ type TransactionListByMaxTimeRequest struct {
MaxTime int64 `form:"max_time" binding:"min=0"`
MinTime int64 `form:"min_time" binding:"min=0"`
Count int `form:"count" binding:"required,min=1,max=50"`
UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"`
}
// TransactionListInMonthByPageRequest represents all parameters of transaction listing by month request
@@ -98,11 +101,13 @@ type TransactionListInMonthByPageRequest struct {
Keyword string `form:"keyword"`
Page int `form:"page" binding:"required,min=1"`
Count int `form:"count" binding:"required,min=1,max=50"`
UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"`
}
// TransactionGetRequest represents all parameters of transaction getting request
type TransactionGetRequest struct {
Id int64 `form:"id,string" binding:"required,min=1"`
Id int64 `form:"id,string" binding:"required,min=1"`
UtcOffset int `form:"utc_offset" binding:"required,min=-720,max=840"`
}
// TransactionDeleteRequest represents all parameters of transaction deleting request
@@ -133,7 +138,11 @@ type TransactionInfoPageWrapperResponse struct {
}
// IsEditable returns whether this transaction can be edited
func (t *Transaction) IsEditable(account *Account, relatedAccount *Account) bool {
func (t *Transaction) IsEditable(currentUser *User, utcOffset int, account *Account, relatedAccount *Account) bool {
if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) {
return false
}
if account == nil || account.Hidden {
return false
}
+116 -47
View File
@@ -1,5 +1,11 @@
package models
import (
"time"
"github.com/mayswind/lab/pkg/utils"
)
// UserType represents user type
type UserType byte
@@ -25,34 +31,51 @@ const (
WEEKDAY_INVALID WeekDay = 255
)
// TransactionEditScope represents the scope which transaction can be edited
type TransactionEditScope byte
// Historical Transaction Edit Scopes
const (
TRANSACTION_EDIT_SCOPE_NONE TransactionEditScope = 0
TRANSACTION_EDIT_SCOPE_ALL TransactionEditScope = 1
TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER TransactionEditScope = 2
TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER TransactionEditScope = 3
TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER TransactionEditScope = 4
TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER TransactionEditScope = 5
TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER TransactionEditScope = 6
TRANSACTION_EDIT_SCOPE_INVALID TransactionEditScope = 255
)
// User represents user data stored in database
type User struct {
Uid int64 `xorm:"PK"`
Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
Password string `xorm:"VARCHAR(64) NOT NULL"`
Salt string `xorm:"VARCHAR(10) NOT NULL"`
Rands string `xorm:"VARCHAR(10) NOT NULL"`
Type UserType `xorm:"TINYINT NOT NULL"`
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
IsAdmin bool `xorm:"NOT NULL"`
Deleted bool `xorm:"NOT NULL"`
EmailVerified bool `xorm:"NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
LastLoginUnixTime int64
Uid int64 `xorm:"PK"`
Username string `xorm:"VARCHAR(32) UNIQUE NOT NULL"`
Email string `xorm:"VARCHAR(100) UNIQUE NOT NULL"`
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
Password string `xorm:"VARCHAR(64) NOT NULL"`
Salt string `xorm:"VARCHAR(10) NOT NULL"`
Rands string `xorm:"VARCHAR(10) NOT NULL"`
Type UserType `xorm:"TINYINT NOT NULL"`
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
IsAdmin bool `xorm:"NOT NULL"`
Deleted bool `xorm:"NOT NULL"`
EmailVerified bool `xorm:"NOT NULL"`
CreatedUnixTime int64
UpdatedUnixTime int64
DeletedUnixTime int64
LastLoginUnixTime int64
}
// UserBasicInfo represents a view-object of user basic info
type UserBasicInfo struct {
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
}
// UserLoginRequest represents all parameters of user login request
@@ -73,12 +96,13 @@ type UserRegisterRequest struct {
// UserProfileUpdateRequest represents all parameters of user updating profile request
type UserProfileUpdateRequest struct {
Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
Password string `json:"password" binding:"omitempty,min=6,max=128"`
OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"`
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
Email string `json:"email" binding:"omitempty,notBlank,max=100,validEmail"`
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
Password string `json:"password" binding:"omitempty,min=6,max=128"`
OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"`
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
}
// UserProfileUpdateResponse represents the data returns to frontend after updating profile
@@ -89,35 +113,80 @@ type UserProfileUpdateResponse struct {
// UserProfileResponse represents a view-object of user profile
type UserProfileResponse struct {
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
Type UserType `json:"type"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
LastLoginAt int64 `json:"lastLoginAt"`
Username string `json:"username"`
Email string `json:"email"`
Nickname string `json:"nickname"`
Type UserType `json:"type"`
DefaultCurrency string `json:"defaultCurrency"`
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
LastLoginAt int64 `json:"lastLoginAt"`
}
// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, utcOffset int) bool {
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE {
return false
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL {
return true
}
now := time.Now()
transactionUnixTime := utils.GetUnixTimeFromTransactionTime(transactionTime)
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_LAST_24H_OR_LATER {
return transactionUnixTime >= now.Unix()-24*60*60
}
_, serverUtcOffset := now.Zone()
serverTodayFirstUnixTime := now.Unix() - int64(now.Hour()*60*60+now.Minute()*60+now.Second())
clientTodayFirstUnixTime := serverTodayFirstUnixTime + int64(utcOffset*60-serverUtcOffset)
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER {
return transactionUnixTime >= clientTodayFirstUnixTime
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_WEEK_OR_LATER {
dayOfWeek := int(now.Weekday()) - int(u.FirstDayOfWeek)
if dayOfWeek < 0 {
dayOfWeek += 7
}
clientWeekFirstUnixTime := clientTodayFirstUnixTime - int64(dayOfWeek*24*60*60)
return transactionUnixTime >= clientWeekFirstUnixTime
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_MONTH_OR_LATER {
clientMonthFirstUnixTime := clientTodayFirstUnixTime - int64((now.Day()-1)*24*60*60)
return transactionUnixTime >= clientMonthFirstUnixTime
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
clientYearFirstUnixTime := clientTodayFirstUnixTime - int64((now.YearDay()-1)*24*60*60)
return transactionUnixTime >= clientYearFirstUnixTime
}
return false
}
// ToUserBasicInfo returns a user basic view-object according to database model
func (u *User) ToUserBasicInfo() *UserBasicInfo {
return &UserBasicInfo{
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
TransactionEditScope: u.TransactionEditScope,
}
}
// ToUserProfileResponse returns a user profile view-object according to database model
func (u *User) ToUserProfileResponse() *UserProfileResponse {
return &UserProfileResponse{
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
Type: u.Type,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
LastLoginAt: u.LastLoginUnixTime,
Username: u.Username,
Email: u.Email,
Nickname: u.Nickname,
Type: u.Type,
DefaultCurrency: u.DefaultCurrency,
FirstDayOfWeek: u.FirstDayOfWeek,
TransactionEditScope: u.TransactionEditScope,
LastLoginAt: u.LastLoginUnixTime,
}
}
+4
View File
@@ -198,6 +198,10 @@ func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err
updateCols = append(updateCols, "first_day_of_week")
}
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
updateCols = append(updateCols, "transaction_edit_scope")
}
user.UpdatedUnixTime = now
updateCols = append(updateCols, "updated_unix_time")