From 1e7b5227ec17c76a23bdd77d8fff85fb28a377af Mon Sep 17 00:00:00 2001 From: Vladimir Evgrafov Date: Tue, 31 Jul 2018 14:58:55 +0300 Subject: [PATCH] MUL-1794 MUL-1763: EOS support, account creation features --- .gitignore | 4 +- client/exchange-api.go | 10 +- client/rest.go | 258 +++++++++++++++++++++++++++++++++++- currencies/currencies.go | 10 +- eos/eos.go | 276 +++++++++++++++++++++++++++++++++++++++ eos/util.go | 53 ++++++++ multy-back.go | 11 +- store/store.go | 15 ++- store/util.go | 19 +++ 9 files changed, 634 insertions(+), 22 deletions(-) create mode 100644 eos/eos.go create mode 100644 eos/util.go create mode 100644 store/util.go diff --git a/.gitignore b/.gitignore index c45604b2..49b2e6d4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ cmd/state.json rpc.cert data-db -.env \ No newline at end of file +.env + +.idea \ No newline at end of file diff --git a/client/exchange-api.go b/client/exchange-api.go index c9bb68ad..cfff70fc 100644 --- a/client/exchange-api.go +++ b/client/exchange-api.go @@ -1,8 +1,8 @@ /* -Copyright 2018 Idealnaya rabota LLC -Licensed under Multy.io license. -See LICENSE for details -*/ + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ package client import ( @@ -11,8 +11,8 @@ import ( "strconv" "time" - "github.com/jekabolt/slf" "github.com/gorilla/websocket" + "github.com/jekabolt/slf" ) const ( diff --git a/client/rest.go b/client/rest.go index 294d38a2..d2f24463 100755 --- a/client/rest.go +++ b/client/rest.go @@ -1,8 +1,8 @@ /* -Copyright 2018 Idealnaya rabota LLC -Licensed under Multy.io license. -See LICENSE for details -*/ + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ package client import ( @@ -26,6 +26,8 @@ import ( "github.com/Multy-io/Multy-back/store" "github.com/jekabolt/slf" + eospb "github.com/Multy-io/Multy-EOS-node-service/proto" + "github.com/Multy-io/Multy-back/eos" btcpb "github.com/Multy-io/Multy-back/node-streamer/btc" ethpb "github.com/Multy-io/Multy-back/node-streamer/eth" "github.com/btcsuite/btcd/rpcclient" @@ -76,6 +78,7 @@ type RestClient struct { BTC *btc.BTCConn ETH *eth.ETHConn + EOS *eos.Conn MultyVerison store.ServerConfig Secretkey string DeviceVersions store.Versions @@ -91,6 +94,7 @@ func SetRestHandlers( donationAddresses []store.DonationInfo, btc *btc.BTCConn, eth *eth.ETHConn, + eos *eos.Conn, mv store.ServerConfig, secretkey string, deviceVersions store.Versions, @@ -101,6 +105,7 @@ func SetRestHandlers( donationAddresses: donationAddresses, BTC: btc, ETH: eth, + EOS: eos, MultyVerison: mv, Secretkey: secretkey, DeviceVersions: deviceVersions, @@ -127,6 +132,12 @@ func SetRestHandlers( v1.POST("/wallet/name", restClient.changeWalletName()) v1.POST("/resync/wallet/:currencyid/:networkid/:walletindex", restClient.resyncWallet()) v1.GET("/exchange/changelly/list", restClient.changellyListCurrencies()) + + // Only EOS for now + v1.POST("/account/create", restClient.accountCreate) + v1.GET("/account/check", restClient.accountCheck) + v1.GET("/account/price", restClient.accountPrice) + v1.GET("/account/get_by_key", restClient.accountGetByKey) } return restClient, nil } @@ -691,6 +702,31 @@ func (restClient *RestClient) deleteWallet() gin.HandlerFunc { code = http.StatusOK message = http.StatusText(http.StatusOK) + + case currencies.EOS: + wallet := user.GetWallet(networkid, currencyId, walletIndex) + for _, address := range wallet.Adresses { + balance, err := restClient.EOS.Client.GetAddressBalance(c, &eospb.Account{ + Name: address.Address, + }) + if err != nil { + restClient.log.Errorf("get balance: %s\t[addr=%s]", err, c.Request.RemoteAddr) + c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrAdressBalance, + }) + return + } + // Main EOS token has 4 digit precision + if balance.Balance != "0.0000 EOS" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrWalletNonZeroBalance, + }) + return + } + } + default: c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, @@ -1240,6 +1276,24 @@ func (restClient *RestClient) sendRawHDTransaction() gin.HandlerFunc { }) return } + case currencies.EOS: + // TODO: check if addr is watched? + resp, err := restClient.EOS.Client.SendRawTx(c, &eospb.RawTx{ + Transaction: []byte(rawTx.Transaction), + }) + if err != nil { + restClient.log.Errorf("sendRawHDTransaction: eos: %s", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": resp.TransactionId, + }) + return default: c.JSON(http.StatusBadRequest, gin.H{ @@ -1504,6 +1558,33 @@ func (restClient *RestClient) getWalletVerbose() gin.HandlerFunc { }) av = []ETHAddressVerbose{} + case currencies.EOS: + + user, err := restClient.userStore.GetUserByToken(token) + if err != nil { + restClient.log.Errorf("getAllWalletsVerbose: restClient.userStore.FindUser: %s\t[addr=%s]", err.Error(), c.Request.RemoteAddr) + } + + wallet := user.GetWallet(networkId, currencyId, walletIndex) + + balances, err := restClient.EOS.GetBalance(c, wallet) + if err != nil { + restClient.log.Errorf("getWalletVerbose: %s\t [addr=%s]", err, c.Request.RemoteAddr) + c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrNoWallet, + "wallet": wv, + }) + return + } + wv = append(wv, balances) + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": http.StatusText(http.StatusOK), + "wallet": wv, + }) + return + default: c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, @@ -1806,6 +1887,24 @@ func (restClient *RestClient) getAllWalletsVerbose() gin.HandlerFunc { Pending: pending, }) av = []ETHAddressVerbose{} + case currencies.EOS: + balances, err := restClient.EOS.GetBalance(c, wallet) + if err != nil { + restClient.log.Errorf("getWalletVerbose: %s\t [addr=%s]", err, c.Request.RemoteAddr) + c.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrNoWallet, + "wallet": wv, + }) + return + } + wv = append(wv, balances) + c.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": http.StatusText(http.StatusOK), + "wallet": wv, + }) + return default: } @@ -2048,6 +2147,15 @@ func (restClient *RestClient) getWalletTransactionsHistory() gin.HandlerFunc { } } + case currencies.EOS: + history, err := restClient.EOS.GetActionHistory(c, user.UserID, walletIndex, currencyId, networkid) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "message": http.StatusText(http.StatusOK), @@ -2195,6 +2303,15 @@ func (restClient *RestClient) resyncWallet() gin.HandlerFunc { if networkID == currencies.ETHTest { } + case currencies.EOS: + for _, address := range walletToResync.Adresses { + _, err := restClient.EOS.Client.ResyncAddress(c, &eospb.AddressToResync{ + Address: address.Address, + }) + if err != nil { + restClient.log.Errorf("eos resync: %s", err) + } + } } } @@ -2258,3 +2375,136 @@ type ChangellyReqest struct { Method string `json:"method"` Params []string `json:"params"` } + +// accountCheck checks if account exists +// and returns account info if it does +func (server *RestClient) accountCheck(ctx *gin.Context) { + _, err := getToken(ctx) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrHeaderError, + }) + return + } + var account eospb.Account + err = decodeBody(ctx, &account) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrRequestBodyError, + }) + return + } + info, err := server.EOS.Client.AccountCheck(ctx, &account) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } + ctx.JSON(http.StatusOK, info) + return +} + +// AccountCreateRequest is a struct for +// client to request EOS account creation +// basic account creation parameters +// are extended with multy storage specific ones +type accountCreateRequest struct { + // TODO: move away from this package + WalletParams + eospb.AccountCreateReq +} + +// accountCreate creates account and wallet for it +func (server *RestClient) accountCreate(ctx *gin.Context) { + token, err := getToken(ctx) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrHeaderError, + }) + return + } + var req accountCreateRequest + decodeBody(ctx, &req) + _, err = server.EOS.Client.AccountCreate(ctx, &req.AccountCreateReq) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } + // add freshly created account to a wallet + err = createCustomWallet(req.WalletParams, token, server, ctx) + if err != nil { + // TODO: create wallet error, need to preserve created account + ctx.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "message": http.StatusText(http.StatusOK), + }) +} + +// account price returns price for account creation +func (server *RestClient) accountPrice(ctx *gin.Context) { + type resources struct { + RAM uint64 `json:"ram"` + CPU float64 `json:"cpu"` + NET float64 `json:"net"` + } + var res resources + err := decodeBody(ctx, &res) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrRequestBodyError, + }) + return + } + price, err := server.EOS.Client.GetRAMPrice(ctx, &eospb.Empty{}) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "price": (price.Price * float64(res.RAM)) + res.CPU + res.NET, + }) + return +} +func (server *RestClient) accountGetByKey(ctx *gin.Context) { + var key eospb.PublicKey + err := decodeBody(ctx, &key) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "code": http.StatusBadRequest, + "message": msgErrRequestBodyError, + }) + return + } + accounts, err := server.EOS.Client.GetKeyAccounts(ctx, &key) + if err != nil { + server.log.Errorf("eos get_key_accounts: %s", err) + ctx.JSON(http.StatusInternalServerError, gin.H{ + "code": http.StatusInternalServerError, + "message": http.StatusText(http.StatusInternalServerError), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "code": http.StatusOK, + "account_names": accounts.AccountNames, + }) +} diff --git a/currencies/currencies.go b/currencies/currencies.go index 26de142c..0039de76 100644 --- a/currencies/currencies.go +++ b/currencies/currencies.go @@ -1,8 +1,8 @@ /* -Copyright 2018 Idealnaya rabota LLC -Licensed under Multy.io license. -See LICENSE for details -*/ + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ package currencies const ( @@ -179,6 +179,7 @@ const ( Ember = 170 Hcash = 171 HTMLCOIN = 172 + EOS = 194 AskCoin = 223 Smartcash = 224 ZenProtocol = 258 @@ -364,6 +365,7 @@ var CurrencyNames = map[int]string{ Ember: "Ember", Hcash: "Hcash", HTMLCOIN: "HTMLCOIN", + EOS: "EOS", AskCoin: "AskCoin", Smartcash: "Smartcash", ZenProtocol: "ZenProtocol", diff --git a/eos/eos.go b/eos/eos.go new file mode 100644 index 00000000..2b8bacd6 --- /dev/null +++ b/eos/eos.go @@ -0,0 +1,276 @@ +/* + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ + +package eos + +import ( + "context" + "encoding/hex" + "fmt" + "github.com/Multy-io/Multy-EOS-node-service/proto" + "github.com/Multy-io/Multy-back/currencies" + "github.com/Multy-io/Multy-back/store" + "github.com/bitly/go-nsq" + "github.com/gin-gonic/gin/json" + "github.com/jekabolt/slf" + "google.golang.org/grpc" + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" + "io" + "strings" +) + +var log = slf.WithContext("eos") + +// Conn is EOS node connection handler +type Conn struct { + Client proto.NodeCommunicationsClient + WatchAddresses chan proto.WatchAddress + + networkID int + + nsq *nsq.Producer + + restoreState *mgo.Collection + txStore *mgo.Collection +} + +// NewConn creates all the connections +func NewConn(nodes []store.CoinType) (*Conn, error) { + grpcConn, err := getGrpc(nodes, currencies.EOS, currencies.Main) + if err != nil { + return nil, fmt.Errorf("getGrpc: %s", err) + } + conn := &Conn{ + Client: proto.NewNodeCommunicationsClient(grpcConn), + //TODO: chanel buffering? + WatchAddresses: make(chan proto.WatchAddress), + } + + conn.runAsyncHandlers() + + return conn, nil +} + +// getGrpc creates grpc connection to the corresponding node service +func getGrpc(nodes []store.CoinType, currencyID, networkID int) (*grpc.ClientConn, error) { + for _, ct := range nodes { + if ct.Š”urrencyID == currencyID && ct.NetworkID == networkID { + return grpc.Dial(ct.GRPCUrl, grpc.WithInsecure()) + } + } + return nil, fmt.Errorf("no such coin in config") +} + +// runAsyncHandlers starts async events goroutines +func (conn *Conn) runAsyncHandlers() { + go conn.watchAddressesHandler() + + go conn.newBlockHandler() + + go conn.newTxHandler() +} + +// newBlockHandler processes block height info down to consumers +func (conn *Conn) newBlockHandler() { + stream, err := conn.Client.NewBlock(context.TODO(), &proto.Empty{}) + if err != nil { + log.Errorf("new block: %s", err) + } + for { + newBlock, err := stream.Recv() + if err == io.EOF { + return + } + if err != nil { + log.Errorf("new block: %s", err) + } + + err = conn.updateRestoreState(newBlock) + if err != nil { + log.Errorf("update restore state %s", err) + } + } +} + +// watchAddressesHandler passes watched addresses to node service +func (conn *Conn) watchAddressesHandler() { + for { + addr := <-conn.WatchAddresses + resp, err := conn.Client.AddNewAddress(context.TODO(), &addr) + if err != nil { + log.Errorf("EventAddNewAddress: %s", err) + } + log.Debugf("EventAddNewAddress Reply %s", resp) + + resp, err = conn.Client.ResyncAddress(context.TODO(), &proto.AddressToResync{ + Address: addr.GetAddress(), + }) + if err != nil { + log.Errorf("EventResyncAddress: cli.EventResyncAddress %s\n", err.Error()) + } + log.Debugf("EventResyncAddress Reply %s", resp) + } +} + +// updateRestoreState updates db's restore state data +func (conn *Conn) updateRestoreState(height *proto.BlockHeight) error { + query := bson.M{"currencyid": currencies.EOS, "networkid": conn.networkID} + update := bson.M{ + "$set": bson.M{ + "blockheight": height.GetHeadBlockNum(), + }, + } + + err := conn.restoreState.Update(query, update) + if err == mgo.ErrNotFound { + return conn.restoreState.Insert(store.LastState{ + BlockHeight: int64(height.GetHeadBlockNum()), + CurrencyID: currencies.EOS, + NetworkID: conn.networkID, + }) + } + + return err +} + +// newTxHandler processes NewTx stream down to consumers +func (conn *Conn) newTxHandler() { + stream, err := conn.Client.NewTx(context.TODO(), &proto.Empty{}) + if err != nil { + log.Errorf("new tx handler: %s", err) + return + } + for { + tx, err := stream.Recv() + if err == io.EOF { + return + } + if err != nil { + log.Errorf("new tx %s", err) + continue + } + + err = conn.saveActionRecord(tx) + if err != nil { + log.Errorf("eos save action: %s", err) + } + if !tx.GetResync() { + err = conn.notifyClients(tx) + if err != nil { + log.Errorf("eos publish action: %s", err) + } + } + } +} + +// saveActionRecord updates db with action data +func (conn *Conn) saveActionRecord(action *proto.Action) error { + sel := bson.M{ + "user_id": action.UserID, + "wallet_index": action.WalletIndex, + "transaction_id": action.TransactionId, + "action_index": action.ActionIndex, + } + + var stored proto.Action + err := conn.txStore.Find(sel).One(&stored) + if err == mgo.ErrNotFound { + err = conn.txStore.Insert(action) + } + // node service fetches new blocks + // so no need for db update + return err +} + +// assetToString presents Asset type as string +// based on eos-go Asset.String method +func assetToString(a *proto.Asset) string { + strInt := fmt.Sprintf("%d", a.Amount) + if len(strInt) < int(a.Precision+1) { + // prepend `0` for the difference: + strInt = strings.Repeat("0", int(a.Precision+uint32(1))-len(strInt)) + strInt + } + + var result string + if a.Precision == 0 { + result = strInt + } else { + result = strInt[:len(strInt)-int(a.Precision)] + "." + strInt[len(strInt)-int(a.Precision):] + } + + return fmt.Sprintf("%s %s", result, a.Symbol) +} + +// notifyClients notifies clients about new EOS action +func (conn *Conn) notifyClients(action *proto.Action) error { + msg := store.TransactionWithUserID{ + UserID: action.UserID, + NotificationMsg: &store.WsTxNotify{ + WalletIndex: int(action.WalletIndex), + NetworkID: conn.networkID, + CurrencyID: currencies.EOS, + Address: action.Address, + TransactionType: int(action.Type), + TxID: fmt.Sprintf("%s:%d", hex.EncodeToString(action.TransactionId), action.ActionIndex), + Amount: assetToString(action.Amount), + From: action.From, + To: action.To, + }, + } + + toSend, err := json.Marshal(msg) + if err != nil { + return err + } + + return conn.nsq.Publish(store.TopicTransaction, toSend) +} + +// GetActions gets EOS actions for user's wallet from db +func (conn *Conn) GetActions(userID string, walletIndex, currencyID, networkID int) ([]proto.Action, error) { + var actions []proto.Action + err := conn.txStore.Find(bson.M{"userid": userID, "walletindex": walletIndex}).All(&actions) + if err == mgo.ErrNotFound { + log.Errorf("no eos transactions for userid %s", userID) + return actions, err + } + return actions, err +} + +// GetActionHistory gets history for users wallet +// and checks confirmations +func (conn *Conn) GetActionHistory(ctx context.Context, userID string, walletIndex, currencyID, networkID int) ([]ActionHistoryRecord, error) { + state, err := conn.Client.GetChainState(ctx, &proto.Empty{}) + //TODO: get actions from node (needs to be implemented in eos-go) + if err != nil { + return nil, err + } + actions, err := conn.GetActions(userID, walletIndex, currencyID, networkID) + if err != nil { + return nil, err + } + + history := make([]ActionHistoryRecord, len(actions)) + for i := range actions { + history[i].Action = actions[i] + if state.LastIrreversibleBlockNum >= history[i].BlockNum { // action is in irreversible state + history[i].Confirmations = 1 + } else { + history[i].Confirmations = 0 + } + } + + return history, err +} + +// ActionHistoryRecord is a record that is pushed to client +// it is extended with confirmations data +type ActionHistoryRecord struct { + proto.Action + + Confirmations int `json:"confirmations"` +} diff --git a/eos/util.go b/eos/util.go new file mode 100644 index 00000000..98966c30 --- /dev/null +++ b/eos/util.go @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ + +package eos + +import ( + "context" + "fmt" + "github.com/Multy-io/Multy-EOS-node-service/proto" + "github.com/Multy-io/Multy-back/store" +) + +type Balance struct { + CurrencyID int `json:"currency_id"` + NetworkID int `json:"network_id"` + WalletIndex int `json:"wallet_index"` + WalletName string `json:"wallet_name"` + LastActionTime int64 `json:"last_action_time"` + DateOfCreation int64 `json:"date_of_creation"` + Assets []*proto.Asset `json:"assets"` +} + +func (conn *Conn) GetBalance(ctx context.Context, wallet store.Wallet) ([]Balance, error) { + if len(wallet.Adresses) == 0 { + return nil, fmt.Errorf("wallet has no addresses") + } + balances := make([]Balance, 0, len(wallet.Adresses)) + for _, addr := range wallet.Adresses { + // get EOS token for now + balance, err := conn.Client.GetTokenBalance(ctx, &proto.BalanceReq{ + Account: addr.Address, + Symbol: "EOS", + }) + if err != nil { + // skip address log error + log.Errorf("GetTokenBalance(%s): %s", addr.Address, err) + continue + } + balances = append(balances, Balance{ + Assets: balance.Assets, + WalletIndex: wallet.WalletIndex, + CurrencyID: wallet.CurrencyID, + NetworkID: wallet.NetworkID, + DateOfCreation: wallet.DateOfCreation, + LastActionTime: wallet.LastActionTime, + WalletName: wallet.WalletName, + }) + } + return balances, nil +} diff --git a/multy-back.go b/multy-back.go index d32e9cea..96fec9ef 100755 --- a/multy-back.go +++ b/multy-back.go @@ -1,8 +1,8 @@ /* -Copyright 2018 Idealnaya rabota LLC -Licensed under Multy.io license. -See LICENSE for details -*/ + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ package multyback import ( @@ -13,6 +13,7 @@ import ( "github.com/Multy-io/Multy-back/btc" "github.com/Multy-io/Multy-back/client" "github.com/Multy-io/Multy-back/currencies" + "github.com/Multy-io/Multy-back/eos" "github.com/Multy-io/Multy-back/eth" btcpb "github.com/Multy-io/Multy-back/node-streamer/btc" ethpb "github.com/Multy-io/Multy-back/node-streamer/eth" @@ -52,6 +53,7 @@ type Multy struct { BTC *btc.BTCConn ETH *eth.ETHConn + EOS *eos.Conn } // Init initializes Multy instance @@ -263,6 +265,7 @@ func (multy *Multy) initHttpRoutes(conf *Configuration) error { conf.DonationAddresses, multy.BTC, multy.ETH, + multy.EOS, conf.MultyVerison, conf.Secretkey, conf.DeviceVersions, diff --git a/store/store.go b/store/store.go index 22127312..f4723755 100644 --- a/store/store.go +++ b/store/store.go @@ -1,8 +1,8 @@ /* -Copyright 2018 Idealnaya rabota LLC -Licensed under Multy.io license. -See LICENSE for details -*/ + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ package store import ( @@ -64,6 +64,7 @@ type Conf struct { type UserStore interface { GetUserByDevice(device bson.M, user *User) + GetUserByToken(token string) (User, error) Update(sel, update bson.M) error Insert(user User) error Close() error @@ -404,3 +405,9 @@ func (mStore *MongoUserStore) Close() error { mStore.session.Close() return nil } + +func (mStore *MongoUserStore) GetUserByToken(token string) (User, error) { + var user User + err := mStore.FindUser(bson.M{"devices.JWT": token}, &user) + return user, err +} diff --git a/store/util.go b/store/util.go new file mode 100644 index 00000000..824cfbf5 --- /dev/null +++ b/store/util.go @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Idealnaya rabota LLC + * Licensed under Multy.io license. + * See LICENSE for details + */ + +package store + +// GetWallet gets wallet from user +// using concrete network id, currency id and wallet index +func (user *User) GetWallet(networkID, currencyID, walletIndex int) (wallet Wallet) { + for _, w := range user.Wallets { + if w.NetworkID == networkID && w.CurrencyID == currencyID && w.WalletIndex == walletIndex { + wallet = w + return + } + } + return +}