github.com/condensat/bank-core@v0.1.0/database/query/account.go (about) 1 // Copyright 2020 Condensat Tech. All rights reserved. 2 // Use of this source code is governed by a MIT 3 // license that can be found in the LICENSE file. 4 5 package query 6 7 import ( 8 "errors" 9 10 "github.com/condensat/bank-core/database" 11 "github.com/condensat/bank-core/database/model" 12 13 "github.com/jinzhu/gorm" 14 ) 15 16 const ( 17 AccountNameDefault = "default" 18 AccountNameWildcard = "*" 19 ) 20 21 var ( 22 ErrAccountExists = errors.New("Account Exists") 23 ErrAccountNotFound = errors.New("Account Not Found") 24 ) 25 26 func CreateAccount(db database.Context, account model.Account) (model.Account, error) { 27 switch gdb := db.DB().(type) { 28 case *gorm.DB: 29 30 if len(account.Name) == 0 { 31 account.Name = AccountNameDefault 32 } 33 34 if !UserExists(db, account.UserID) { 35 return model.Account{}, ErrUserNotFound 36 } 37 38 if !CurrencyExists(db, account.CurrencyName) { 39 return model.Account{}, ErrCurrencyNotFound 40 } 41 42 if AccountsExists(db, account.UserID, account.CurrencyName, account.Name) { 43 return model.Account{}, ErrAccountExists 44 } 45 46 var result model.Account 47 err := gdb. 48 Where(model.Account{ 49 UserID: account.UserID, 50 CurrencyName: account.CurrencyName, 51 Name: account.Name, 52 }). 53 Assign(account). 54 FirstOrCreate(&result).Error 55 56 if err != nil { 57 return model.Account{}, err 58 } 59 60 // Create init operation 61 _, err = txApppendAccountOperation(db, model.NewInitOperation(result.ID, 0)) 62 if err != nil { 63 return model.Account{}, err 64 } 65 66 return result, err 67 68 default: 69 return model.Account{}, database.ErrInvalidDatabase 70 } 71 } 72 73 // AccountsExists 74 func AccountsExists(db database.Context, userID model.UserID, currency model.CurrencyName, name model.AccountName) bool { 75 entries, err := GetAccountsByUserAndCurrencyAndName(db, userID, currency, name) 76 77 return err == nil && len(entries) > 0 78 } 79 80 func GetAccountByID(db database.Context, accountID model.AccountID) (model.Account, error) { 81 var result model.Account 82 83 gdb := db.DB().(*gorm.DB) 84 if gdb == nil { 85 return result, database.ErrInvalidDatabase 86 } 87 88 err := gdb.Model(&model.Account{}). 89 Scopes(ScopeAccountID(accountID)). 90 First(&result).Error 91 92 return result, err 93 } 94 95 func GetUserAccounts(db database.Context, userID model.UserID) ([]model.AccountID, error) { 96 var result []model.AccountID 97 98 gdb := db.DB().(*gorm.DB) 99 if gdb == nil { 100 return result, database.ErrInvalidDatabase 101 } 102 103 var list []*model.Account 104 err := gdb.Model(&model.Account{}). 105 Scopes(ScopeUserID(userID)). 106 Find(&list).Error 107 108 if err != nil && err != gorm.ErrRecordNotFound { 109 return nil, err 110 } 111 112 return convertAccountIds(list), err 113 } 114 115 func convertAccountIds(list []*model.Account) []model.AccountID { 116 var result []model.AccountID 117 for _, curr := range list { 118 if curr != nil { 119 result = append(result, curr.ID) 120 } 121 } 122 123 return result[:] 124 } 125 126 // GetAccountsByNameAndCurrency 127 func GetAccountsByUserAndCurrencyAndName(db database.Context, userID model.UserID, currency model.CurrencyName, name model.AccountName) ([]model.Account, error) { 128 return QueryAccountList(db, userID, currency, name) 129 } 130 131 type AccountSummary struct { 132 CurrencyName string 133 Balance float64 134 TotalLocked float64 135 } 136 137 type AccountInfos struct { 138 Count int 139 Active int 140 Accounts []AccountSummary 141 } 142 143 func AccountsInfos(db database.Context) (AccountInfos, error) { 144 return AccountsInfosByUser(db, 0) 145 } 146 147 func AccountsInfosByUser(db database.Context, userID model.UserID) (AccountInfos, error) { 148 gdb := db.DB().(*gorm.DB) 149 if gdb == nil { 150 return AccountInfos{}, database.ErrInvalidDatabase 151 } 152 153 var totalAccounts int64 154 err := gdb.Model(&model.Account{}). 155 Where(model.Account{UserID: userID}). 156 Count(&totalAccounts).Error 157 if err != nil { 158 return AccountInfos{}, err 159 } 160 161 subQueryAccount := gdb.Model(&model.Account{}). 162 Select("id as aid, currency_name"). 163 SubQuery() 164 165 if userID != 0 { 166 subQueryAccount = gdb.Model(&model.Account{}). 167 Select("id as aid, currency_name"). 168 Where(model.Account{UserID: userID}). 169 SubQuery() 170 } 171 172 var activeAccounts int64 173 err = gdb.Model(&model.AccountState{}). 174 Joins("JOIN (?) AS a ON a.aid = account_id", subQueryAccount). 175 Where(&model.AccountState{ 176 State: model.AccountStatusNormal, 177 }).Count(&activeAccounts).Error 178 if err != nil { 179 return AccountInfos{}, err 180 } 181 182 subQueryLast := gdb.Model(&model.AccountOperation{}). 183 Select("MAX(id)"). 184 Group("account_id"). 185 SubQuery() 186 187 var list []*AccountSummary 188 err = gdb.Table("account_operation"). 189 Joins("JOIN (?) AS a ON a.aid = account_id", subQueryAccount). 190 Where("id IN (?)", subQueryLast). 191 Group("currency_name"). 192 Select("currency_name, SUM(balance) as balance, SUM(total_locked) as total_locked"). 193 Find(&list).Error 194 195 if err != nil && err != gorm.ErrRecordNotFound { 196 return AccountInfos{}, err 197 } 198 199 return AccountInfos{ 200 Count: int(totalAccounts), 201 Active: int(activeAccounts), 202 Accounts: convertAccountSummaryList(list), 203 }, nil 204 } 205 206 func convertAccountSummaryList(list []*AccountSummary) []AccountSummary { 207 var result []AccountSummary 208 for _, curr := range list { 209 if curr != nil { 210 result = append(result, *curr) 211 } 212 } 213 214 return result[:] 215 } 216 217 func AccountPagingCount(db database.Context, countByPage int) (int, error) { 218 if countByPage <= 0 { 219 countByPage = 1 220 } 221 222 switch gdb := db.DB().(type) { 223 case *gorm.DB: 224 225 var result int 226 err := gdb. 227 Model(&model.Account{}). 228 Count(&result).Error 229 var partialPage int 230 if result%countByPage > 0 { 231 partialPage = 1 232 } 233 return result/countByPage + partialPage, err 234 235 default: 236 return 0, database.ErrInvalidDatabase 237 } 238 } 239 240 func AccountPage(db database.Context, accountID model.AccountID, countByPage int) ([]model.Account, error) { 241 switch gdb := db.DB().(type) { 242 case *gorm.DB: 243 244 if accountID < 1 { 245 accountID = 1 246 } 247 if countByPage <= 0 { 248 countByPage = 1 249 } 250 251 var list []*model.Account 252 err := gdb.Model(&model.Account{}). 253 Where("id >= ?", accountID). 254 Order("id ASC"). 255 Limit(countByPage). 256 Find(&list).Error 257 258 if err != nil && err != gorm.ErrRecordNotFound { 259 return nil, err 260 } 261 262 return convertAccount(list), nil 263 264 default: 265 return nil, database.ErrInvalidDatabase 266 } 267 } 268 269 func convertAccount(list []*model.Account) []model.Account { 270 var result []model.Account 271 for _, curr := range list { 272 if curr != nil { 273 result = append(result, *curr) 274 } 275 } 276 277 return result[:] 278 } 279 280 // QueryAccountList 281 func QueryAccountList(db database.Context, userID model.UserID, currency model.CurrencyName, name model.AccountName) ([]model.Account, error) { 282 gdb := db.DB().(*gorm.DB) 283 if gdb == nil { 284 return nil, database.ErrInvalidDatabase 285 } 286 287 var filters []func(db *gorm.DB) *gorm.DB 288 if userID == 0 { 289 return nil, errors.New("UserId is mandatory") 290 } 291 292 // default account name if empty 293 if len(name) == 0 { 294 name = AccountNameDefault 295 } 296 297 filters = append(filters, ScopeUserID(userID)) 298 // manage wildcards 299 if currency != "*" { 300 filters = append(filters, ScopeAccountCurrencyName(currency)) 301 } 302 if name != "*" { 303 filters = append(filters, ScopeAccountName(name)) 304 } 305 306 var list []*model.Account 307 err := gdb.Model(&model.Account{}). 308 Scopes(filters...). 309 Find(&list).Error 310 311 if err != nil && err != gorm.ErrRecordNotFound { 312 return nil, err 313 } 314 315 return convertAccountList(list), nil 316 } 317 318 // ScopeAccountID 319 func ScopeAccountID(accountID model.AccountID) func(db *gorm.DB) *gorm.DB { 320 return func(db *gorm.DB) *gorm.DB { 321 return db.Where(reqAccountID(), accountID) 322 } 323 } 324 325 // ScopeUserID 326 func ScopeUserID(userID model.UserID) func(db *gorm.DB) *gorm.DB { 327 return func(db *gorm.DB) *gorm.DB { 328 return db.Where(reqUserID(), userID) 329 } 330 } 331 332 // ScopeCurencyName 333 func ScopeAccountCurrencyName(name model.CurrencyName) func(db *gorm.DB) *gorm.DB { 334 return func(db *gorm.DB) *gorm.DB { 335 return db.Where(reqAccountCurrencyName(), name) 336 } 337 } 338 339 // ScopeAccountName 340 func ScopeAccountName(name model.AccountName) func(db *gorm.DB) *gorm.DB { 341 return func(db *gorm.DB) *gorm.DB { 342 return db.Where(reqAccountName(), name) 343 } 344 } 345 346 func convertAccountList(list []*model.Account) []model.Account { 347 var result []model.Account 348 for _, curr := range list { 349 if curr == nil { 350 continue 351 } 352 result = append(result, *curr) 353 } 354 355 return result[:] 356 } 357 358 const ( 359 colID = "id" 360 colUserID = "user_id" 361 colAccountCurrencyName = "currency_name" 362 colAccountName = "name" 363 ) 364 365 func accountColumnNames() []string { 366 return []string{ 367 colID, 368 colUserID, 369 colAccountCurrencyName, 370 colAccountName, 371 } 372 } 373 374 // zero allocation querys string for scope 375 func reqAccountID() string { 376 var req [len(colID) + len(reqEQ)]byte 377 off := 0 378 off += copy(req[off:], colID) 379 copy(req[off:], reqEQ) 380 381 return string(req[:]) 382 } 383 384 // zero allocation querys string for scope 385 func reqUserID() string { 386 var req [len(colUserID) + len(reqEQ)]byte 387 off := 0 388 off += copy(req[off:], colUserID) 389 copy(req[off:], reqEQ) 390 391 return string(req[:]) 392 } 393 394 func reqAccountCurrencyName() string { 395 var req [len(colAccountCurrencyName) + len(reqEQ)]byte 396 off := 0 397 off += copy(req[off:], colAccountCurrencyName) 398 copy(req[off:], reqEQ) 399 400 return string(req[:]) 401 } 402 403 func reqAccountName() string { 404 var req [len(colAccountName) + len(reqEQ)]byte 405 off := 0 406 off += copy(req[off:], colAccountName) 407 copy(req[off:], reqEQ) 408 409 return string(req[:]) 410 }