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  }