Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
1,177 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,4 @@ _testmain.go | |
*.exe | ||
*.test | ||
*.prof | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
sudo: false | ||
language: go | ||
go: | ||
- 1.7.1 | ||
before_install: | ||
- go get github.com/mattn/goveralls | ||
script: | ||
- ./build | ||
- $HOME/gopath/bin/goveralls -service=travis-ci |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,6 @@ | ||
# prosperbot-frontend | ||
# ProsperBot Frontend | ||
|
||
[![Build Status](https://travis-ci.org/mtlynch/prosperbot-frontend.svg?branch=master)](https://travis-ci.org/mtlynch/prosperbot-frontend) | ||
[![Coverage Status](https://coveralls.io/repos/github/mtlynch/prosperbot-frontend/badge.svg?branch=master)](https://coveralls.io/github/mtlynch/prosperbot-frontend?branch=master) | ||
[![GoDoc](https://godoc.org/github.com/mtlynch/prosperbot-frontend?status.svg)](https://godoc.org/github.com/mtlynch/prosperbot-frontend) | ||
[![Go Report Card](https://goreportcard.com/badge/github.com/mtlynch/prosperbot-frontend)](https://goreportcard.com/report/github.com/mtlynch/prosperbot-frontend) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package account | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"time" | ||
|
||
"github.com/mtlynch/prosperbot/redis" | ||
) | ||
|
||
type ( | ||
redisListReader interface { | ||
LRange(key string, start int64, stop int64) ([]string, error) | ||
Quit() (string, error) | ||
} | ||
|
||
accountInfoManager struct { | ||
redis redisListReader | ||
} | ||
|
||
accountAttributeFunc func(r redis.AccountRecord) float64 | ||
|
||
AccountAttributeRecord struct { | ||
Value float64 | ||
Timestamp time.Time | ||
} | ||
) | ||
|
||
func NewAccountInfoManager() (accountInfoManager, error) { | ||
r, err := redis.New() | ||
if err != nil { | ||
return accountInfoManager{}, err | ||
} | ||
return accountInfoManager{r}, nil | ||
} | ||
|
||
var ErrNoAccountRecords = errors.New("no record of account cash balance") | ||
|
||
func accountRecordToAccountAttributeRecord(r redis.AccountRecord, f accountAttributeFunc) AccountAttributeRecord { | ||
return AccountAttributeRecord{ | ||
Value: f(r), | ||
Timestamp: r.Timestamp, | ||
} | ||
} | ||
|
||
func accountRecordsToAccountAttributeRecords(accountRecords []redis.AccountRecord, f accountAttributeFunc) []AccountAttributeRecord { | ||
cashBalanceRecords := make([]AccountAttributeRecord, len(accountRecords)) | ||
for i, r := range accountRecords { | ||
cashBalanceRecords[i] = accountRecordToAccountAttributeRecord(r, f) | ||
} | ||
return cashBalanceRecords | ||
} | ||
|
||
func collapseAccountAttributeRecords(r []AccountAttributeRecord) []AccountAttributeRecord { | ||
if len(r) <= 1 { | ||
return r | ||
} | ||
prev := r[len(r)-1] | ||
collapsed := []AccountAttributeRecord{prev} | ||
for i := len(r) - 2; i >= 0; i-- { | ||
if prev.Value != r[i].Value { | ||
collapsed = append(collapsed, r[i]) | ||
} | ||
prev = r[i] | ||
} | ||
return collapsed | ||
} | ||
|
||
func (aim accountInfoManager) history(f accountAttributeFunc) ([]AccountAttributeRecord, error) { | ||
accountRecordsSerialized, err := aim.redis.LRange(redis.KeyAccountInformation, 0, -1) | ||
if err != nil { | ||
return []AccountAttributeRecord{}, err | ||
} | ||
accountRecords := make([]redis.AccountRecord, len(accountRecordsSerialized)) | ||
for i, v := range accountRecordsSerialized { | ||
err = json.Unmarshal([]byte(v), &accountRecords[i]) | ||
if err != nil { | ||
return []AccountAttributeRecord{}, err | ||
} | ||
} | ||
records := accountRecordsToAccountAttributeRecords(accountRecords, f) | ||
return collapseAccountAttributeRecords(records), nil | ||
} | ||
|
||
func (aim accountInfoManager) Close() { | ||
aim.redis.Quit() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package account | ||
|
||
import ( | ||
"errors" | ||
"reflect" | ||
"testing" | ||
"time" | ||
) | ||
|
||
type mockRedisListReader struct { | ||
KeyGot string | ||
Err error | ||
List []string | ||
} | ||
|
||
func (lr *mockRedisListReader) LRange(key string, start int64, stop int64) ([]string, error) { | ||
lr.KeyGot = key | ||
if lr.Err != nil { | ||
return []string{}, lr.Err | ||
} | ||
if (start == 0) && (stop == -1) { | ||
return lr.List, nil | ||
} | ||
if (start > int64(len(lr.List))) || ((stop + 1) > int64(len(lr.List))) { | ||
return []string{}, nil | ||
} | ||
return lr.List[start : stop+1], nil | ||
} | ||
|
||
func (lr mockRedisListReader) Quit() (string, error) { | ||
return "", nil | ||
} | ||
|
||
const ( | ||
accountInformationSerializedA = `{"Value":{"AvailableCashBalance":100,"TotalPrincipalReceivedOnActiveNotes":0,"OutstandingPrincipalOnActiveNotes":0,"LastWithdrawAmount":0,"LastDepositAmount":0,"LastDepositDate":"0001-01-01T00:00:00Z","PendingInvestmentsPrimaryMarket":0,"PendingInvestmentsSecondaryMarket":0,"PendingQuickInvestOrders":0,"TotalAmountInvestedOnActiveNotes":0,"TotalAccountValue":0,"InflightGross":0,"LastWithdrawDate":"0001-01-01T00:00:00Z"},"Timestamp":"2016-01-28T15:35:04.000000022Z"}` | ||
accountInformationSerializedB = `{"Value":{"AvailableCashBalance":125.5,"TotalPrincipalReceivedOnActiveNotes":0,"OutstandingPrincipalOnActiveNotes":0,"LastWithdrawAmount":0,"LastDepositAmount":0,"LastDepositDate":"0001-01-01T00:00:00Z","PendingInvestmentsPrimaryMarket":0,"PendingInvestmentsSecondaryMarket":0,"PendingQuickInvestOrders":0,"TotalAmountInvestedOnActiveNotes":0,"TotalAccountValue":0,"InflightGross":0,"LastWithdrawDate":"0001-01-01T00:00:00Z"},"Timestamp":"2016-02-14T12:28:15.000000022Z"}` | ||
accountInformationSerializedC = `{"Value":{"AvailableCashBalance":125.5,"TotalPrincipalReceivedOnActiveNotes":0,"OutstandingPrincipalOnActiveNotes":0,"LastWithdrawAmount":0,"LastDepositAmount":0,"LastDepositDate":"0001-01-01T00:00:00Z","PendingInvestmentsPrimaryMarket":0,"PendingInvestmentsSecondaryMarket":0,"PendingQuickInvestOrders":0,"TotalAmountInvestedOnActiveNotes":0,"TotalAccountValue":0,"InflightGross":0,"LastWithdrawDate":"0001-01-01T00:00:00Z"},"Timestamp":"2016-02-14T12:29:15.000000022Z"}` | ||
accountInformationSerializedD = `{"Value":{"AvailableCashBalance":95.25,"TotalPrincipalReceivedOnActiveNotes":0,"OutstandingPrincipalOnActiveNotes":0,"LastWithdrawAmount":0,"LastDepositAmount":0,"LastDepositDate":"0001-01-01T00:00:00Z","PendingInvestmentsPrimaryMarket":0,"PendingInvestmentsSecondaryMarket":0,"PendingQuickInvestOrders":0,"TotalAmountInvestedOnActiveNotes":0,"TotalAccountValue":0,"InflightGross":0,"LastWithdrawDate":"0001-01-01T00:00:00Z"},"Timestamp":"2016-02-14T12:30:15.000000022Z"}` | ||
badJSON = "{{mock bad JSON" | ||
) | ||
|
||
func TestCashBalanceHistory(t *testing.T) { | ||
var tests = []struct { | ||
accountList []string | ||
lrangeErr error | ||
wantRecords []AccountAttributeRecord | ||
wantSuccess bool | ||
msg string | ||
}{ | ||
{ | ||
accountList: []string{ | ||
accountInformationSerializedB, | ||
accountInformationSerializedA, | ||
}, | ||
lrangeErr: errors.New("mock LRange error"), | ||
wantSuccess: false, | ||
msg: "should return error when redis call fails", | ||
}, | ||
{ | ||
accountList: []string{}, | ||
wantRecords: []AccountAttributeRecord{}, | ||
wantSuccess: true, | ||
msg: "should return empty list when there is no account history", | ||
}, | ||
{ | ||
accountList: []string{ | ||
accountInformationSerializedB, | ||
}, | ||
wantRecords: []AccountAttributeRecord{ | ||
{ | ||
Value: 125.50, | ||
Timestamp: time.Date(2016, 2, 14, 12, 28, 15, 22, time.UTC), | ||
}, | ||
}, | ||
wantSuccess: true, | ||
msg: "should return valid record when single valid record exists", | ||
}, | ||
{ | ||
accountList: []string{ | ||
accountInformationSerializedD, | ||
accountInformationSerializedC, | ||
accountInformationSerializedB, | ||
accountInformationSerializedA, | ||
}, | ||
wantRecords: []AccountAttributeRecord{ | ||
{ | ||
Value: 100.0, | ||
Timestamp: time.Date(2016, 1, 28, 15, 35, 4, 22, time.UTC), | ||
}, | ||
{ | ||
Value: 125.50, | ||
Timestamp: time.Date(2016, 2, 14, 12, 28, 15, 22, time.UTC), | ||
}, | ||
{ | ||
Value: 95.25, | ||
Timestamp: time.Date(2016, 2, 14, 12, 30, 15, 22, time.UTC), | ||
}, | ||
}, | ||
wantSuccess: true, | ||
msg: "should return valid record when valid records exist", | ||
}, | ||
{ | ||
accountList: []string{badJSON}, | ||
wantSuccess: false, | ||
msg: "malformed JSON in redis should cause error", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
aim := accountInfoManager{ | ||
redis: &mockRedisListReader{ | ||
List: tt.accountList, | ||
}, | ||
} | ||
gotRecords, gotErr := aim.CashBalanceHistory() | ||
if gotErr != nil && tt.wantSuccess { | ||
t.Errorf("%s: unexpected error from CashBalanceHistory, got: %v, want: nil", tt.msg, gotErr) | ||
} | ||
if !tt.wantSuccess { | ||
continue | ||
} | ||
if !reflect.DeepEqual(gotRecords, tt.wantRecords) { | ||
t.Errorf("%s: unexpected records from CashBalanceHistory, got: %v, want: %v", tt.msg, gotRecords, tt.wantRecords) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package account | ||
|
||
import ( | ||
"github.com/mtlynch/prosperbot/redis" | ||
) | ||
|
||
func cashBalanceAttribute(r redis.AccountRecord) float64 { | ||
return r.Value.AvailableCashBalance | ||
} | ||
|
||
func (aim accountInfoManager) CashBalanceHistory() ([]AccountAttributeRecord, error) { | ||
return aim.history(cashBalanceAttribute) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package account | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
type accountInfoFunc func(m accountInfoManager) (interface{}, error) | ||
|
||
func accountAttributeHandler(f accountInfoFunc) http.Handler { | ||
fn := func(w http.ResponseWriter, r *http.Request) { | ||
cbm, err := NewAccountInfoManager() | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get account information: %v", err))) | ||
return | ||
} | ||
defer cbm.Close() | ||
b, err := f(cbm) | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get account information: %v", err))) | ||
return | ||
} | ||
response, err := json.Marshal(b) | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get account information: %v", err))) | ||
return | ||
} | ||
w.Write(response) | ||
} | ||
return http.HandlerFunc(fn) | ||
} | ||
|
||
func CashBalanceHistoryHandler() http.Handler { | ||
return accountAttributeHandler( | ||
func(m accountInfoManager) (interface{}, error) { | ||
return m.CashBalanceHistory() | ||
}) | ||
} | ||
|
||
func AccountValueHistoryHandler() http.Handler { | ||
return accountAttributeHandler( | ||
func(m accountInfoManager) (interface{}, error) { | ||
return m.AccountValueHistory() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package account | ||
|
||
import ( | ||
"github.com/mtlynch/prosperbot/redis" | ||
) | ||
|
||
func accountValueAttribute(r redis.AccountRecord) float64 { | ||
return r.Value.TotalAccountValue | ||
} | ||
|
||
func (aim accountInfoManager) AccountValueHistory() ([]AccountAttributeRecord, error) { | ||
return aim.history(accountValueAttribute) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/bin/bash | ||
|
||
# Exit on first error. | ||
set -e | ||
|
||
go test ./... | ||
|
||
# Make sure there is no output from gofmt. | ||
gofmt -s -d . 2>&1 | read && (echo 'error: not formatted' || exit 1) | ||
|
||
go vet ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/bin/sh | ||
rm -rf .git/hooks/ && ln -s -f ../hooks .git/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/bin/sh | ||
./build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package main | ||
|
||
import ( | ||
"log" | ||
"net/http" | ||
|
||
"github.com/mtlynch/prosperbot-frontend/account" | ||
"github.com/mtlynch/prosperbot-frontend/notes" | ||
) | ||
|
||
func serveSingle(pattern string, filename string) { | ||
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { | ||
log.Printf("Requested static file: %v\n", r.URL.Path) | ||
http.ServeFile(w, r, filename) | ||
}) | ||
} | ||
|
||
func main() { | ||
log.Println("starting up dashboard") | ||
|
||
http.Handle("/cashBalanceHistory", account.CashBalanceHistoryHandler()) | ||
http.Handle("/accountValueHistory", account.AccountValueHistoryHandler()) | ||
http.Handle("/notes.json", notes.NotesHandler()) | ||
http.Handle("/static/", | ||
http.StripPrefix("/static/", | ||
http.FileServer(http.Dir("./static/")))) | ||
serveSingle("/", "./static/dashboard.html") | ||
serveSingle("/notes", "./static/notes.html") | ||
|
||
log.Fatal(http.ListenAndServe(":8082", nil)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package notes | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
func NotesHandler() http.Handler { | ||
fn := func(w http.ResponseWriter, r *http.Request) { | ||
reader, err := NewNoteReader() | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get note information: %v", err))) | ||
return | ||
} | ||
defer reader.Close() | ||
n, err := reader.notes() | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get note information: %v", err))) | ||
return | ||
} | ||
response, err := json.Marshal(n) | ||
if err != nil { | ||
w.Write([]byte(fmt.Sprintf("failed to get note information: %v", err))) | ||
return | ||
} | ||
w.Write(response) | ||
} | ||
return http.HandlerFunc(fn) | ||
} |
Oops, something went wrong.