Skip to content

Commit

Permalink
admin: Add Accounts.
Browse files Browse the repository at this point in the history
In order to view account data in the database, add an api enpoint to the
admin server that retrieves all accounts and returns a slice of
accounts. Byte arrays are encoded in hex for readability.
  • Loading branch information
JoeGruffins committed Jun 5, 2020
1 parent 048ee40 commit d3cd976
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 1 deletion.
9 changes: 9 additions & 0 deletions server/admin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,12 @@ func (s *Server) apiSuspend(w http.ResponseWriter, r *http.Request) {
SuspendTime: APITime{suspEpoch.End},
})
}

// apiAccounts is the handler for the '/accounts' API request.
func (s *Server) apiAccounts(w http.ResponseWriter, _ *http.Request) {
accts, err := s.core.Accounts()
if err != nil {
http.Error(w, fmt.Sprintf("failed to retrieve accounts: %v", err), http.StatusInternalServerError)
}
writeJSON(w, accts)
}
3 changes: 3 additions & 0 deletions server/admin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"sync"
"time"

"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/market"
"github.com/decred/slog"
"github.com/go-chi/chi"
Expand All @@ -38,6 +39,7 @@ var (

// SvrCore is satisfied by server/dex.DEX.
type SvrCore interface {
Accounts() ([]*db.Account, error)
ConfigMsg() json.RawMessage
MarketRunning(mktName string) (found, running bool)
MarketStatus(mktName string) *market.Status
Expand Down Expand Up @@ -118,6 +120,7 @@ func NewServer(cfg *SrvConfig) (*Server, error) {
r.Use(middleware.AllowContentType("application/json"))
r.Get("/ping", s.apiPing)
r.Get("/config", s.apiConfig)
r.Get("/accounts", s.apiAccounts)

r.Get("/markets", s.apiMarkets)
r.Route("/market/{"+marketNameKey+"}", func(rm chi.Router) {
Expand Down
82 changes: 81 additions & 1 deletion server/admin/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/elliptic"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -21,6 +22,7 @@ import (
"time"

"decred.org/dcrdex/dex/encode"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/market"
"github.com/decred/dcrd/certgen"
"github.com/decred/slog"
Expand All @@ -41,7 +43,9 @@ type TMarket struct {
}

type TCore struct {
markets map[string]*TMarket
markets map[string]*TMarket
accounts []*db.Account
accountsErr error
}

func (c *TCore) ConfigMsg() json.RawMessage { return nil }
Expand Down Expand Up @@ -130,6 +134,8 @@ func (w *tResponseWriter) WriteHeader(statusCode int) {
w.code = statusCode
}

func (c *TCore) Accounts() ([]*db.Account, error) { return c.accounts, c.accountsErr }

// genCertPair generates a key/cert pair to the paths provided.
func genCertPair(certFile, keyFile string) error {
log.Infof("Generating TLS certificates...")
Expand Down Expand Up @@ -696,3 +702,77 @@ func TestAuthMiddleware(t *testing.T) {
wantAuthError(test.name, test.wantErr)
}
}

func TestAccounts(t *testing.T) {
core := &TCore{
accounts: []*db.Account{},
}
srv := &Server{
core: core,
}

mux := chi.NewRouter()
mux.Get("/accounts", srv.apiAccounts)

// No accounts.
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "https://localhost/accounts", nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusOK)
}
respBody := w.Body.String()
if respBody != "[]\n" {
t.Errorf("incorrect response body: %q", respBody)
}

// An account.
acct := &db.Account{
AccountID: "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc",
Pubkey: "0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19",
FeeAddress: "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k",
FeeCoin: "6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005",
BrokenRule: byte(255),
}
core.accounts = append(core.accounts, acct)

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/accounts", nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusOK)
}

exp := `[
{
"AccountID": "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc",
"Pubkey": "0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19",
"FeeAddress": "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k",
"FeeCoin": "6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005",
"BrokenRule": 255
}
]
`
if exp != w.Body.String() {
t.Errorf("unexpected response %q, wanted %q", w.Body.String(), exp)
}

// core.Accounts error
core.accountsErr = errors.New("error")

w = httptest.NewRecorder()
r, _ = http.NewRequest("GET", "https://localhost/accounts", nil)
r.RemoteAddr = "localhost"

mux.ServeHTTP(w, r)

if w.Code != http.StatusInternalServerError {
t.Fatalf("apiAccounts returned code %d, expected %d", w.Code, http.StatusInternalServerError)
}
}
30 changes: 30 additions & 0 deletions server/db/driver/pg/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package pg

import (
"database/sql"
"encoding/hex"
"errors"
"fmt"

"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/db"
"decred.org/dcrdex/server/db/driver/pg/internal"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrutil/v2"
Expand Down Expand Up @@ -43,6 +45,34 @@ func (a *Archiver) Account(aid account.AccountID) (*account.Account, bool, bool)
return acct, isPaid, isOpen
}

// Accounts returns data for all accounts. Byte array fields in the database are
// encoded as hex strings.
func (a *Archiver) Accounts() ([]*db.Account, error) {
stmt := fmt.Sprintf(internal.SelectAllAccounts, a.tables.accounts)
rows, err := a.db.Query(stmt)
if err != nil {
return nil, err
}
defer rows.Close()
var accts []*db.Account
for rows.Next() {
a := new(db.Account)
id, pubkey, feeCoin := []byte{}, []byte{}, []byte{}
err = rows.Scan(&id, &pubkey, &a.FeeAddress, &feeCoin, &a.BrokenRule)
if err != nil {
return nil, err
}
a.AccountID = hex.EncodeToString(id)
a.Pubkey = hex.EncodeToString(pubkey)
a.FeeCoin = hex.EncodeToString(feeCoin)
accts = append(accts, a)
}
if err = rows.Err(); err != nil {
return nil, err
}
return accts, nil
}

// CreateAccount creates an entry for a new account in the accounts table. A
// DCR registration fee address is created and returned.
func (a *Archiver) CreateAccount(acct *account.Account) (string, error) {
Expand Down
12 changes: 12 additions & 0 deletions server/db/driver/pg/accounts_online_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ func TestAccounts(t *testing.T) {
t.Fatalf("newly paid account marked as closed")
}

accts, err := archie.Accounts()
if err != nil {
t.Fatalf("error getting accounts: %v", err)
}
if accts[0].AccountID != "0a9912205b2cbab0c25c2de30bda9074de0ae23b065489a99199bad763f102cc" ||
accts[0].Pubkey != "0204988a498d5d19514b217e872b4dbd1cf071d365c4879e64ed5919881c97eb19" ||
accts[0].FeeAddress != "DsdQFmH3azyoGKJHt2ArJNxi35LCEgMqi8k" ||
accts[0].FeeCoin != "6e515ff861f2016fd0da2f3eccdf8290c03a9d116bfba2f6729e648bdc6e5aed00000005" ||
accts[0].BrokenRule != byte(0) {
t.Fatal("accounts has unexpected data")
}

// Close the account for failure to complete a swap.
archie.CloseAccount(tAcctID, account.FailureToAct)
_, _, open = archie.Account(tAcctID)
Expand Down
3 changes: 3 additions & 0 deletions server/db/driver/pg/internal/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const (
FROM %s
WHERE account_id = $1;`

// SelectAllAccounts retrieves all accounts.
SelectAllAccounts = `SELECT * FROM %s;`

// CreateAccount creates an entry for a new account.
CreateAccount = `INSERT INTO %s (account_id, pubkey, fee_address)
VALUES ($1, $2, $3);`
Expand Down
4 changes: 4 additions & 0 deletions server/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ type AccountArchiver interface {
// PayAccount sets the registration fee payment transaction details for the
// account, completing the registration process.
PayAccount(account.AccountID, []byte) error

// Accounts returns data for all accounts. Byte array fields in the
// database are encoded as hex strings.
Accounts() ([]*Account, error)
}

// MatchData represents an order pair match, but with just the order IDs instead
Expand Down
14 changes: 14 additions & 0 deletions server/db/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.

package db

// Account holds data returned by Accounts. Byte array fields in the database
// are encoded as hex strings.
type Account struct {
AccountID string
Pubkey string
FeeAddress string
FeeCoin string
BrokenRule byte
}
6 changes: 6 additions & 0 deletions server/dex/dex.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,9 @@ func (dm *DEX) SuspendMarket(name string, tSusp time.Time, persistBooks bool) *m

// TODO: resume by relaunching the market subsystems (Run)
// Resume / ResumeMarket

// Accounts returns data for all accounts. Byte array fields in the database are
// encoded as hex strings.
func (dm *DEX) Accounts() ([]*db.Account, error) {
return dm.storage.Accounts()
}
1 change: 1 addition & 0 deletions server/market/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func (ta *TArchivist) Account(account.AccountID) (acct *account.Account, paid, o
func (ta *TArchivist) CreateAccount(*account.Account) (string, error) { return "", nil }
func (ta *TArchivist) AccountRegAddr(account.AccountID) (string, error) { return "", nil }
func (ta *TArchivist) PayAccount(account.AccountID, []byte) error { return nil }
func (ta *TArchivist) Accounts() ([]*db.Account, error) { return nil, nil }
func (ta *TArchivist) Close() error { return nil }

func randomOrderID() order.OrderID {
Expand Down

0 comments on commit d3cd976

Please sign in to comment.