Commit e9ab2181 authored by Will Cory's avatar Will Cory

feat: hook up api to dao

parent fd0a77f7
...@@ -2,79 +2,12 @@ package api ...@@ -2,79 +2,12 @@ package api
import ( import (
"encoding/json" "encoding/json"
"net/http" "github.com/ethereum-optimism/optimism/indexer/database"
"strconv" "github.com/ethereum/go-ethereum/common"
"time"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"net/http"
) )
// TODO in this pr most of these types should be coming from the ORM instead
// DepositsDAO represents the Database Access Object for deposits
type DepositDAO interface {
GetDeposits(limit int, cursor string, sortDirection string) ([]Deposit, string, bool, error)
}
// WithdrawalDAO represents the Database Access Object for deposits
type WithdrawalDAO interface {
GetWithdrawals(limit int, cursor string, sortDirection string, sortBy string) ([]Withdrawal, string, bool, error)
}
// Deposit data structure
type Deposit struct {
Guid string `json:"guid"`
Amount string `json:"amount"`
BlockNumber int `json:"blockNumber"`
BlockTimestamp time.Time `json:"blockTimestamp"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
L1Token TokenListItem `json:"l1Token"`
L2Token TokenListItem `json:"l2Token"`
}
// Withdrawal data structure
type Withdrawal struct {
Guid string `json:"guid"`
Amount string `json:"amount"`
BlockNumber int `json:"blockNumber"`
BlockTimestamp time.Time `json:"blockTimestamp"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
WithdrawalState string `json:"withdrawalState"`
Proof *ProofClaim `json:"proof"`
Claim *ProofClaim `json:"claim"`
L1Token TokenListItem `json:"l1Token"`
L2Token TokenListItem `json:"l2Token"`
}
// TokenListItem data structure
type TokenListItem struct {
ChainId int `json:"chainId"`
Address string `json:"address"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
LogoURI string `json:"logoURI"`
Extensions Extensions `json:"extensions"`
}
// Extensions data structure
type Extensions struct {
OptimismBridgeAddress string `json:"optimismBridgeAddress"`
BridgeType string `json:"bridgeType"`
}
// ProofClaim data structure
type ProofClaim struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp time.Time `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
// PaginationResponse for paginated responses
type PaginationResponse struct { type PaginationResponse struct {
// TODO type this better // TODO type this better
Data interface{} `json:"data"` Data interface{} `json:"data"`
...@@ -83,59 +16,59 @@ type PaginationResponse struct { ...@@ -83,59 +16,59 @@ type PaginationResponse struct {
} }
func (a *Api) DepositsHandler(w http.ResponseWriter, r *http.Request) { func (a *Api) DepositsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.bridgeView
address := common.HexToAddress(chi.URLParam(r, "address"))
limit := getIntFromQuery(r, "limit", 10) // limit := getIntFromQuery(r, "limit", 10)
cursor := r.URL.Query().Get("cursor") // cursor := r.URL.Query().Get("cursor")
sortDirection := r.URL.Query().Get("sortDirection") // sortDirection := r.URL.Query().Get("sortDirection")
deposits, err := bv.DepositsByAddress(address)
deposits, nextCursor, hasNextPage, err := a.DepositDAO.GetDeposits(limit, cursor, sortDirection)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{ response := PaginationResponse{
Data: deposits, Data: deposits,
Cursor: nextCursor, // Cursor: nextCursor,
HasNextPage: hasNextPage, HasNextPage: false,
} }
jsonResponse(w, response, http.StatusOK) jsonResponse(w, response, http.StatusOK)
} }
func (a *Api) WithdrawalsHandler(w http.ResponseWriter, r *http.Request) { func (a *Api) WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
limit := getIntFromQuery(r, "limit", 10) bv := a.bridgeView
cursor := r.URL.Query().Get("cursor")
sortDirection := r.URL.Query().Get("sortDirection") address := common.HexToAddress(chi.URLParam(r, "address"))
sortBy := r.URL.Query().Get("sortBy")
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
withdrawals, err := bv.WithdrawalsByAddress(address)
withdrawals, nextCursor, hasNextPage, err := a.WithdrawalDAO.GetWithdrawals(limit, cursor, sortDirection, sortBy)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{ response := PaginationResponse{
Data: withdrawals, Data: withdrawals,
Cursor: nextCursor, // Cursor: nextCursor,
HasNextPage: hasNextPage, HasNextPage: false,
} }
jsonResponse(w, response, http.StatusOK) jsonResponse(w, response, http.StatusOK)
} }
func getIntFromQuery(r *http.Request, key string, defaultValue int) int {
valueStr := r.URL.Query().Get(key)
if valueStr == "" {
return defaultValue
}
value, err := strconv.Atoi(valueStr)
if err != nil {
return defaultValue
}
return value
}
func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) { func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
...@@ -143,22 +76,22 @@ func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) { ...@@ -143,22 +76,22 @@ func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
} }
type Api struct { type Api struct {
Router *chi.Mux Router *chi.Mux
DepositDAO DepositDAO bridgeView database.BridgeView
WithdrawalDAO WithdrawalDAO
} }
func NewApi(depositDAO DepositDAO, withdrawalDAO WithdrawalDAO) *Api { func NewApi(bv database.BridgeView) *Api {
r := chi.NewRouter() r := chi.NewRouter()
api := &Api{ api := &Api{
Router: r, Router: r,
DepositDAO: depositDAO, bridgeView: bv,
WithdrawalDAO: withdrawalDAO,
} }
// these regex are .+ because I wasn't sure what they should be
r.Get("/api/v0/deposits", api.DepositsHandler) // don't want a regex for addresses because would prefer to validate the address
r.Get("/api/v0/withdrawals", api.WithdrawalsHandler) // with go-ethereum and throw a friendly error message
r.Get("/api/v0/deposits/{address:.+}", api.DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.WithdrawalsHandler)
return api return api
......
package api package api
import ( import (
"encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
type MockDB struct { // MockBridgeView mocks the BridgeView interface
mock.Mock type MockBridgeView struct{}
}
func (db *MockDB) GetDeposits(limit int, cursor string, sortDirection string) ([]Deposit, string, bool, error) {
args := db.Called(limit, cursor, sortDirection)
return args.Get(0).([]Deposit), args.String(1), args.Bool(2), args.Error(3)
}
func (db *MockDB) GetWithdrawals(limit int, cursor string, sortDirection string, sortBy string) ([]Withdrawal, string, bool, error) {
args := db.Called(limit, cursor, sortDirection, sortBy)
return args.Get(0).([]Withdrawal), args.String(1), args.Bool(2), args.Error(3)
}
func TestApi(t *testing.T) {
mockDB := new(MockDB)
mockDeposits := []Deposit{ // DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*database.DepositWithTransactionHash, error) {
return []*database.DepositWithTransactionHash{
{ {
Guid: "test-guid", Deposit: database.Deposit{
Amount: "1000", GUID: "mockGUID1",
BlockNumber: 123, InitiatedL1EventGUID: "mockEventGUID1",
BlockTimestamp: time.Unix(123456, 0), Tx: database.Transaction{},
From: "0x1", TokenPair: database.TokenPair{},
To: "0x2", },
TransactionHash: "0x3", L1TransactionHash: common.HexToHash("0x123"),
}, },
} }, nil
}
mockWithdrawals := []Withdrawal{ // WithdrawalsByAddress mocks returning withdrawals by an address
func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) {
return []*database.WithdrawalWithTransactionHashes{
{ {
Guid: "test-guid", Withdrawal: database.Withdrawal{
Amount: "1000", GUID: "mockGUID2",
BlockNumber: 123, InitiatedL2EventGUID: "mockEventGUID2",
BlockTimestamp: time.Unix(123456, 0), WithdrawalHash: common.HexToHash("0x456"),
From: "0x1", Tx: database.Transaction{},
To: "0x2", TokenPair: database.TokenPair{},
TransactionHash: "0x3", },
L2TransactionHash: common.HexToHash("0x789"),
}, },
} }, nil
}
mockDB.On("GetDeposits", 10, "", "").Return(mockDeposits, "nextCursor", false, nil)
mockDB.On("GetWithdrawals", 10, "", "", "").Return(mockWithdrawals, "nextCursor", false, nil)
testApi := NewApi(mockDB, mockDB)
req, _ := http.NewRequest("GET", "/api/v0/deposits", nil)
rr := httptest.NewRecorder()
testApi.Router.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, "status code should be 200")
// TODO make this type exist
var depositsResponse DepositsResponse
err := json.Unmarshal(rr.Body.Bytes(), &depositsResponse)
assert.NoError(t, err)
assert.Equal(t, mockDeposits, depositsResponse.Data) func TestDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeView{})
request, err := http.NewRequest("GET", "/api/v0/deposits/0x123", nil)
assert.Nil(t, err)
req, _ = http.NewRequest("GET", "/api/v0/withdrawals", nil) responseRecorder := httptest.NewRecorder()
rr = httptest.NewRecorder() api.Router.ServeHTTP(responseRecorder, request)
testApi.Router.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, "status code should be 200") assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
// TODO make this type exist func TestWithdrawalsHandler(t *testing.T) {
var withdrawalsResponse WithdrawalsResponse api := NewApi(&MockBridgeView{})
err = json request, err := http.NewRequest("GET", "/api/v0/withdrawals/0x123", nil)
err = json.Unmarshal(rr.Body.Bytes(), &withdrawalsResponse) assert.Nil(t, err)
assert.NoError(t, err)
// Assert response data responseRecorder := httptest.NewRecorder()
assert.Equal(t, mockWithdrawals, withdrawalsResponse.Data) api.Router.ServeHTTP(responseRecorder, request)
// Finally, assert that the methods were called with the expected parameters assert.Equal(t, http.StatusOK, responseRecorder.Code)
mockDB.AssertCalled(t, "GetDeposits", 10, "", "")
mockDB.AssertCalled(t, "GetWithdrawals", 10, "", "", "")
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment