refactor: Use struct to parse API responses
- Return bytes for each request to the Flexpool API - Delegate JSON marshalling the higher functions - Use int64 when possible BREAKING CHANGE: database structure has to be updated to use integer instead of real. Please follow instructions to upgrade from 1.0 to 1.1. Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
parent
c4f3854861
commit
ebbd4ac2cd
8 changed files with 133 additions and 65 deletions
9
UPGRADES.md
Normal file
9
UPGRADES.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# 1.0 to 1.1
|
||||
|
||||
Some numeric types have been updated from **float64** to **int64**. Upgrade the database types by running the following migration:
|
||||
|
||||
```
|
||||
sqlite3 flexassistant.db < migrations/1.0_to_1.1.sql
|
||||
```
|
||||
|
||||
And replace `flexassistant.db` by the content of the `database_file` setting if it's has been changed.
|
104
client.go
104
client.go
|
@ -32,37 +32,8 @@ func NewFlexpoolClient() *FlexpoolClient {
|
|||
}
|
||||
}
|
||||
|
||||
// request to create an HTTPS request, call the Flexpool API, detect errors and return the result
|
||||
func (f *FlexpoolClient) request(url string) (result map[string]interface{}, err error) {
|
||||
log.Debugf("Requesting %s", url)
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("User-Agent", UserAgent)
|
||||
|
||||
resp, err := f.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
jsonBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json.Unmarshal(jsonBody, &result)
|
||||
|
||||
if result["error"] != nil {
|
||||
return nil, fmt.Errorf("Flexpool API error: %s", result["error"].(string))
|
||||
}
|
||||
return result["result"].(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
// requestBytes to create an HTTPS request, call the Flexpool API, detect errors and return the result in bytes
|
||||
func (f *FlexpoolClient) requestBytes(url string) ([]byte, error) {
|
||||
// request to create an HTTPS request, call the Flexpool API, detect errors and return the result in bytes
|
||||
func (f *FlexpoolClient) request(url string) ([]byte, error) {
|
||||
log.Debugf("Requesting %s", url)
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
|
@ -91,30 +62,55 @@ func (f *FlexpoolClient) requestBytes(url string) ([]byte, error) {
|
|||
return jsonBody, nil
|
||||
}
|
||||
|
||||
// BalanceResponse represents the JSON structure of the Flexpool API response for balance
|
||||
type BalanceResponse struct {
|
||||
Error string `json:"error"`
|
||||
Result struct {
|
||||
Balance int64 `json:"balance"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// MinerBalance returns the current unpaid balance
|
||||
func (f *FlexpoolClient) MinerBalance(coin string, address string) (float64, error) {
|
||||
response, err := f.request(fmt.Sprintf("%s/miner/balance?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
||||
func (f *FlexpoolClient) MinerBalance(coin string, address string) (int64, error) {
|
||||
body, err := f.request(fmt.Sprintf("%s/miner/balance?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return response["balance"].(float64), nil
|
||||
|
||||
var response BalanceResponse
|
||||
json.Unmarshal(body, &response)
|
||||
return response.Result.Balance, nil
|
||||
}
|
||||
|
||||
// PaymentsResponse represents the JSON structure of the Flexpool API response for payments
|
||||
type PaymentsResponse struct {
|
||||
Error string `json:"error"`
|
||||
Result struct {
|
||||
Data []struct {
|
||||
Hash string `json:"hash"`
|
||||
Value int64 `json:"value"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
} `json:"data"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// MinerPayments returns an ordered list of payments
|
||||
func (f *FlexpoolClient) MinerPayments(coin string, address string, limit int) (payments []*Payment, err error) {
|
||||
page := 0
|
||||
for {
|
||||
response, err := f.request(fmt.Sprintf("%s/miner/payments/?coin=%s&address=%s&page=%d", FlexpoolAPIURL, coin, address, page))
|
||||
body, err := f.request(fmt.Sprintf("%s/miner/payments/?coin=%s&address=%s&page=%d", FlexpoolAPIURL, coin, address, page))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, result := range response["data"].([]interface{}) {
|
||||
raw := result.(map[string]interface{})
|
||||
var response PaymentsResponse
|
||||
json.Unmarshal(body, &response)
|
||||
|
||||
for _, result := range response.Result.Data {
|
||||
payment := NewPayment(
|
||||
raw["hash"].(string),
|
||||
raw["value"].(float64),
|
||||
raw["timestamp"].(float64),
|
||||
result.Hash,
|
||||
result.Value,
|
||||
result.Timestamp,
|
||||
)
|
||||
payments = append(payments, payment)
|
||||
if len(payments) >= limit {
|
||||
|
@ -144,7 +140,7 @@ type WorkersResponse struct {
|
|||
|
||||
// MinerWorkers returns a list of workers given a miner address
|
||||
func (f *FlexpoolClient) MinerWorkers(coin string, address string) (workers []*Worker, err error) {
|
||||
body, err := f.requestBytes(fmt.Sprintf("%s/miner/workers?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
||||
body, err := f.request(fmt.Sprintf("%s/miner/workers?coin=%s&address=%s", FlexpoolAPIURL, coin, address))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -164,21 +160,35 @@ func (f *FlexpoolClient) MinerWorkers(coin string, address string) (workers []*W
|
|||
return workers, nil
|
||||
}
|
||||
|
||||
// BlocksResponse represents the JSON structure of the Flexpool API response for blocks
|
||||
type BlocksResponse struct {
|
||||
Error string `json:"error"`
|
||||
Result struct {
|
||||
Data []struct {
|
||||
Hash string `json:"hash"`
|
||||
Number int64 `json:"number"`
|
||||
Reward int64 `json:"reward"`
|
||||
} `json:"data"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// PoolBlocks returns an ordered list of blocks
|
||||
func (f *FlexpoolClient) PoolBlocks(coin string, limit int) (blocks []*Block, err error) {
|
||||
page := 0
|
||||
for {
|
||||
response, err := f.request(fmt.Sprintf("%s/pool/blocks/?coin=%s&page=%d", FlexpoolAPIURL, coin, page))
|
||||
body, err := f.request(fmt.Sprintf("%s/pool/blocks/?coin=%s&page=%d", FlexpoolAPIURL, coin, page))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, result := range response["data"].([]interface{}) {
|
||||
raw := result.(map[string]interface{})
|
||||
var response BlocksResponse
|
||||
json.Unmarshal(body, &response)
|
||||
|
||||
for _, result := range response.Result.Data {
|
||||
block := NewBlock(
|
||||
raw["hash"].(string),
|
||||
raw["number"].(float64),
|
||||
raw["reward"].(float64),
|
||||
result.Hash,
|
||||
result.Number,
|
||||
result.Reward,
|
||||
)
|
||||
blocks = append(blocks, block)
|
||||
if len(blocks) >= limit {
|
||||
|
|
2
main.go
2
main.go
|
@ -132,7 +132,7 @@ func main() {
|
|||
log.Warnf("Could not fetch unpaid balance: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Unpaid balance %.0f", balance)
|
||||
log.Debugf("Unpaid balance %d", balance)
|
||||
miner.Balance = balance
|
||||
if miner.Balance != dbMiner.Balance {
|
||||
dbMiner.Balance = balance
|
||||
|
|
49
migrations/1.0_to_1.1.sql
Normal file
49
migrations/1.0_to_1.1.sql
Normal file
|
@ -0,0 +1,49 @@
|
|||
-- Migrate from 1.0 to 1.1
|
||||
-- SQLite doesn't support ALTER TABLE... ALTER TYPE
|
||||
-- This migration file creates the new structures with "integer" types
|
||||
-- instead of "real", move data to this new table then drop the old one.
|
||||
|
||||
-- miners
|
||||
|
||||
ALTER TABLE `miners` RENAME TO `miners_old`;
|
||||
|
||||
CREATE TABLE `miners` (
|
||||
`id` integer,
|
||||
`created_at` datetime,
|
||||
`updated_at` datetime,
|
||||
`deleted_at` datetime,
|
||||
`coin` text,
|
||||
`address` text NOT NULL UNIQUE,
|
||||
`balance` integer,
|
||||
`last_payment_timestamp` integer,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
INSERT INTO `miners` SELECT * FROM `miners_old`;
|
||||
|
||||
DROP TABLE `miners_old`;
|
||||
|
||||
CREATE INDEX `idx_miners_deleted_at` ON `miners`(`deleted_at`);
|
||||
|
||||
|
||||
-- pools
|
||||
|
||||
ALTER TABLE `pools` RENAME TO `pools_old`;
|
||||
|
||||
CREATE TABLE `pools` (
|
||||
`id` integer,
|
||||
`created_at` datetime,
|
||||
`updated_at` datetime,
|
||||
`deleted_at` datetime,
|
||||
`coin` text NOT NULL UNIQUE,
|
||||
`last_block_number` integer,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
INSERT INTO `pools` SELECT * FROM `pools_old`;
|
||||
|
||||
DROP TABLE `pools_old`;
|
||||
|
||||
CREATE INDEX `idx_pools_deleted_at` ON `pools`(`deleted_at`);
|
||||
|
||||
SELECT "Database migrated to 1.1"
|
10
miner.go
10
miner.go
|
@ -19,8 +19,8 @@ type Miner struct {
|
|||
gorm.Model
|
||||
Coin string
|
||||
Address string `gorm:"unique;not null"`
|
||||
Balance float64
|
||||
LastPaymentTimestamp float64
|
||||
Balance int64
|
||||
LastPaymentTimestamp int64
|
||||
}
|
||||
|
||||
// NewMiner creates a Miner
|
||||
|
@ -56,12 +56,12 @@ func (m *Miner) String() string {
|
|||
// Payment to store payment attributes
|
||||
type Payment struct {
|
||||
Hash string
|
||||
Value float64
|
||||
Timestamp float64
|
||||
Value int64
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
// NewPayment creates a Payment
|
||||
func NewPayment(hash string, value float64, timestamp float64) *Payment {
|
||||
func NewPayment(hash string, value int64, timestamp int64) *Payment {
|
||||
return &Payment{
|
||||
Hash: hash,
|
||||
Value: value,
|
||||
|
|
|
@ -119,7 +119,7 @@ func (t *TelegramNotifier) NotifyBlock(pool Pool, block Block) error {
|
|||
return err
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("🎉 *%s* [#%.0f](%s) _%s_", verb, block.Number, url, ac.FormatMoney(convertedValue))
|
||||
message := fmt.Sprintf("🎉 *%s* [#%d](%s) _%s_", verb, block.Number, url, ac.FormatMoney(convertedValue))
|
||||
return t.sendMessage(message)
|
||||
}
|
||||
|
||||
|
|
12
pool.go
12
pool.go
|
@ -10,7 +10,7 @@ import (
|
|||
type Pool struct {
|
||||
gorm.Model
|
||||
Coin string `gorm:"unique;not null"`
|
||||
LastBlockNumber float64
|
||||
LastBlockNumber int64
|
||||
}
|
||||
|
||||
// NewPool creates a Pool
|
||||
|
@ -25,13 +25,13 @@ func (p *Pool) String() string {
|
|||
|
||||
// Block to store block attributes
|
||||
type Block struct {
|
||||
Hash string `gorm:"unique;not null"`
|
||||
Number float64 `gorm:"not null"`
|
||||
Reward float64 `gorm:"not null"`
|
||||
Hash string `gorm:"unique;not null"`
|
||||
Number int64 `gorm:"not null"`
|
||||
Reward int64 `gorm:"not null"`
|
||||
}
|
||||
|
||||
// NewBlock creates a Block
|
||||
func NewBlock(hash string, number float64, reward float64) *Block {
|
||||
func NewBlock(hash string, number int64, reward int64) *Block {
|
||||
return &Block{
|
||||
Hash: hash,
|
||||
Number: number,
|
||||
|
@ -41,5 +41,5 @@ func NewBlock(hash string, number float64, reward float64) *Block {
|
|||
|
||||
// String represents Block to a printable format
|
||||
func (b *Block) String() string {
|
||||
return fmt.Sprintf("Block<%.0f>", b.Number)
|
||||
return fmt.Sprintf("Block<%d>", b.Number)
|
||||
}
|
||||
|
|
10
utils.go
10
utils.go
|
@ -12,7 +12,7 @@ const MojoToXCHDivider = 1000000000000
|
|||
|
||||
// ConvertCurrency divides the smallest unit of the currency to the currency itself
|
||||
// Example: for "eth", convert from Weis to ETH
|
||||
func ConvertCurrency(coin string, value float64) (float64, error) {
|
||||
func ConvertCurrency(coin string, value int64) (float64, error) {
|
||||
switch coin {
|
||||
case "eth":
|
||||
return ConvertWeis(value), nil
|
||||
|
@ -24,13 +24,13 @@ func ConvertCurrency(coin string, value float64) (float64, error) {
|
|||
}
|
||||
|
||||
// ConvertWeis converts the value from Weis to ETH
|
||||
func ConvertWeis(value float64) float64 {
|
||||
return value / WeisToETHDivider
|
||||
func ConvertWeis(value int64) float64 {
|
||||
return float64(value) / WeisToETHDivider
|
||||
}
|
||||
|
||||
// ConvertMojo converts the value from Mojo to XCH
|
||||
func ConvertMojo(value float64) float64 {
|
||||
return value / MojoToXCHDivider
|
||||
func ConvertMojo(value int64) float64 {
|
||||
return float64(value) / MojoToXCHDivider
|
||||
}
|
||||
|
||||
// ConvertAction returns "Miner" for Ethereum and "Farmer" for Chia
|
||||
|
|
Reference in a new issue