aboutsummaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/const.go9
-rw-r--r--api/const_string.go4
-rw-r--r--api/external_services.go40
-rw-r--r--api/market_config.go76
-rw-r--r--api/markets.go29
-rw-r--r--api/routes.go11
6 files changed, 166 insertions, 3 deletions
diff --git a/api/const.go b/api/const.go
index 2edd6f4..1b22355 100644
--- a/api/const.go
+++ b/api/const.go
@@ -6,15 +6,19 @@ import "net/http"
6type Status uint32 6type Status uint32
7type ErrorCode uint32 7type ErrorCode uint32
8 8
9const EXTERNAL_SERVICE_TIMEOUT_SECONDS = 10
10
9const ( 11const (
10 OK Status = iota 12 OK Status = iota
11 NOK 13 NOK
12 14
13 BadRequest ErrorCode = iota + 1 15 BadRequest ErrorCode = iota + 1
14 EmailExists 16 EmailExists
17 ExternalServiceTimeout
15 InternalError 18 InternalError
16 InvalidCredentials 19 InvalidCredentials
17 InvalidEmail 20 InvalidEmail
21 InvalidMarketCredentials
18 InvalidOtp 22 InvalidOtp
19 InvalidPassword 23 InvalidPassword
20 NeedOtpValidation 24 NeedOtpValidation
@@ -31,7 +35,7 @@ func StatusToHttpCode(status Status, code ErrorCode) int {
31 } 35 }
32 36
33 switch code { 37 switch code {
34 case BadRequest, InvalidPassword, InvalidEmail: 38 case BadRequest, InvalidPassword, InvalidEmail, InvalidMarketCredentials:
35 return http.StatusBadRequest 39 return http.StatusBadRequest
36 40
37 case InvalidCredentials, InvalidOtp: 41 case InvalidCredentials, InvalidOtp:
@@ -45,6 +49,9 @@ func StatusToHttpCode(status Status, code ErrorCode) int {
45 49
46 case NotFound: 50 case NotFound:
47 return http.StatusNotFound 51 return http.StatusNotFound
52
53 case ExternalServiceTimeout:
54 return http.StatusGatewayTimeout
48 } 55 }
49 56
50 return http.StatusInternalServerError 57 return http.StatusInternalServerError
diff --git a/api/const_string.go b/api/const_string.go
index 611db40..e4b9e50 100644
--- a/api/const_string.go
+++ b/api/const_string.go
@@ -15,9 +15,9 @@ func (i Status) String() string {
15 return _Status_name[_Status_index[i]:_Status_index[i+1]] 15 return _Status_name[_Status_index[i]:_Status_index[i+1]]
16} 16}
17 17
18const _ErrorCode_name = "BadRequestEmailExistsInternalErrorInvalidCredentialsInvalidEmailInvalidOtpInvalidPasswordNeedOtpValidationNotAuthorizedNotFoundOtpAlreadySetupOtpNotSetupUserNotConfirmed" 18const _ErrorCode_name = "BadRequestEmailExistsExternalServiceTimeoutInternalErrorInvalidCredentialsInvalidEmailInvalidMarketCredentialsInvalidOtpInvalidPasswordNeedOtpValidationNotAuthorizedNotFoundOtpAlreadySetupOtpNotSetupUserNotConfirmed"
19 19
20var _ErrorCode_index = [...]uint8{0, 10, 21, 34, 52, 64, 74, 89, 106, 119, 127, 142, 153, 169} 20var _ErrorCode_index = [...]uint8{0, 10, 21, 43, 56, 74, 86, 110, 120, 135, 152, 165, 173, 188, 199, 215}
21 21
22func (i ErrorCode) String() string { 22func (i ErrorCode) String() string {
23 i -= 3 23 i -= 3
diff --git a/api/external_services.go b/api/external_services.go
new file mode 100644
index 0000000..c467171
--- /dev/null
+++ b/api/external_services.go
@@ -0,0 +1,40 @@
1package api
2
3import (
4 "context"
5 "fmt"
6 "time"
7)
8
9// Use this to call external services. It will handle timeout and request cancellation gracefully.
10func CallExternalService(tag string, timeout time.Duration, routine func() *Error) *Error {
11 routineDone := make(chan *Error)
12
13 go func() {
14 routineDone <- routine()
15 }()
16
17 ctx, cancel := context.WithTimeout(context.Background(), timeout)
18 defer cancel()
19
20 select {
21 case err := <-routineDone:
22 return err
23 case <-ctx.Done():
24 return &Error{ExternalServiceTimeout, "external service timeout", fmt.Errorf("'%v' routine timeouted", tag)}
25 }
26}
27
28var ErrorChan chan error
29
30func ErrorMonitoring() {
31 for {
32 err := <-ErrorChan
33 log.Errorf("error: %v", err)
34 }
35}
36
37func init() {
38 ErrorChan = make(chan error)
39 go ErrorMonitoring()
40}
diff --git a/api/market_config.go b/api/market_config.go
index 3fd10ae..d85af4d 100644
--- a/api/market_config.go
+++ b/api/market_config.go
@@ -2,7 +2,13 @@ package api
2 2
3import ( 3import (
4 "fmt" 4 "fmt"
5 "strings"
6 "time"
5 7
8 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/markets"
9
10 "github.com/jloup/utils"
11 "github.com/shopspring/decimal"
6 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" 12 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
7) 13)
8 14
@@ -42,6 +48,73 @@ func (q MarketConfigQuery) Run() (interface{}, *Error) {
42 return config.Config, nil 48 return config.Config, nil
43} 49}
44 50
51type MarketBalanceQuery struct {
52 In struct {
53 User db.User
54 Market string
55 Currency string
56 }
57}
58
59func (q MarketBalanceQuery) ValidateParams() *Error {
60 if q.In.Market != "poloniex" {
61 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)}
62 }
63
64 // TODO: we should request market for available currencies.
65 if q.In.Currency != "BTC" && q.In.Currency != "USDT" && q.In.Currency != "ETH" {
66 return &Error{BadRequest, "invalid currency, accept [BTC, USDT, ETH]", fmt.Errorf("'%v' is not a valid currency", q.In.Currency)}
67 }
68
69 return nil
70}
71
72func (q MarketBalanceQuery) Run() (interface{}, *Error) {
73 config, err := db.GetUserMarketConfig(q.In.User.Id, q.In.Market)
74 if err != nil {
75 return nil, NewInternalError(err)
76 }
77
78 if config.Config["key"] == "" || config.Config["secret"] == "" {
79 return nil, &Error{BadRequest, "your credentials for this market are not setup", fmt.Errorf("'%v' credentials are not setup", q.In.Market)}
80 }
81
82 result := struct {
83 Value decimal.Decimal `json:"value"`
84 ValueCurrency string `json:"valueCurrency"`
85 Balance map[string]decimal.Decimal `json:"balance"`
86 }{}
87
88 resultErr := CallExternalService(fmt.Sprintf("'%s' GetBalanceValue", q.In.Market), EXTERNAL_SERVICE_TIMEOUT_SECONDS*time.Second, func() *Error {
89 balance, err := Poloniex.GetBalance(config.Config["key"], config.Config["secret"])
90
91 if utils.ErrIs(err, markets.InvalidCredentials) {
92 return &Error{InvalidMarketCredentials, "wrong market credentials", fmt.Errorf("wrong '%v' market credentials", q.In.Market)}
93 }
94
95 if err != nil {
96 return NewInternalError(err)
97 }
98
99 value, err := Poloniex.ComputeAccountBalanceValue(balance, q.In.Currency)
100 if err != nil {
101 return NewInternalError(err)
102 }
103
104 result.Balance = balance
105 result.ValueCurrency = q.In.Currency
106 result.Value = value.Round(8)
107
108 return nil
109 })
110
111 if resultErr != nil {
112 return nil, resultErr
113 }
114
115 return &result, nil
116}
117
45type UpdateMarketConfigQuery struct { 118type UpdateMarketConfigQuery struct {
46 In struct { 119 In struct {
47 User db.User 120 User db.User
@@ -56,6 +129,9 @@ func (q UpdateMarketConfigQuery) ValidateParams() *Error {
56 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)} 129 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)}
57 } 130 }
58 131
132 q.In.Secret = strings.TrimSpace(q.In.Secret)
133 q.In.Key = strings.TrimSpace(q.In.Key)
134
59 return nil 135 return nil
60} 136}
61 137
diff --git a/api/markets.go b/api/markets.go
new file mode 100644
index 0000000..60fb912
--- /dev/null
+++ b/api/markets.go
@@ -0,0 +1,29 @@
1package api
2
3import (
4 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/markets"
5)
6
7var Poloniex *markets.Poloniex
8
9func OpenMarketsConnection() error {
10 for {
11 err := Poloniex.StartTicker()
12 if err != nil {
13 return err
14 }
15 log.Warn("connection to poloniex stream ended, restarting it...")
16 }
17}
18
19func init() {
20 Poloniex = markets.NewPoloniex()
21
22 // We open markets connections in the background as it can take time.
23 go func() {
24 err := OpenMarketsConnection()
25 if err != nil {
26 ErrorChan <- err
27 }
28 }()
29}
diff --git a/api/routes.go b/api/routes.go
index d7e712c..cdf3dd9 100644
--- a/api/routes.go
+++ b/api/routes.go
@@ -41,6 +41,7 @@ var Groups = []Group{
41 []Route{ 41 []Route{
42 {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"}, 42 {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"},
43 {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"}, 43 {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"},
44 {"GET", []gin.HandlerFunc{GetMarketBalance}, "/:name/balance"},
44 }, 45 },
45 }, 46 },
46} 47}
@@ -111,6 +112,16 @@ func GetMarketConfig(c *gin.Context) {
111 RunQuery(query, c) 112 RunQuery(query, c)
112} 113}
113 114
115func GetMarketBalance(c *gin.Context) {
116 query := &MarketBalanceQuery{}
117
118 query.In.User = GetUser(c)
119 query.In.Market = c.Param("name")
120 query.In.Currency = c.Query("currency")
121
122 RunQuery(query, c)
123}
124
114func UpdateMarketConfig(c *gin.Context) { 125func UpdateMarketConfig(c *gin.Context) {
115 query := &UpdateMarketConfigQuery{} 126 query := &UpdateMarketConfigQuery{}
116 127