From eee38ef83064746475f86d7a147d46d3feb7ce2d Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:15:39 +1100 Subject: [PATCH 01/24] Added template contact page. --- web/dashboard-contact.html | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 web/dashboard-contact.html diff --git a/web/dashboard-contact.html b/web/dashboard-contact.html new file mode 100644 index 00000000..c66f754a --- /dev/null +++ b/web/dashboard-contact.html @@ -0,0 +1,49 @@ +{{template "header" .}} + + +
+
+ +
+

Contact Us

+ +

Contact us @ https://github.com/thrasher-/gocryptotrader

+
+

+ +

+
+
+
+
+{{template "footer" .}} From 8a4ec72460e2a37c255b157a9b9990e9ae75bcbe Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:17:07 +1100 Subject: [PATCH 02/24] Added marketdepth template. --- web/dashboard-marketdepth.html | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 web/dashboard-marketdepth.html diff --git a/web/dashboard-marketdepth.html b/web/dashboard-marketdepth.html new file mode 100644 index 00000000..9171689b --- /dev/null +++ b/web/dashboard-marketdepth.html @@ -0,0 +1,89 @@ +{{template "header" .}} + + +
+
+ +
+

Current Market Depth & Analysis

+ +
+
+ Generic placeholder thumbnail +

Asset Allocation Pie Graph

+ Currencies owned and tracked. +
+
+ Generic placeholder thumbnail +

Profit/Loss/Initial Pie Graph

+ Current portfolio health. +
+
+ Generic placeholder thumbnail +

Exchanges Pie Graph

+ Based on current daily volume and analysis. +
+
+ Generic placeholder thumbnail +

Market Capitalisation Pie Graph

+ In USD terms per deemed weighted currencies. +
+
+ +

Tickers table

+
+ + + + + + + + + + + + + + + + + + + +
ExchangeLAST - BTC/USDLAST - LTC/USDLAST - BTC/LTC
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
+
+
+
+
+{{template "footer" .}} From d15280ee252347ede1c9cf53dbd21117f2d12ad5 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:17:57 +1100 Subject: [PATCH 03/24] Added ordermanagment template. --- web/dashboard-ordermanagement.html | 70 ++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 web/dashboard-ordermanagement.html diff --git a/web/dashboard-ordermanagement.html b/web/dashboard-ordermanagement.html new file mode 100644 index 00000000..66113ce1 --- /dev/null +++ b/web/dashboard-ordermanagement.html @@ -0,0 +1,70 @@ +{{template "header" .}} + + +
+
+ +
+

Order Management

+ +

+

Order Management Tools Go Here.

+

+ +

Prior Trades

+
+ + + + + + + + + + + + + + + + + + + +
DATE/TIMEEXCHANGECURRENCIES USEDPROFIT LOSS
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
+
+
+
+
+{{template "footer" .}} From 78ff31dd3fedc80c8242817fb179460e7c3baf26 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:18:36 +1100 Subject: [PATCH 04/24] Added reports template. --- web/dashboard-reports.html | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 web/dashboard-reports.html diff --git a/web/dashboard-reports.html b/web/dashboard-reports.html new file mode 100644 index 00000000..ff1fe934 --- /dev/null +++ b/web/dashboard-reports.html @@ -0,0 +1,89 @@ +{{template "header" .}} + + +
+
+ +
+

Reports

+ +
+
+ Generic placeholder thumbnail +

Asset Allocation Pie Graph

+ Currencies owned and tracked. +
+
+ Generic placeholder thumbnail +

Profit/Loss/Initial Pie Graph

+ Current portfolio health. +
+
+ Generic placeholder thumbnail +

Exchanges Pie Graph

+ Based on current daily volume and analysis. +
+
+ Generic placeholder thumbnail +

Market Capitalisation Pie Graph

+ In USD terms per deemed weighted currencies. +
+
+ +

Running Report. Subdivided in monthly/yearly/quarter

+
+ + + + + + + + + + + + + + + + + + + +
Trade Execution dateTrade ExchangeTrade TypeInitial InvestmentPROFIT/LOSS
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
+
+
+
+
+{{template "footer" .}} From dd9f06f7fefe21fcf8320df68bf7686cadc4b189 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:19:03 +1100 Subject: [PATCH 05/24] Added settings template. --- web/dashboard-settings.html | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 web/dashboard-settings.html diff --git a/web/dashboard-settings.html b/web/dashboard-settings.html new file mode 100644 index 00000000..e0d8b562 --- /dev/null +++ b/web/dashboard-settings.html @@ -0,0 +1,44 @@ +{{template "header" .}} + + +
+
+ +
+

Settings

+ +

Settings Table

+
+
+
+{{template "footer" .}} From 8090d95a1c761e3520d72a453b84d340e9162ed6 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:19:34 +1100 Subject: [PATCH 06/24] Added error template. --- web/error.html | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 web/error.html diff --git a/web/error.html b/web/error.html new file mode 100644 index 00000000..3a89b218 --- /dev/null +++ b/web/error.html @@ -0,0 +1,37 @@ +{{template "header" .}} + +
+ +
+ +
+ +
+
+ +
+
+ +
+

ERROR

+

The error {{.Error}} has occured.

+

+ +

+
+ +
+
+
+
+ +
+ +
+ +
+ +{{template "footer" .}} From b40b90c66f306e663fa8c85c6a6a3c322cc86294 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:21:47 +1100 Subject: [PATCH 07/24] Added mushroom favicon. --- web/favicon.ico | Bin 0 -> 139245 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 web/favicon.ico diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2216206f901c754e58980769a0fd861bf5e171f4 GIT binary patch literal 139245 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 z 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 T+c54SrXJoChVySe!)O05X}ipK literal 0 HcmV?d00001 From 931b0b9d5802a091b982c0d89056648140f168f2 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:24:34 +1100 Subject: [PATCH 08/24] Added stylesheet insertion & favicon link. --- web/header.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/header.html b/web/header.html index 74ad3d64..18cf5c47 100644 --- a/web/header.html +++ b/web/header.html @@ -3,13 +3,17 @@ - {{.title}} - + + + + {{.StaticStylesheet}} + + {{end}} From bd122142918a17b53764fe7d7d11d8d9b218d70d Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:31:50 +1100 Subject: [PATCH 09/24] Added coverpage template. --- web/index.html | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/web/index.html b/web/index.html index f81d204a..f58206dc 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,37 @@ {{template "header" .}} -

Index

+
-{{template "footer" .}} \ No newline at end of file +
+ +
+ +
+
+ +
+
+ +
+

Template Trading Platform v0.0

+

Login Test

+

+ Login Test Page +

+
+ +
+
+
+
+ +
+ +
+ +
+ +{{template "footer" .}} From 707ccd909ca6473da6b140ed87ff7fdd5935dc62 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:32:22 +1100 Subject: [PATCH 10/24] Added login page template. --- web/login.html | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 web/login.html diff --git a/web/login.html b/web/login.html new file mode 100644 index 00000000..699df545 --- /dev/null +++ b/web/login.html @@ -0,0 +1,6 @@ +{{template "header" .}} +

+ This is the login page.... +

+goto dashboard +{{template "footer" .}} From c4dcf47b47472666d35569b70f8539a35231f99d Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:32:59 +1100 Subject: [PATCH 11/24] Added stylesheet for templates. --- web/static/css/cover.css | 163 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 web/static/css/cover.css diff --git a/web/static/css/cover.css b/web/static/css/cover.css new file mode 100644 index 00000000..f4d789e9 --- /dev/null +++ b/web/static/css/cover.css @@ -0,0 +1,163 @@ +/* + * Globals + */ + +/* Links */ +a, +a:focus, +a:hover { + color: #fff; +} + +/* Custom default button */ +.btn-default, +.btn-default:hover, +.btn-default:focus { + color: #333; + text-shadow: none; /* Prevent inheritence from `body` */ + background-color: #fff; + border: 1px solid #fff; +} + + +/* + * Base structure + */ + +html, +body { + height: 100%; + background-color: #333; +} +body { + color: #fff; + text-align: center; + text-shadow: 0 1px 3px rgba(0,0,0,.5); +} + +/* Extra markup and styles for table-esque vertical and horizontal centering */ +.site-wrapper { + display: table; + width: 100%; + height: 100%; /* For at least Firefox */ + min-height: 100%; + -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5); + box-shadow: inset 0 0 100px rgba(0,0,0,.5); +} +.site-wrapper-inner { + display: table-cell; + vertical-align: top; +} +.cover-container { + margin-right: auto; + margin-left: auto; +} + +/* Padding for spacing */ +.inner { + padding: 30px; +} + + +/* + * Header + */ +.masthead-brand { + margin-top: 10px; + margin-bottom: 10px; +} + +.masthead-nav > li { + display: inline-block; +} +.masthead-nav > li + li { + margin-left: 20px; +} +.masthead-nav > li > a { + padding-right: 0; + padding-left: 0; + font-size: 16px; + font-weight: bold; + color: #fff; /* IE8 proofing */ + color: rgba(255,255,255,.75); + border-bottom: 2px solid transparent; +} +.masthead-nav > li > a:hover, +.masthead-nav > li > a:focus { + background-color: transparent; + border-bottom-color: #a9a9a9; + border-bottom-color: rgba(255,255,255,.25); +} +.masthead-nav > .active > a, +.masthead-nav > .active > a:hover, +.masthead-nav > .active > a:focus { + color: #fff; + border-bottom-color: #fff; +} + +@media (min-width: 768px) { + .masthead-brand { + float: left; + } + .masthead-nav { + float: right; + } +} + + +/* + * Cover + */ + +.cover { + padding: 0 20px; +} +.cover .btn-lg { + padding: 10px 20px; + font-weight: bold; +} + + +/* + * Footer + */ + +.mastfoot { + color: #999; /* IE8 proofing */ + color: rgba(255,255,255,.5); +} + + +/* + * Affix and center + */ + +@media (min-width: 768px) { + /* Pull out the header and footer */ + .masthead { + position: fixed; + top: 0; + } + .mastfoot { + position: fixed; + bottom: 0; + } + /* Start the vertical centering */ + .site-wrapper-inner { + vertical-align: middle; + } + /* Handle the widths */ + .masthead, + .mastfoot, + .cover-container { + width: 100%; /* Must be percentage or pixels for horizontal alignment */ + } +} + +@media (min-width: 992px) { + .masthead, + .mastfoot, + .cover-container { + width: 700px; + } +} \ No newline at end of file From 490a4e51c512a3d5d102e88e6e1b49826f75fe57 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:33:12 +1100 Subject: [PATCH 12/24] Added stylesheet for templates. --- web/static/css/dashboard.css | 105 +++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 web/static/css/dashboard.css diff --git a/web/static/css/dashboard.css b/web/static/css/dashboard.css new file mode 100644 index 00000000..de315748 --- /dev/null +++ b/web/static/css/dashboard.css @@ -0,0 +1,105 @@ +/* + * Base structure + */ + +/* Move down content because we have a fixed navbar that is 50px tall */ +body { + padding-top: 50px; +} + + +/* + * Global add-ons + */ + +.sub-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + +/* + * Top navigation + * Hide default border to remove 1px line. + */ +.navbar-fixed-top { + border: 0; +} + +/* + * Sidebar + */ + +/* Hide for mobile, show later */ +.sidebar { + display: none; +} +@media (min-width: 768px) { + .sidebar { + position: fixed; + top: 51px; + bottom: 0; + left: 0; + z-index: 1000; + display: block; + padding: 20px; + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ + background-color: #f5f5f5; + border-right: 1px solid #eee; + } +} + +/* Sidebar navigation */ +.nav-sidebar { + margin-right: -21px; /* 20px padding + 1px border */ + margin-bottom: 20px; + margin-left: -20px; +} +.nav-sidebar > li > a { + padding-right: 20px; + padding-left: 20px; +} +.nav-sidebar > .active > a, +.nav-sidebar > .active > a:hover, +.nav-sidebar > .active > a:focus { + color: #fff; + background-color: #428bca; +} + + +/* + * Main content + */ + +.main { + padding: 20px; +} +@media (min-width: 768px) { + .main { + padding-right: 40px; + padding-left: 40px; + } +} +.main .page-header { + margin-top: 0; +} + + +/* + * Placeholder dashboard ideas + */ + +.placeholders { + margin-bottom: 30px; + text-align: center; +} +.placeholders h4 { + margin-bottom: 0; +} +.placeholder { + margin-bottom: 20px; +} +.placeholder img { + display: inline-block; + border-radius: 50%; +} \ No newline at end of file From 3637ed0a1ff718f84cfd2f15af9c87b85f603367 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 29 Mar 2016 17:34:05 +1100 Subject: [PATCH 13/24] Added handler for filesystem and logic for rastering pages. --- webserver.go | 83 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/webserver.go b/webserver.go index df7daf66..fe54fdcb 100644 --- a/webserver.go +++ b/webserver.go @@ -2,11 +2,24 @@ package main import ( "html/template" + "io/ioutil" "log" "net/http" "strconv" ) +type Page struct { + Title string + StaticStylesheet template.HTML + Body []byte + Error string +} + +const ( + coverCSS = `` + dashboardCSS = `` +) + func GetWebserverHost() string { host := SplitStrings(bot.config.Webserver.ListenAddress, ":")[0] if host == "" { @@ -22,6 +35,8 @@ func GetWebserverPort() int { } func StartWebserver() error { + fs := http.FileServer(http.Dir("web")) + http.Handle("/web/", http.StripPrefix("/web/", fs)) http.HandleFunc("/", index) var err error go func() { @@ -39,16 +54,62 @@ func ServerHTTPError(w http.ResponseWriter, err error) { func index(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - tmpl, err := template.ParseFiles("web/index.html", "web/header.html", "web/footer.html") - if err != nil { - ServerHTTPError(w, err) - return - } - - tmplValues := map[string]interface{}{"title": "Home"} - tmpl.Execute(w, tmplValues) - if err != nil { - ServerHTTPError(w, err) - return + switch r.URL.Path { + case "/": + renderTemplate(w, "index.html", readPage("/index")) + case "/login": + renderTemplate(w, "login.html", readPage(r.URL.Path)) + case "/logout": + renderTemplate(w, "index.html", readPage("/index")) + case "/dashboard-marketdepth": + renderTemplate(w, "dashboard-marketdepth.html", readPage(r.URL.Path)) + case "/dashboard-ordermanagement": + renderTemplate(w, "dashboard-ordermanagement.html", readPage(r.URL.Path)) + case "/dashboard-contact": + renderTemplate(w, "dashboard-contact.html", readPage(r.URL.Path)) + case "/dashboard-settings": + renderTemplate(w, "dashboard-settings.html", readPage(r.URL.Path)) + case "/dashboard-reports": + renderTemplate(w, "dashboard-reports.html", readPage(r.URL.Path)) + default: + w.WriteHeader(http.StatusNotFound) + renderTemplate(w, "error.html", readPage("/error")) + } +} + +func readPage(client string) *Page { + filename := "web/" + client[1:] + ".html" + body, err := ioutil.ReadFile(filename) + if err != nil { + log.Println("Webserver: Failed to open file -- ", err, "client string is: ", client) + return nil + } + stylesheet := setStylesheet(client) + return &Page{Title: client[1:], StaticStylesheet: stylesheet, Body: body} +} + +func setStylesheet(client string) template.HTML { + if len(client) >= 10 { + if client[:10] == "/dashboard" { + return template.HTML(dashboardCSS) + } + } + return template.HTML(coverCSS) +} + +func renderTemplate(w http.ResponseWriter, pageName string, p *Page) { + tmpl, err := template.ParseFiles("web/index.html", "web/header.html", + "web/footer.html", "web/dashboard-marketdepth.html", "web/login.html", + "web/dashboard-ordermanagement.html", "web/dashboard-reports.html", + "web/dashboard-settings.html", "web/dashboard-contact.html", "web/error.html") + + if err != nil { + log.Println("Webserver: Could not parsefile -- ", err) + ServerHTTPError(w, err) + return + } + err = tmpl.ExecuteTemplate(w, pageName, p) + if err != nil { + log.Println("Webserver: Could not execute template -- ", err) } } From 4b2a336e1bc2138d53d4f5447c4573ceeedf5f5d Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 23 May 2016 22:18:42 +1000 Subject: [PATCH 14/24] Initial commit trying to turn bot to have RESTful JSON endpoint --- anxroutes.go | 27 +++++++++++ main.go | 39 ++++++++------- restfulHandlers.go | 34 ++++++++++++++ restfulLogger.go | 23 +++++++++ restfulRouter.go | 25 ++++++++++ restfulRoutes.go | 33 +++++++++++++ restfulTodo.go | 11 +++++ webserver.go | 115 --------------------------------------------- 8 files changed, 172 insertions(+), 135 deletions(-) create mode 100644 anxroutes.go create mode 100644 restfulHandlers.go create mode 100644 restfulLogger.go create mode 100644 restfulRouter.go create mode 100644 restfulRoutes.go create mode 100644 restfulTodo.go delete mode 100644 webserver.go diff --git a/anxroutes.go b/anxroutes.go new file mode 100644 index 00000000..18e089c7 --- /dev/null +++ b/anxroutes.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "net/http" + "github.com/gorilla/mux" +) + +func getLatestAnxTicker(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + response := bot.exchange.anx.GetTicker(currency) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } +} + +var anxRoutes = Routes{ + Route{ + "Index", + "GET", + "/exchanges/anx/latest/{currency}", + getLatestAnxTicker, + }, +} \ No newline at end of file diff --git a/main.go b/main.go index 9ce75fc5..848a3138 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "runtime" "strconv" "syscall" + "net/http" ) type Exchange struct { @@ -80,29 +81,11 @@ func main() { } else { log.Printf("SMS support enabled. Number of SMS contacts %d.\n", GetEnabledSMSContacts()) } - } - if !bot.config.SMS.Enabled { + } else { log.Println("SMS support disabled.") } - if bot.config.Webserver.Enabled { - err := CheckWebserverValues() - if err != nil { - log.Println(err) // non fatal event - bot.config.Webserver.Enabled = false - } else { - log.Println("HTTP Webserver support enabled.") - err = StartWebserver() - if err != nil { - log.Println("Unable to start Webserver: ", err) - } else { - log.Printf("HTTP server enabled and running at http://%s:%d\n", GetWebserverHost(), GetWebserverPort()) - } - } - } - if !bot.config.Webserver.Enabled { - log.Println("HTTP Webserver support disabled.") - } + log.Printf("Available Exchanges: %d. Enabled Exchanges: %d.\n", len(bot.config.Exchanges), GetEnabledExchanges()) log.Println("Bot Exchange support:") @@ -150,6 +133,22 @@ func main() { } } } + + if bot.config.Webserver.Enabled { + err := CheckWebserverValues() + if err != nil { + log.Println(err) // non fatal event + bot.config.Webserver.Enabled = false + } else { + log.Println("HTTP Webserver support enabled.") + router := NewRouter(bot.exchanges) + log.Fatal(http.ListenAndServe(":8080", router)) + } + } + if !bot.config.Webserver.Enabled { + log.Println("HTTP Webserver support disabled.") + } + <-bot.shutdown Shutdown() } diff --git a/restfulHandlers.go b/restfulHandlers.go new file mode 100644 index 00000000..83cc1b89 --- /dev/null +++ b/restfulHandlers.go @@ -0,0 +1,34 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "github.com/gorilla/mux" +) + +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w,bot.exchanges[0].GetName()) + + +} + +func TodoIndex(w http.ResponseWriter, r *http.Request) { + + todos := Todos{ + Todo{Name: "Write presentation"}, + Todo{Name: "Host meetup"}, + } + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(todos); err != nil { + panic(err) + } +} + +func TodoShow(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + todoId := vars["todoId"] + fmt.Fprintln(w, "Todo show:", todoId) +} \ No newline at end of file diff --git a/restfulLogger.go b/restfulLogger.go new file mode 100644 index 00000000..a1f21d1b --- /dev/null +++ b/restfulLogger.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "net/http" + "time" +) + +func Logger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} \ No newline at end of file diff --git a/restfulRouter.go b/restfulRouter.go new file mode 100644 index 00000000..ea4edae0 --- /dev/null +++ b/restfulRouter.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +func NewRouter(exchanges []IBotExchange) *mux.Router { + router := mux.NewRouter().StrictSlash(true) + allRoutes := append(routes,anxRoutes...) + for _, route := range allRoutes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) + + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) + + } + return router +} \ No newline at end of file diff --git a/restfulRoutes.go b/restfulRoutes.go new file mode 100644 index 00000000..bdf42b62 --- /dev/null +++ b/restfulRoutes.go @@ -0,0 +1,33 @@ +package main + +import "net/http" + +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +type Routes []Route + +var routes = Routes{ + Route{ + "Index", + "GET", + "/", + Index, + }, + Route{ + "TodoIndex", + "GET", + "/todos", + TodoIndex, + }, + Route{ + "TodoShow", + "GET", + "/todos/{todoId}", + TodoShow, + }, +} \ No newline at end of file diff --git a/restfulTodo.go b/restfulTodo.go new file mode 100644 index 00000000..36e5fa32 --- /dev/null +++ b/restfulTodo.go @@ -0,0 +1,11 @@ +package main + +import "time" + +type Todo struct { + Name string `json:"name"` + Completed bool `json:"completed"` + Due time.Time `json:"due"` +} + +type Todos []Todo diff --git a/webserver.go b/webserver.go deleted file mode 100644 index fe54fdcb..00000000 --- a/webserver.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "html/template" - "io/ioutil" - "log" - "net/http" - "strconv" -) - -type Page struct { - Title string - StaticStylesheet template.HTML - Body []byte - Error string -} - -const ( - coverCSS = `` - dashboardCSS = `` -) - -func GetWebserverHost() string { - host := SplitStrings(bot.config.Webserver.ListenAddress, ":")[0] - if host == "" { - return "localhost" - } - return host -} - -func GetWebserverPort() int { - portStr := SplitStrings(bot.config.Webserver.ListenAddress, ":")[1] - port, _ := strconv.Atoi(portStr) - return port -} - -func StartWebserver() error { - fs := http.FileServer(http.Dir("web")) - http.Handle("/web/", http.StripPrefix("/web/", fs)) - http.HandleFunc("/", index) - var err error - go func() { - err = http.ListenAndServe(bot.config.Webserver.ListenAddress, nil) - }() - return err -} - -func ServerHTTPError(w http.ResponseWriter, err error) { - log.Println(err) - w.WriteHeader(http.StatusInternalServerError) - w.Header().Set("Content-Type", "text/plain; charset=utf-8") -} - -func index(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - - switch r.URL.Path { - case "/": - renderTemplate(w, "index.html", readPage("/index")) - case "/login": - renderTemplate(w, "login.html", readPage(r.URL.Path)) - case "/logout": - renderTemplate(w, "index.html", readPage("/index")) - case "/dashboard-marketdepth": - renderTemplate(w, "dashboard-marketdepth.html", readPage(r.URL.Path)) - case "/dashboard-ordermanagement": - renderTemplate(w, "dashboard-ordermanagement.html", readPage(r.URL.Path)) - case "/dashboard-contact": - renderTemplate(w, "dashboard-contact.html", readPage(r.URL.Path)) - case "/dashboard-settings": - renderTemplate(w, "dashboard-settings.html", readPage(r.URL.Path)) - case "/dashboard-reports": - renderTemplate(w, "dashboard-reports.html", readPage(r.URL.Path)) - default: - w.WriteHeader(http.StatusNotFound) - renderTemplate(w, "error.html", readPage("/error")) - } -} - -func readPage(client string) *Page { - filename := "web/" + client[1:] + ".html" - body, err := ioutil.ReadFile(filename) - if err != nil { - log.Println("Webserver: Failed to open file -- ", err, "client string is: ", client) - return nil - } - stylesheet := setStylesheet(client) - return &Page{Title: client[1:], StaticStylesheet: stylesheet, Body: body} -} - -func setStylesheet(client string) template.HTML { - if len(client) >= 10 { - if client[:10] == "/dashboard" { - return template.HTML(dashboardCSS) - } - } - return template.HTML(coverCSS) -} - -func renderTemplate(w http.ResponseWriter, pageName string, p *Page) { - tmpl, err := template.ParseFiles("web/index.html", "web/header.html", - "web/footer.html", "web/dashboard-marketdepth.html", "web/login.html", - "web/dashboard-ordermanagement.html", "web/dashboard-reports.html", - "web/dashboard-settings.html", "web/dashboard-contact.html", "web/error.html") - - if err != nil { - log.Println("Webserver: Could not parsefile -- ", err) - ServerHTTPError(w, err) - return - } - err = tmpl.ExecuteTemplate(w, pageName, p) - if err != nil { - log.Println("Webserver: Could not execute template -- ", err) - } -} From 95cfb356ba93639820094cee92377d2a3f4d53ec Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 5 Jun 2016 19:10:32 +1000 Subject: [PATCH 15/24] Adds GetTickerPrice(currency string) TickerPrice func to interface. Implements on all exchanges (where possible) Attempts to make routing generic with /exchanges/{exchangeName}/latest/{currency} path and response --- alphapointhttp.go | 13 +++++++++++++ anxhttp.go | 9 +++++++++ anxroutes.go | 27 --------------------------- bitfinexhttp.go | 14 ++++++++++++++ bitstamphttp.go | 13 +++++++++++++ brightonpeakhttp.go | 13 +++++++++++++ btcchttp.go | 9 +++++++++ btcehttp.go | 15 +++++++++++++++ btcmarkets.go | 13 +++++++++++++ coinbasehttp.go | 12 ++++++++++++ geminihttp.go | 7 +++++++ huobihttp.go | 9 +++++++++ interfaces.go | 2 ++ itbithttp.go | 10 ++++++++++ kraken.go | 15 +++++++++++++++ lakebtchttp.go | 15 +++++++++++++++ localbitcoinshttp.go | 12 ++++++++++++ okcoinhttp.go | 13 +++++++++++++ poloniexhttp.go | 13 +++++++++++++ restfulRouter.go | 2 +- tickerRoutes.go | 36 ++++++++++++++++++++++++++++++++++++ 21 files changed, 244 insertions(+), 28 deletions(-) delete mode 100644 anxroutes.go create mode 100644 tickerRoutes.go diff --git a/alphapointhttp.go b/alphapointhttp.go index fef2333f..a20334d1 100644 --- a/alphapointhttp.go +++ b/alphapointhttp.go @@ -203,6 +203,19 @@ func (a *Alphapoint) GetTicker(symbol string) (AlphapointTicker, error) { return response, nil } +func (a *Alphapoint) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := a.GetTicker(currency) + if err != nil { + log.Println(err) + return TickerPrice{} + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + + return tickerPrice +} + func (a *Alphapoint) GetTrades(symbol string, startIndex, count int) (AlphapointTrades, error) { request := make(map[string]interface{}) request["ins"] = symbol diff --git a/anxhttp.go b/anxhttp.go index 8b130d16..9e90cac7 100644 --- a/anxhttp.go +++ b/anxhttp.go @@ -188,6 +188,15 @@ func (a *ANX) GetTicker(currency string) ANXTicker { return ticker } +func (a *ANX) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker := a.GetTicker(currency) + tickerPrice.Ask = ticker.Data.Buy.Value + tickerPrice.Bid = ticker.Data.Sell.Value + + return tickerPrice +} + func (a *ANX) GetAPIKey(username, password, otp, deviceID string) (string, string) { request := make(map[string]interface{}) request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] diff --git a/anxroutes.go b/anxroutes.go deleted file mode 100644 index 18e089c7..00000000 --- a/anxroutes.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "encoding/json" - "net/http" - "github.com/gorilla/mux" -) - -func getLatestAnxTicker(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - currency := vars["currency"] - response := bot.exchange.anx.GetTicker(currency) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -var anxRoutes = Routes{ - Route{ - "Index", - "GET", - "/exchanges/anx/latest/{currency}", - getLatestAnxTicker, - }, -} \ No newline at end of file diff --git a/bitfinexhttp.go b/bitfinexhttp.go index 3871f5c7..ef4b1ef1 100644 --- a/bitfinexhttp.go +++ b/bitfinexhttp.go @@ -284,6 +284,20 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (BitfinexTicker, return response, nil } + +func (b *Bitfinex) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := b.GetTicker(currency, nil) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + + return tickerPrice +} + type BitfinexLendbookBidAsk struct { Rate float64 `json:"rate,string"` Amount float64 `json:"amount,string"` diff --git a/bitstamphttp.go b/bitstamphttp.go index c30dc513..7648a9fc 100644 --- a/bitstamphttp.go +++ b/bitstamphttp.go @@ -234,6 +234,19 @@ func (b *Bitstamp) GetTicker(hourly bool) (BitstampTicker, error) { return ticker, nil } +func (b *Bitstamp) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := b.GetTicker(true) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + + return tickerPrice +} + func (b *Bitstamp) GetOrderbook() (BitstampOrderbook, error) { type response struct { Timestamp int64 `json:"timestamp,string"` diff --git a/brightonpeakhttp.go b/brightonpeakhttp.go index 2208f30a..872fc314 100644 --- a/brightonpeakhttp.go +++ b/brightonpeakhttp.go @@ -134,6 +134,19 @@ func (b *BrightonPeak) GetTicker(symbol string) (AlphapointTicker, error) { return b.API.GetTicker(symbol) } +func (b *BrightonPeak) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := b.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + + return tickerPrice +} + func (b *BrightonPeak) GetTrades(symbol string, startIndex, count int) (AlphapointTrades, error) { return b.API.GetTrades(symbol, startIndex, count) } diff --git a/btcchttp.go b/btcchttp.go index e8cfddf1..652fb8b0 100644 --- a/btcchttp.go +++ b/btcchttp.go @@ -272,6 +272,15 @@ func (b *BTCC) GetTicker(symbol string) BTCCTicker { return resp.Ticker } +func (b *BTCC) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker := b.GetTicker(currency) + tickerPrice.Ask = ticker.Sell + tickerPrice.Bid = ticker.Buy + + return tickerPrice +} + func (b *BTCC) GetTradesLast24h(symbol string) bool { req := fmt.Sprintf("%sdata/trades?market=%s", BTCC_API_URL, symbol) err := SendHTTPGetRequest(req, true, nil) diff --git a/btcehttp.go b/btcehttp.go index 0367d047..e9b65c17 100644 --- a/btcehttp.go +++ b/btcehttp.go @@ -185,6 +185,21 @@ func (b *BTCE) GetTicker(symbol string) (map[string]BTCeTicker, error) { return response.Data, nil } +/// This getticker is different, so I'll let someone else implement.... +/// Or I will, just later... +func (b *BTCE) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + /*ticker, err:= b.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Buy + tickerPrice.Bid = ticker.Sell + */ + return tickerPrice +} + func (b *BTCE) GetDepth(symbol string) { type Response struct { Data map[string]BTCEOrderbook diff --git a/btcmarkets.go b/btcmarkets.go index 5f82f9ab..5f50adfa 100644 --- a/btcmarkets.go +++ b/btcmarkets.go @@ -188,6 +188,19 @@ func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) { return ticker, nil } +func (b *BTCMarkets) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := b.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.BestAsk + tickerPrice.Bid = ticker.BestBID + + return tickerPrice +} + func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) { orderbook := BTCMarketsOrderbook{} path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol) diff --git a/coinbasehttp.go b/coinbasehttp.go index 7d98ee5b..d157fca6 100644 --- a/coinbasehttp.go +++ b/coinbasehttp.go @@ -359,6 +359,18 @@ func (c *Coinbase) GetTicker(symbol string) (CoinbaseTicker, error) { return ticker, nil } +func (c *Coinbase) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := c.GetTicker(currency) + if err != nil { + log.Println(err) + return TickerPrice{} + } + tickerPrice.Ask = ticker.Price + + return tickerPrice +} + func (c *Coinbase) GetTrades(symbol string) ([]CoinbaseTrade, error) { trades := []CoinbaseTrade{} path := fmt.Sprintf("%s/%s/%s", COINBASE_API_URL+COINBASE_PRODUCTS, symbol, COINBASE_TRADES) diff --git a/geminihttp.go b/geminihttp.go index 8cdf1946..f5594e95 100644 --- a/geminihttp.go +++ b/geminihttp.go @@ -186,6 +186,13 @@ func (g *Gemini) Run() { } } +/// Once GetTicker is created, then add in GetTickerPrice code plz - Scott +func (g *Gemini) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + + return tickerPrice +} + func (g *Gemini) GetSymbols() ([]string, error) { symbols := []string{} path := fmt.Sprintf("%s/v%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_SYMBOLS) diff --git a/huobihttp.go b/huobihttp.go index e77625f0..acc3aecf 100644 --- a/huobihttp.go +++ b/huobihttp.go @@ -132,6 +132,15 @@ func (h *HUOBI) GetTicker(symbol string) HuobiTicker { return resp.Ticker } +func (h *HUOBI) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker := h.GetTicker(currency) + tickerPrice.Ask = ticker.Sell + tickerPrice.Bid = ticker.Buy + + return tickerPrice +} + func (h *HUOBI) GetOrderBook(symbol string) bool { path := fmt.Sprintf("http://market.huobi.com/staticmarket/depth_%s_json.js", symbol) err := SendHTTPGetRequest(path, true, nil) diff --git a/interfaces.go b/interfaces.go index 8fd4bfd2..ea2b2d0d 100644 --- a/interfaces.go +++ b/interfaces.go @@ -6,4 +6,6 @@ type IBotExchange interface { SetDefaults() GetName() string IsEnabled() bool + GetTickerPrice(currency string) TickerPrice } + diff --git a/itbithttp.go b/itbithttp.go index f7b4f300..c249c5cb 100644 --- a/itbithttp.go +++ b/itbithttp.go @@ -134,6 +134,16 @@ func (i *ItBit) GetTicker(currency string) ItBitTicker { return itbitTicker } +func (i *ItBit) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker := i.GetTicker(currency) + + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + + return tickerPrice +} + type ItbitOrderbookEntry struct { Quantitiy float64 `json:"quantity,string"` Price float64 `json:"price,string"` diff --git a/kraken.go b/kraken.go index e1e646cc..71a56106 100644 --- a/kraken.go +++ b/kraken.go @@ -229,6 +229,21 @@ func (k *Kraken) GetTicker(symbol string) error { return nil } +//This will return the TickerPrice struct when tickers are completed here.. +func (k *Kraken) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + /* + ticker, err := i.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid + */ + return tickerPrice +} + func (k *Kraken) GetOHLC(symbol string) error { values := url.Values{} values.Set("pair", symbol) diff --git a/lakebtchttp.go b/lakebtchttp.go index 8b6417f2..a84d3133 100644 --- a/lakebtchttp.go +++ b/lakebtchttp.go @@ -148,6 +148,21 @@ func (l *LakeBTC) GetTicker() LakeBTCTickerResponse { return response } +func (l *LakeBTC) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker := l.GetTicker() + + if(currency == "USD") { + tickerPrice.Ask = ticker.USD.Ask + tickerPrice.Bid = ticker.USD.Bid + } else if(currency =="CNY") { + tickerPrice.Ask = ticker.CNY.Ask + tickerPrice.Bid = ticker.CNY.Bid + } + + return tickerPrice +} + func (l *LakeBTC) GetOrderBook(currency string) bool { req := LAKEBTC_ORDERBOOK if currency == "CNY" { diff --git a/localbitcoinshttp.go b/localbitcoinshttp.go index d6a8b977..c5ad4219 100644 --- a/localbitcoinshttp.go +++ b/localbitcoinshttp.go @@ -136,6 +136,18 @@ func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { return result, nil } +func (l *LocalBitcoins) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := l.GetTicker() + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker[currency].Rates.Last + + return tickerPrice +} + type LocalBitcoinsTrade struct { TID int64 `json:"tid"` Date int64 `json:"date"` diff --git a/okcoinhttp.go b/okcoinhttp.go index 79a47834..1411ee2a 100644 --- a/okcoinhttp.go +++ b/okcoinhttp.go @@ -322,6 +322,19 @@ func (o *OKCoin) GetTicker(symbol string) (OKCoinTicker, error) { return resp.Ticker, nil } +func (o *OKCoin) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := o.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Sell + tickerPrice.Bid = ticker.Buy + + return tickerPrice +} + func (o *OKCoin) GetOrderBook(symbol string, size int64, merge bool) (OKCoinOrderbook, error) { resp := OKCoinOrderbook{} vals := url.Values{} diff --git a/poloniexhttp.go b/poloniexhttp.go index 1925c591..4e525c64 100644 --- a/poloniexhttp.go +++ b/poloniexhttp.go @@ -161,6 +161,19 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { return resp.Data, nil } +func (p *Poloniex) GetTickerPrice(currency string) TickerPrice { + var tickerPrice TickerPrice + ticker, err := p.GetTicker() + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker[currency].Last + tickerPrice.Bid = ticker[currency].HighestBid + + return tickerPrice +} + func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) diff --git a/restfulRouter.go b/restfulRouter.go index ea4edae0..063be8d8 100644 --- a/restfulRouter.go +++ b/restfulRouter.go @@ -8,7 +8,7 @@ import ( func NewRouter(exchanges []IBotExchange) *mux.Router { router := mux.NewRouter().StrictSlash(true) - allRoutes := append(routes,anxRoutes...) + allRoutes := append(routes,exchangeRoutes...) for _, route := range allRoutes { var handler http.Handler handler = route.HandlerFunc diff --git a/tickerRoutes.go b/tickerRoutes.go new file mode 100644 index 00000000..396bf7d1 --- /dev/null +++ b/tickerRoutes.go @@ -0,0 +1,36 @@ +package main + +import ( + "encoding/json" + "net/http" + "github.com/gorilla/mux" +) + +func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + exchangeName := vars["exchangeName"] + var response TickerPrice; + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if(bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName) { + response = bot.exchanges[i].GetTickerPrice(currency) + } + } + } + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } +} + +var exchangeRoutes = Routes{ + Route{ + "Index", + "GET", + "/exchanges/{exchangeName}/latest/{currency}", + jsonTickerResponse, + }, +} \ No newline at end of file From bc34e2f94c5a388084dc48a765f6a398b9a6d750 Mon Sep 17 00:00:00 2001 From: Scott Date: Sun, 5 Jun 2016 19:48:38 +1000 Subject: [PATCH 16/24] (Re)uses config file for server port --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 848a3138..ae0f7990 100644 --- a/main.go +++ b/main.go @@ -142,7 +142,7 @@ func main() { } else { log.Println("HTTP Webserver support enabled.") router := NewRouter(bot.exchanges) - log.Fatal(http.ListenAndServe(":8080", router)) + log.Fatal(http.ListenAndServe(bot.config.Webserver.ListenAddress, router)) } } if !bot.config.Webserver.Enabled { From d561bac8912b7534833292123c7b305ade11460e Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 30 Jun 2016 21:22:30 +1000 Subject: [PATCH 17/24] A completely broken commit which starts the process of retrieving all exchanges and currency data in one call for a frontend dashboard/db --- alphapointhttp.go | 2 ++ anxhttp.go | 4 ++++ interfaces.go | 1 + restfulRouter.go | 1 - restfulRoutes.go | 19 +------------------ tickerRoutes.go | 32 +++++++++++++++++++++++++++++++- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/alphapointhttp.go b/alphapointhttp.go index a20334d1..14628c81 100644 --- a/alphapointhttp.go +++ b/alphapointhttp.go @@ -216,6 +216,8 @@ func (a *Alphapoint) GetTickerPrice(currency string) TickerPrice { return tickerPrice } + + func (a *Alphapoint) GetTrades(symbol string, startIndex, count int) (AlphapointTrades, error) { request := make(map[string]interface{}) request["ins"] = symbol diff --git a/anxhttp.go b/anxhttp.go index 9e90cac7..4e5b849f 100644 --- a/anxhttp.go +++ b/anxhttp.go @@ -197,6 +197,10 @@ func (a *ANX) GetTickerPrice(currency string) TickerPrice { return tickerPrice } +func (a *ANX) GetEnabledCurrencies() []string { + return a.EnabledPairs +} + func (a *ANX) GetAPIKey(username, password, otp, deviceID string) (string, string) { request := make(map[string]interface{}) request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] diff --git a/interfaces.go b/interfaces.go index ea2b2d0d..add48245 100644 --- a/interfaces.go +++ b/interfaces.go @@ -7,5 +7,6 @@ type IBotExchange interface { GetName() string IsEnabled() bool GetTickerPrice(currency string) TickerPrice + //GetEnabledCurrencies() []string } diff --git a/restfulRouter.go b/restfulRouter.go index 063be8d8..b412908a 100644 --- a/restfulRouter.go +++ b/restfulRouter.go @@ -2,7 +2,6 @@ package main import ( "net/http" - "github.com/gorilla/mux" ) diff --git a/restfulRoutes.go b/restfulRoutes.go index bdf42b62..1941d55d 100644 --- a/restfulRoutes.go +++ b/restfulRoutes.go @@ -12,22 +12,5 @@ type Route struct { type Routes []Route var routes = Routes{ - Route{ - "Index", - "GET", - "/", - Index, - }, - Route{ - "TodoIndex", - "GET", - "/todos", - TodoIndex, - }, - Route{ - "TodoShow", - "GET", - "/todos/{todoId}", - TodoShow, - }, + } \ No newline at end of file diff --git a/tickerRoutes.go b/tickerRoutes.go index 396bf7d1..47404dda 100644 --- a/tickerRoutes.go +++ b/tickerRoutes.go @@ -26,9 +26,39 @@ func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { } } +func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { + var response[] TickerPrice + //bot.config.Cryptocurrencies + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if(bot.exchanges[i].IsEnabled()) { + for _, exch := range bot.config.Exchanges { + if(bot.exchanges[i].GetName() == exch.Name) { + for _, enabledPair := range exch.BaseCurrencies { + response = append(response, bot.exchanges[i].GetTickerPrice(enabledPair)) + } + } + } + } + } + } + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } +} + var exchangeRoutes = Routes{ Route{ - "Index", + "AllActiveExchangesAndCurrencies", + "GET", + "/exchanges/enabled/latest/all", + getAllActiveTickersResponse, + }, + Route{ + "IndividualExchangeAndCurrency", "GET", "/exchanges/{exchangeName}/latest/{currency}", jsonTickerResponse, From 7223875230adc7ccb1ca8810790c67e5c49e4677 Mon Sep 17 00:00:00 2001 From: GloriousCode Date: Wed, 13 Jul 2016 21:43:48 +1000 Subject: [PATCH 18/24] Now adds a universal way to retrieve all enabled currencies New endpoint to retrieve values for all enabled currency data for all enabled exchanges and return it as JSON object for front end --- anxhttp.go | 7 ++- bitfinexhttp.go | 15 ++++-- bitstamphttp.go | 11 +++- brightonpeakhttp.go | 13 ++++- btcchttp.go | 13 ++++- btcehttp.go | 4 ++ btcmarkets.go | 8 ++- coinbasehttp.go | 8 ++- geminihttp.go | 6 ++- huobihttp.go | 11 +++- interfaces.go | 5 +- itbithttp.go | 6 ++- kraken.go | 18 ++++--- lakebtchttp.go | 12 +++-- localbitcoinshttp.go | 7 ++- main.go | 54 +++++++++---------- okcoinhttp.go | 16 ++++-- poloniexhttp.go | 13 ++++- poloniexwebsocket.go | 5 +- restfulHandlers.go | 31 ++--------- restfulRouter.go | 33 ++++++------ restfulTodo.go | 11 ---- ticker.go | 16 +++--- tickerRoutes.go | 122 +++++++++++++++++++++++++------------------ 24 files changed, 268 insertions(+), 177 deletions(-) delete mode 100644 restfulTodo.go diff --git a/anxhttp.go b/anxhttp.go index 4e5b849f..f23e0e49 100644 --- a/anxhttp.go +++ b/anxhttp.go @@ -193,7 +193,12 @@ func (a *ANX) GetTickerPrice(currency string) TickerPrice { ticker := a.GetTicker(currency) tickerPrice.Ask = ticker.Data.Buy.Value tickerPrice.Bid = ticker.Data.Sell.Value - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Data.Low.Value + tickerPrice.Last = ticker.Data.Last.Value + tickerPrice.Volume = ticker.Data.Vol.Value + tickerPrice.High = ticker.Data.High.Value + return tickerPrice } diff --git a/bitfinexhttp.go b/bitfinexhttp.go index ef4b1ef1..f3503482 100644 --- a/bitfinexhttp.go +++ b/bitfinexhttp.go @@ -3,12 +3,13 @@ package main import ( "errors" "fmt" - "github.com/gorilla/websocket" "log" "net/url" "strconv" "strings" "time" + + "github.com/gorilla/websocket" ) const ( @@ -211,6 +212,10 @@ func (b *Bitfinex) Setup(exch Exchanges) { } } +func (k *Bitfinex) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (b *Bitfinex) Start() { go b.Run() } @@ -284,7 +289,6 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (BitfinexTicker, return response, nil } - func (b *Bitfinex) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker, err := b.GetTicker(currency, nil) @@ -294,7 +298,12 @@ func (b *Bitfinex) GetTickerPrice(currency string) TickerPrice { } tickerPrice.Ask = ticker.Ask tickerPrice.Bid = ticker.Bid - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Volume + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/bitstamphttp.go b/bitstamphttp.go index 7648a9fc..e2d35311 100644 --- a/bitstamphttp.go +++ b/bitstamphttp.go @@ -169,6 +169,10 @@ func (b *Bitstamp) Setup(exch Exchanges) { } } +func (k *Bitstamp) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (b *Bitstamp) SetEnabled(enabled bool) { b.Enabled = enabled } @@ -243,7 +247,12 @@ func (b *Bitstamp) GetTickerPrice(currency string) TickerPrice { } tickerPrice.Ask = ticker.Ask tickerPrice.Bid = ticker.Bid - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Volume + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/brightonpeakhttp.go b/brightonpeakhttp.go index 872fc314..8e80e14a 100644 --- a/brightonpeakhttp.go +++ b/brightonpeakhttp.go @@ -61,6 +61,10 @@ func (b *BrightonPeak) Setup(exch Exchanges) { } } +func (k *BrightonPeak) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (b *BrightonPeak) Start() { go b.Run() } @@ -134,7 +138,7 @@ func (b *BrightonPeak) GetTicker(symbol string) (AlphapointTicker, error) { return b.API.GetTicker(symbol) } -func (b *BrightonPeak) GetTickerPrice(currency string) TickerPrice { +func (b *BrightonPeak) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker, err := b.GetTicker(currency) if err != nil { @@ -143,7 +147,12 @@ func (b *BrightonPeak) GetTickerPrice(currency string) TickerPrice { } tickerPrice.Ask = ticker.Ask tickerPrice.Bid = ticker.Bid - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Volume + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/btcchttp.go b/btcchttp.go index 652fb8b0..13f042d1 100644 --- a/btcchttp.go +++ b/btcchttp.go @@ -198,6 +198,10 @@ func (b *BTCC) Setup(exch Exchanges) { } } +func (k *BTCC) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + //Start is run if exchange is enabled, after Setup func (b *BTCC) Start() { go b.Run() @@ -272,12 +276,17 @@ func (b *BTCC) GetTicker(symbol string) BTCCTicker { return resp.Ticker } -func (b *BTCC) GetTickerPrice(currency string) TickerPrice { +func (b *BTCC) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker := b.GetTicker(currency) tickerPrice.Ask = ticker.Sell tickerPrice.Bid = ticker.Buy - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Vol + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/btcehttp.go b/btcehttp.go index e9b65c17..a220ae9b 100644 --- a/btcehttp.go +++ b/btcehttp.go @@ -116,6 +116,10 @@ func (b *BTCE) Setup(exch Exchanges) { } } +func (k *BTCE) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (b *BTCE) Start() { go b.Run() } diff --git a/btcmarkets.go b/btcmarkets.go index 5f50adfa..cec733ee 100644 --- a/btcmarkets.go +++ b/btcmarkets.go @@ -125,6 +125,10 @@ func (b *BTCMarkets) Setup(exch Exchanges) { } } +func (k *BTCMarkets) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (b *BTCMarkets) Start() { go b.Run() } @@ -197,7 +201,9 @@ func (b *BTCMarkets) GetTickerPrice(currency string) TickerPrice { } tickerPrice.Ask = ticker.BestAsk tickerPrice.Bid = ticker.BestBID - + tickerPrice.CryptoCurrency = currency + tickerPrice.Last = ticker.LastPrice + return tickerPrice } diff --git a/coinbasehttp.go b/coinbasehttp.go index d157fca6..fc0d916c 100644 --- a/coinbasehttp.go +++ b/coinbasehttp.go @@ -159,6 +159,10 @@ func (c *Coinbase) Setup(exch Exchanges) { } } +func (k *Coinbase) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (c *Coinbase) Start() { go c.Run() } @@ -367,7 +371,9 @@ func (c *Coinbase) GetTickerPrice(currency string) TickerPrice { return TickerPrice{} } tickerPrice.Ask = ticker.Price - + tickerPrice.CryptoCurrency = currency + tickerPrice.Volume = ticker.Size + return tickerPrice } diff --git a/geminihttp.go b/geminihttp.go index f5594e95..548d5ff6 100644 --- a/geminihttp.go +++ b/geminihttp.go @@ -137,6 +137,10 @@ func (g *Gemini) Setup(exch Exchanges) { } } +func (k *Gemini) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (g *Gemini) Start() { go g.Run() } @@ -189,7 +193,7 @@ func (g *Gemini) Run() { /// Once GetTicker is created, then add in GetTickerPrice code plz - Scott func (g *Gemini) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice - + return tickerPrice } diff --git a/huobihttp.go b/huobihttp.go index acc3aecf..bed18289 100644 --- a/huobihttp.go +++ b/huobihttp.go @@ -79,6 +79,10 @@ func (h *HUOBI) Setup(exch Exchanges) { } } +func (k *HUOBI) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (h *HUOBI) Start() { go h.Run() } @@ -137,7 +141,12 @@ func (h *HUOBI) GetTickerPrice(currency string) TickerPrice { ticker := h.GetTicker(currency) tickerPrice.Ask = ticker.Sell tickerPrice.Bid = ticker.Buy - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Vol + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/interfaces.go b/interfaces.go index add48245..f6d66d03 100644 --- a/interfaces.go +++ b/interfaces.go @@ -6,7 +6,6 @@ type IBotExchange interface { SetDefaults() GetName() string IsEnabled() bool - GetTickerPrice(currency string) TickerPrice - //GetEnabledCurrencies() []string + GetTickerPrice(currency string) TickerPrice + GetEnabledCurrencies() []string } - diff --git a/itbithttp.go b/itbithttp.go index c249c5cb..253221c0 100644 --- a/itbithttp.go +++ b/itbithttp.go @@ -86,6 +86,10 @@ func (i *ItBit) Setup(exch Exchanges) { } } +func (k *ItBit) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (i *ItBit) Start() { go i.Run() } @@ -140,7 +144,7 @@ func (i *ItBit) GetTickerPrice(currency string) TickerPrice { tickerPrice.Ask = ticker.Ask tickerPrice.Bid = ticker.Bid - + return tickerPrice } diff --git a/kraken.go b/kraken.go index 71a56106..3bad879d 100644 --- a/kraken.go +++ b/kraken.go @@ -90,6 +90,10 @@ func (k *Kraken) Setup(exch Exchanges) { } } +func (k *Kraken) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (k *Kraken) Start() { go k.Run() } @@ -233,13 +237,13 @@ func (k *Kraken) GetTicker(symbol string) error { func (k *Kraken) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice /* - ticker, err := i.GetTicker(currency) - if err != nil { - log.Println(err) - return tickerPrice - } - tickerPrice.Ask = ticker.Ask - tickerPrice.Bid = ticker.Bid + ticker, err := i.GetTicker(currency) + if err != nil { + log.Println(err) + return tickerPrice + } + tickerPrice.Ask = ticker.Ask + tickerPrice.Bid = ticker.Bid */ return tickerPrice } diff --git a/lakebtchttp.go b/lakebtchttp.go index a84d3133..edea18fd 100644 --- a/lakebtchttp.go +++ b/lakebtchttp.go @@ -95,6 +95,10 @@ func (l *LakeBTC) Setup(exch Exchanges) { } } +func (k *LakeBTC) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (l *LakeBTC) Start() { go l.Run() } @@ -151,15 +155,17 @@ func (l *LakeBTC) GetTicker() LakeBTCTickerResponse { func (l *LakeBTC) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker := l.GetTicker() - - if(currency == "USD") { + + if currency == "USD" { tickerPrice.Ask = ticker.USD.Ask tickerPrice.Bid = ticker.USD.Bid - } else if(currency =="CNY") { + } else if currency == "CNY" { tickerPrice.Ask = ticker.CNY.Ask tickerPrice.Bid = ticker.CNY.Bid } + tickerPrice.CryptoCurrency = currency + return tickerPrice } diff --git a/localbitcoinshttp.go b/localbitcoinshttp.go index c5ad4219..fba44eaf 100644 --- a/localbitcoinshttp.go +++ b/localbitcoinshttp.go @@ -74,6 +74,10 @@ func (l *LocalBitcoins) Setup(exch Exchanges) { } } +func (k *LocalBitcoins) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (l *LocalBitcoins) Start() { go l.Run() } @@ -144,7 +148,8 @@ func (l *LocalBitcoins) GetTickerPrice(currency string) TickerPrice { return tickerPrice } tickerPrice.Ask = ticker[currency].Rates.Last - + tickerPrice.CryptoCurrency = currency + return tickerPrice } diff --git a/main.go b/main.go index ae0f7990..fa670b73 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,12 @@ package main import ( "errors" "log" + "net/http" "os" "os/signal" "runtime" "strconv" "syscall" - "net/http" ) type Exchange struct { @@ -41,8 +41,9 @@ type Bot struct { var bot Bot func SetupBotConfiguration(s IBotExchange, exch Exchanges) { - s.Setup(exch) + s.SetDefaults() if s.GetName() == exch.Name { + s.Setup(exch) if s.IsEnabled() { log.Printf("%s: Exchange support: %s (Authenticated API support: %s - Verbose mode: %s).\n", exch.Name, IsEnabled(exch.Enabled), IsEnabled(exch.AuthenticatedAPISupport), IsEnabled(exch.Verbose)) s.Start() @@ -85,8 +86,6 @@ func main() { log.Println("SMS support disabled.") } - - log.Printf("Available Exchanges: %d. Enabled Exchanges: %d.\n", len(bot.config.Exchanges), GetEnabledExchanges()) log.Println("Bot Exchange support:") @@ -94,25 +93,24 @@ func main() { bot.exchange.okcoinChina.APIUrl = OKCOIN_API_URL_CHINA bot.exchanges = []IBotExchange{ - &bot.exchange.anx, - &bot.exchange.kraken, - &bot.exchange.btcc, - &bot.exchange.bitstamp, - &bot.exchange.brightonpeak, - &bot.exchange.bitfinex, - &bot.exchange.btce, - &bot.exchange.btcmarkets, - &bot.exchange.coinbase, - &bot.exchange.gemini, - &bot.exchange.okcoinChina, - &bot.exchange.okcoinIntl, - &bot.exchange.itbit, - &bot.exchange.lakebtc, - &bot.exchange.localbitcoins, - &bot.exchange.poloniex, - &bot.exchange.huobi, + new(ANX), + new(Kraken), + new(BTCC), + new(Bitstamp), + new(BrightonPeak), + new(Bitfinex), + new(BTCE), + new(BTCMarkets), + new(Coinbase), + new(Gemini), + new(OKCoin), + new(OKCoin), + new(ItBit), + new(LakeBTC), + new(LocalBitcoins), + new(Poloniex), + new(HUOBI), } - for i := 0; i < len(bot.exchanges); i++ { if bot.exchanges[i] != nil { bot.exchanges[i].SetDefaults() @@ -133,22 +131,22 @@ func main() { } } } - - if bot.config.Webserver.Enabled { + + if bot.config.Webserver.Enabled { err := CheckWebserverValues() if err != nil { log.Println(err) // non fatal event - bot.config.Webserver.Enabled = false + //bot.config.Webserver.Enabled = false } else { log.Println("HTTP Webserver support enabled.") - router := NewRouter(bot.exchanges) - log.Fatal(http.ListenAndServe(bot.config.Webserver.ListenAddress, router)) + router := NewRouter(bot.exchanges) + log.Fatal(http.ListenAndServe(bot.config.Webserver.ListenAddress, router)) } } if !bot.config.Webserver.Enabled { log.Println("HTTP Webserver support disabled.") } - + <-bot.shutdown Shutdown() } diff --git a/okcoinhttp.go b/okcoinhttp.go index 1411ee2a..a1cb3343 100644 --- a/okcoinhttp.go +++ b/okcoinhttp.go @@ -2,12 +2,13 @@ package main import ( "errors" - "github.com/gorilla/websocket" "log" "net/url" "strconv" "strings" "time" + + "github.com/gorilla/websocket" ) const ( @@ -229,6 +230,10 @@ func (o *OKCoin) Setup(exch Exchanges) { } } +func (k *OKCoin) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (o *OKCoin) Start() { go o.Run() } @@ -322,7 +327,7 @@ func (o *OKCoin) GetTicker(symbol string) (OKCoinTicker, error) { return resp.Ticker, nil } -func (o *OKCoin) GetTickerPrice(currency string) TickerPrice { +func (o *OKCoin) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker, err := o.GetTicker(currency) if err != nil { @@ -331,7 +336,12 @@ func (o *OKCoin) GetTickerPrice(currency string) TickerPrice { } tickerPrice.Ask = ticker.Sell tickerPrice.Bid = ticker.Buy - + tickerPrice.CryptoCurrency = currency + tickerPrice.Low = ticker.Low + tickerPrice.Last = ticker.Last + tickerPrice.Volume = ticker.Vol + tickerPrice.High = ticker.High + return tickerPrice } diff --git a/poloniexhttp.go b/poloniexhttp.go index 4e525c64..198c5ff0 100644 --- a/poloniexhttp.go +++ b/poloniexhttp.go @@ -105,6 +105,10 @@ func (p *Poloniex) Setup(exch Exchanges) { } } +func (k *Poloniex) GetEnabledCurrencies() []string { + return k.EnabledPairs +} + func (p *Poloniex) Start() { go p.Run() } @@ -161,16 +165,21 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { return resp.Data, nil } -func (p *Poloniex) GetTickerPrice(currency string) TickerPrice { +func (p *Poloniex) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker, err := p.GetTicker() if err != nil { log.Println(err) return tickerPrice } + tickerPrice.CryptoCurrency = currency tickerPrice.Ask = ticker[currency].Last tickerPrice.Bid = ticker[currency].HighestBid - + tickerPrice.High = ticker[currency].HighestBid + tickerPrice.Last = ticker[currency].Last + tickerPrice.Low = ticker[currency].LowestAsk + tickerPrice.Volume = ticker[currency].BaseVolume + return tickerPrice } diff --git a/poloniexwebsocket.go b/poloniexwebsocket.go index 2ae9b3d2..59e76f47 100644 --- a/poloniexwebsocket.go +++ b/poloniexwebsocket.go @@ -1,9 +1,10 @@ package main import ( - "gopkg.in/jcelliott/turnpike.v2" "log" "strconv" + + "gopkg.in/jcelliott/turnpike.v2" ) const ( @@ -132,7 +133,7 @@ func PoloniexOnDepthOrTrade(args []interface{}, kwargs map[string]interface{}) { func (p *Poloniex) WebsocketClient() { for p.Enabled && p.Websocket { - c, err := turnpike.NewWebsocketClient(turnpike.JSON, POLONIEX_WEBSOCKET_ADDRESS) + c, err := turnpike.NewWebsocketClient(turnpike.JSON, POLONIEX_WEBSOCKET_ADDRESS, nil) if err != nil { log.Printf("%s Unable to connect to Websocket. Error: %s\n", p.GetName(), err) continue diff --git a/restfulHandlers.go b/restfulHandlers.go index 83cc1b89..bdc5c32c 100644 --- a/restfulHandlers.go +++ b/restfulHandlers.go @@ -1,34 +1,11 @@ package main import ( - "encoding/json" - "fmt" - "net/http" - "github.com/gorilla/mux" + "fmt" + "net/http" ) func Index(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w,bot.exchanges[0].GetName()) - - + fmt.Fprintln(w, bot.exchanges[0].GetName()) + } - -func TodoIndex(w http.ResponseWriter, r *http.Request) { - - todos := Todos{ - Todo{Name: "Write presentation"}, - Todo{Name: "Host meetup"}, - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(todos); err != nil { - panic(err) - } -} - -func TodoShow(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - todoId := vars["todoId"] - fmt.Fprintln(w, "Todo show:", todoId) -} \ No newline at end of file diff --git a/restfulRouter.go b/restfulRouter.go index b412908a..0ed2d18b 100644 --- a/restfulRouter.go +++ b/restfulRouter.go @@ -1,24 +1,25 @@ package main import ( - "net/http" - "github.com/gorilla/mux" + "net/http" + + "github.com/gorilla/mux" ) func NewRouter(exchanges []IBotExchange) *mux.Router { - router := mux.NewRouter().StrictSlash(true) - allRoutes := append(routes,exchangeRoutes...) - for _, route := range allRoutes { - var handler http.Handler - handler = route.HandlerFunc - handler = Logger(handler, route.Name) + router := mux.NewRouter().StrictSlash(true) + allRoutes := append(routes, exchangeRoutes...) + for _, route := range allRoutes { + var handler http.Handler + handler = route.HandlerFunc + handler = Logger(handler, route.Name) - router. - Methods(route.Method). - Path(route.Pattern). - Name(route.Name). - Handler(handler) + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(handler) - } - return router -} \ No newline at end of file + } + return router +} diff --git a/restfulTodo.go b/restfulTodo.go deleted file mode 100644 index 36e5fa32..00000000 --- a/restfulTodo.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import "time" - -type Todo struct { - Name string `json:"name"` - Completed bool `json:"completed"` - Due time.Time `json:"due"` -} - -type Todos []Todo diff --git a/ticker.go b/ticker.go index c73dc505..3e36fe4a 100644 --- a/ticker.go +++ b/ticker.go @@ -5,14 +5,14 @@ import ( ) type TickerPrice struct { - CryptoCurrency string - FiatCurrency string - Last float64 - High float64 - Low float64 - Bid float64 - Ask float64 - Volume float64 + CryptoCurrency string `json:"CryptoCurrency"` + FiatCurrency string `json:"FiatCurrency"` + Last float64 `json:"Last"` + High float64 `json:"High"` + Low float64 `json:"Low"` + Bid float64 `json:"Bid"` + Ask float64 `json:"Ask"` + Volume float64 `json:"Volume"` } type Ticker struct { diff --git a/tickerRoutes.go b/tickerRoutes.go index 47404dda..ccc81d23 100644 --- a/tickerRoutes.go +++ b/tickerRoutes.go @@ -1,66 +1,84 @@ package main import ( - "encoding/json" - "net/http" - "github.com/gorilla/mux" + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" ) func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - currency := vars["currency"] - exchangeName := vars["exchangeName"] - var response TickerPrice; - for i := 0; i < len(bot.exchanges); i++ { - if bot.exchanges[i] != nil { - if(bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName) { - response = bot.exchanges[i].GetTickerPrice(currency) - } - } - } + vars := mux.Vars(r) + currency := vars["currency"] + exchangeName := vars["exchangeName"] + var response TickerPrice + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { + response = bot.exchanges[i].GetTickerPrice(currency) + } + } + } - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + encoder := json.NewEncoder(w) + + if err := encoder.Encode(response); err != nil { + panic(err) + } +} + +type AllEnabledExchangeCurrencies struct { + Data []EnabledExchangeCurrencies `json:"data"` +} + +type EnabledExchangeCurrencies struct { + ExchangeName string `json:"exchangeName"` + ExchangeValues []TickerPrice `json:"exchangeValues"` } func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { - var response[] TickerPrice - //bot.config.Cryptocurrencies - for i := 0; i < len(bot.exchanges); i++ { - if bot.exchanges[i] != nil { - if(bot.exchanges[i].IsEnabled()) { - for _, exch := range bot.config.Exchanges { - if(bot.exchanges[i].GetName() == exch.Name) { - for _, enabledPair := range exch.BaseCurrencies { - response = append(response, bot.exchanges[i].GetTickerPrice(enabledPair)) - } - } - } - } - } - } + var response AllEnabledExchangeCurrencies + log.Println(bot.exchanges[15]) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } + for _, individualBot := range bot.exchanges { + if individualBot != nil && individualBot.IsEnabled() { + var individualExchange EnabledExchangeCurrencies + individualExchange.ExchangeName = individualBot.GetName() + log.Println("Getting enabled currencies for '" + individualBot.GetName() + "'") + currencies := individualBot.GetEnabledCurrencies() + log.Println(currencies) + for _, currency := range currencies { + tickerPrice := individualBot.GetTickerPrice(currency) + log.Println(tickerPrice) + + individualExchange.ExchangeValues = append(individualExchange.ExchangeValues, tickerPrice) + } + response.Data = append(response.Data, individualExchange) + } + } + + log.Println(response) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } } var exchangeRoutes = Routes{ - Route{ - "AllActiveExchangesAndCurrencies", - "GET", - "/exchanges/enabled/latest/all", - getAllActiveTickersResponse, - }, - Route{ - "IndividualExchangeAndCurrency", - "GET", - "/exchanges/{exchangeName}/latest/{currency}", - jsonTickerResponse, - }, -} \ No newline at end of file + Route{ + "AllActiveExchangesAndCurrencies", + "GET", + "/exchanges/enabled/latest/all", + getAllActiveTickersResponse, + }, + Route{ + "IndividualExchangeAndCurrency", + "GET", + "/exchanges/{exchangeName}/latest/{currency}", + jsonTickerResponse, + }, +} From 175a75ba7bb16e44e475a5621ba184e566b7524b Mon Sep 17 00:00:00 2001 From: GloriousCode Date: Wed, 13 Jul 2016 21:46:47 +1000 Subject: [PATCH 19/24] Adds new server which requires node. Real basic, real crap --- web/glorious-web/Server.js | 38 ++++++++++++++++++++ web/glorious-web/package.json | 10 ++++++ web/glorious-web/views/about.html | 25 +++++++++++++ web/glorious-web/views/contact.html | 56 +++++++++++++++++++++++++++++ web/glorious-web/views/index.html | 30 ++++++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 web/glorious-web/Server.js create mode 100644 web/glorious-web/package.json create mode 100644 web/glorious-web/views/about.html create mode 100644 web/glorious-web/views/contact.html create mode 100644 web/glorious-web/views/index.html diff --git a/web/glorious-web/Server.js b/web/glorious-web/Server.js new file mode 100644 index 00000000..ac06935d --- /dev/null +++ b/web/glorious-web/Server.js @@ -0,0 +1,38 @@ +var express = require('express') + , app = express(); +var request = require('request'); + + var path = __dirname + '/views/'; + +app.listen(80, function(){ + console.log('CORS-enabled web server listening on port 80'); +}); + + +app.get("/",function(req,res){ + res.sendFile(path + "index.html"); +}); + +app.get("/about",function(req,res){ + res.sendFile(path + "about.html"); +}); + +app.get("/contact",function(req,res){ + res.sendFile(path + "contact.html"); +}); + + +app.listen(3000,function(){ + console.log("Live at Port 3000"); +}); + + + +app.get('/Data/:path', function (req, res) { + request({ + url :'http://localhost:8080/exchanges/Poloniex/latest/BTC_LTC' + },function(err, resp, body){ + res.send(body); + }) + +}); \ No newline at end of file diff --git a/web/glorious-web/package.json b/web/glorious-web/package.json new file mode 100644 index 00000000..94a89b92 --- /dev/null +++ b/web/glorious-web/package.json @@ -0,0 +1,10 @@ +{ + "name" : "website-using-express", + "version" : "0.0.1", + "scripts" : { + "start" : "node Server.js" + }, + "dependencies" : { + "express" : "latest" + } +} diff --git a/web/glorious-web/views/about.html b/web/glorious-web/views/about.html new file mode 100644 index 00000000..346c0ba7 --- /dev/null +++ b/web/glorious-web/views/about.html @@ -0,0 +1,25 @@ + + + + + + +
+ +
+
+

+ Put detail description about you and your company. +

+
+
+ + diff --git a/web/glorious-web/views/contact.html b/web/glorious-web/views/contact.html new file mode 100644 index 00000000..2fe1bb5c --- /dev/null +++ b/web/glorious-web/views/contact.html @@ -0,0 +1,56 @@ + + + + + + +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + diff --git a/web/glorious-web/views/index.html b/web/glorious-web/views/index.html new file mode 100644 index 00000000..87b33ef7 --- /dev/null +++ b/web/glorious-web/views/index.html @@ -0,0 +1,30 @@ + + + + +Single page web app using Angularjs + + + + + + + + +
+ +
+

+This is place to put content. You can put slider, short description about your website and place some links for navigation. +

+
+ + From 1b0c8a8e2365ad7bf6d3b5133d40cfab3fd471db Mon Sep 17 00:00:00 2001 From: GloriousCode Date: Sat, 16 Jul 2016 19:06:18 +1000 Subject: [PATCH 20/24] Implements EJS for some template action --- web/glorious-web/Server.js | 56 +++--- web/glorious-web/package.json | 5 +- web/glorious-web/views/about.html | 25 --- web/glorious-web/views/contact.html | 56 ------ web/glorious-web/views/index.html | 30 ---- web/glorious-web/views/pages/about.ejs | 200 +++++++++++++++++++++ web/glorious-web/views/pages/index.ejs | 26 +++ web/glorious-web/views/pages/settings.ejs | 27 +++ web/glorious-web/views/partials/footer.ejs | 3 + web/glorious-web/views/partials/head.ejs | 11 ++ web/glorious-web/views/partials/header.ejs | 21 +++ 11 files changed, 319 insertions(+), 141 deletions(-) delete mode 100644 web/glorious-web/views/about.html delete mode 100644 web/glorious-web/views/contact.html delete mode 100644 web/glorious-web/views/index.html create mode 100644 web/glorious-web/views/pages/about.ejs create mode 100644 web/glorious-web/views/pages/index.ejs create mode 100644 web/glorious-web/views/pages/settings.ejs create mode 100644 web/glorious-web/views/partials/footer.ejs create mode 100644 web/glorious-web/views/partials/head.ejs create mode 100644 web/glorious-web/views/partials/header.ejs diff --git a/web/glorious-web/Server.js b/web/glorious-web/Server.js index ac06935d..ecdceec3 100644 --- a/web/glorious-web/Server.js +++ b/web/glorious-web/Server.js @@ -1,38 +1,38 @@ -var express = require('express') - , app = express(); -var request = require('request'); - - var path = __dirname + '/views/'; +var express = require('express'); +var app = express(); -app.listen(80, function(){ - console.log('CORS-enabled web server listening on port 80'); +// set the view engine to ejs +app.set('view engine', 'ejs'); + +// use res.render to load up an ejs view file + +// index page +app.get('/', function(req, res) { + res.render('pages/index', { + }); +}); + +// setting page +app.get('/settings', function(req, res) { + res.render('pages/settings', { + }); +}); + +// about page +app.get('/about', function(req, res) { + res.render('pages/about'); }); -app.get("/",function(req,res){ - res.sendFile(path + "index.html"); -}); - -app.get("/about",function(req,res){ - res.sendFile(path + "about.html"); -}); - -app.get("/contact",function(req,res){ - res.sendFile(path + "contact.html"); -}); - - -app.listen(3000,function(){ - console.log("Live at Port 3000"); -}); - - - -app.get('/Data/:path', function (req, res) { +app.get('/data/all-enabled-currencies', function (req, res) { request({ - url :'http://localhost:8080/exchanges/Poloniex/latest/BTC_LTC' + url :'http://localhost:9050/exchanges/enabled/latest/all' },function(err, resp, body){ res.send(body); }) +}); + +app.listen(80, function(){ + console.log('CORS-enabled web server listening on port 80'); }); \ No newline at end of file diff --git a/web/glorious-web/package.json b/web/glorious-web/package.json index 94a89b92..6ed86901 100644 --- a/web/glorious-web/package.json +++ b/web/glorious-web/package.json @@ -1,10 +1,11 @@ { - "name" : "website-using-express", + "name" : "gocryptotrader-website", "version" : "0.0.1", "scripts" : { "start" : "node Server.js" }, "dependencies" : { - "express" : "latest" + "express" : "latest", + "ejs":"latest" } } diff --git a/web/glorious-web/views/about.html b/web/glorious-web/views/about.html deleted file mode 100644 index 346c0ba7..00000000 --- a/web/glorious-web/views/about.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -
- -
-
-

- Put detail description about you and your company. -

-
-
- - diff --git a/web/glorious-web/views/contact.html b/web/glorious-web/views/contact.html deleted file mode 100644 index 2fe1bb5c..00000000 --- a/web/glorious-web/views/contact.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
- -
-
-
-
- - diff --git a/web/glorious-web/views/index.html b/web/glorious-web/views/index.html deleted file mode 100644 index 87b33ef7..00000000 --- a/web/glorious-web/views/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - -Single page web app using Angularjs - - - - - - - - -
- -
-

-This is place to put content. You can put slider, short description about your website and place some links for navigation. -

-
- - diff --git a/web/glorious-web/views/pages/about.ejs b/web/glorious-web/views/pages/about.ejs new file mode 100644 index 00000000..9a156802 --- /dev/null +++ b/web/glorious-web/views/pages/about.ejs @@ -0,0 +1,200 @@ + + + + + + <% include ../partials/head %> + + + +
+ <% include ../partials/header %> +
+ +
+
+
+ +
+
+

+ + README.md +

+ +

Cryptocurrency trading bot written in Golang

+ +

Build Status +Test Coverage

+ +

A cryptocurrency trading bot supporting multiple exchanges written in Golang.

+ +

Please note that this bot is under development and is not ready for production!

+ +

Exchange Support Table

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExchangeREST APIStreaming APIFIX API
AlphapointYesYesNA
ANXPROYesNoNA
BitfinexYesYesNA
BitstampYesYesNA
BTCCYesYesNo
BTCEYesNANA
BTCMarketsYesNANA
CoinbaseYesYesNo
GeminiYesNANA
HuobiYesYesNo
ItBitYesNANA
KrakenYesNANA
LakeBTCYesYesNA
LocalBitcoinsYesNANA
OKCoin (both)YesYesNo
PoloniexYesYesNA
+ +

** NA means not applicable as the Exchange does not support the feature.

+ +

Current Features

+ +
    +
  • Support for all Exchange fiat and digital currencies, with the ability to individually toggle them on/off.
  • +
  • REST API support for all exchanges.
  • +
  • Websocket support for applicable exchanges.
  • +
  • Ability to turn off/on certain exchanges.
  • +
  • Ability to adjust manual polling timer for exchanges.
  • +
  • SMS notification support via SMS Gateway.
  • +
  • Basic event trigger system.
  • +
+ +

Planned Features

+ +
    +
  • WebGUI.
  • +
  • FIX support.
  • +
  • Expanding event trigger system.
  • +
  • TALib.
  • +
  • Trade history summary generation for tax purposes.
  • +
+ +

Please feel free to submit any pull requests or suggest any desired features to be added.

+ +

Compiling instructions

+ +

Download Go from https://golang.org/dl/
+Using a terminal, type go get github.com/thrasher-/gocryptotrader
+Change directory to the package directory, then type go install.
+Copy config_example.json to config.json.
+Make any neccessary changes to the config file.
+Run the application!

+ +

Binaries

+ +

Binaries will be published once the codebase reaches a stable condition.

+
+
+
+ +
+
+ +
+

Look I'm A Sidebar!

+
+ +
+
+
+ +
+ <% include ../partials/footer %> +
+ + + \ No newline at end of file diff --git a/web/glorious-web/views/pages/index.ejs b/web/glorious-web/views/pages/index.ejs new file mode 100644 index 00000000..dc80b817 --- /dev/null +++ b/web/glorious-web/views/pages/index.ejs @@ -0,0 +1,26 @@ + + + + + + <% include ../partials/head %> + + + +
+ <% include ../partials/header %> +
+ +
+
+

Hello world!

+ +
+
+ +
+ <% include ../partials/footer %> +
+ + + \ No newline at end of file diff --git a/web/glorious-web/views/pages/settings.ejs b/web/glorious-web/views/pages/settings.ejs new file mode 100644 index 00000000..582d5c3a --- /dev/null +++ b/web/glorious-web/views/pages/settings.ejs @@ -0,0 +1,27 @@ + + + + + + <% include ../partials/head %> + + + +
+ <% include ../partials/header %> +
+ +
+
+

Settings

+

This will be a visiual interface to edit the config file

+

Things like enabling exchanges, currencies and setting excahnge account settings

+
+
+ +
+ <% include ../partials/footer %> +
+ + + \ No newline at end of file diff --git a/web/glorious-web/views/partials/footer.ejs b/web/glorious-web/views/partials/footer.ejs new file mode 100644 index 00000000..4d4d8d41 --- /dev/null +++ b/web/glorious-web/views/partials/footer.ejs @@ -0,0 +1,3 @@ + + +

© Copyright 2016 GoCrypto Trader

\ No newline at end of file diff --git a/web/glorious-web/views/partials/head.ejs b/web/glorious-web/views/partials/head.ejs new file mode 100644 index 00000000..00ba9698 --- /dev/null +++ b/web/glorious-web/views/partials/head.ejs @@ -0,0 +1,11 @@ + + + +GoCrpto Trader + + + + + + + \ No newline at end of file diff --git a/web/glorious-web/views/partials/header.ejs b/web/glorious-web/views/partials/header.ejs new file mode 100644 index 00000000..ce5d59b8 --- /dev/null +++ b/web/glorious-web/views/partials/header.ejs @@ -0,0 +1,21 @@ + + + \ No newline at end of file From 174d715dbba4b760c4c8300d2efc5bd68966e711 Mon Sep 17 00:00:00 2001 From: GloriousCode Date: Sat, 23 Jul 2016 09:57:40 +1000 Subject: [PATCH 21/24] Going to start trying to get routing working --- web/glorious-web/Server.js | 1 + web/glorious-web/app.js | 13 +++++++++++++ web/glorious-web/routes/index.js | 3 +++ 3 files changed, 17 insertions(+) create mode 100644 web/glorious-web/app.js create mode 100644 web/glorious-web/routes/index.js diff --git a/web/glorious-web/Server.js b/web/glorious-web/Server.js index ecdceec3..f72cf164 100644 --- a/web/glorious-web/Server.js +++ b/web/glorious-web/Server.js @@ -1,4 +1,5 @@ var express = require('express'); +var routes = require('./routes'); var app = express(); // set the view engine to ejs diff --git a/web/glorious-web/app.js b/web/glorious-web/app.js new file mode 100644 index 00000000..e52f19f3 --- /dev/null +++ b/web/glorious-web/app.js @@ -0,0 +1,13 @@ +'use strict'; + +// Declare app level module which depends on views, and components +angular.module('gocryptoweb', [ + 'ngRoute', + 'gocryptoweb.view1', + 'gocryptoweb.view2' +]). +config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { + $locationProvider.hashPrefix('!'); + + $routeProvider.otherwise({redirectTo: '/view1'}); +}]); diff --git a/web/glorious-web/routes/index.js b/web/glorious-web/routes/index.js new file mode 100644 index 00000000..c2d652f5 --- /dev/null +++ b/web/glorious-web/routes/index.js @@ -0,0 +1,3 @@ +exports.index = function(req, res){ + res.render('layout'); +}; \ No newline at end of file From 5176a10b45f4738011f1bb379a1b7bdb897843fd Mon Sep 17 00:00:00 2001 From: GloriousCode Date: Sat, 23 Jul 2016 12:55:58 +1000 Subject: [PATCH 22/24] I should have it now. But cannot check it out due to lack of CDN connetivity --- .gitignore | 4 +++- web/glorious-web/app.js | 3 +-- web/glorious-web/controllers.js | 23 ++++++++++++++++++++++ web/glorious-web/index.js | 9 +++++++++ web/glorious-web/views/pages/index.ejs | 8 ++++---- web/glorious-web/views/partials/footer.ejs | 2 +- 6 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 web/glorious-web/controllers.js create mode 100644 web/glorious-web/index.js diff --git a/.gitignore b/.gitignore index 0cffcb34..f2403fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -config.json \ No newline at end of file +config.json +node_modules +lib diff --git a/web/glorious-web/app.js b/web/glorious-web/app.js index e52f19f3..55c994ec 100644 --- a/web/glorious-web/app.js +++ b/web/glorious-web/app.js @@ -3,8 +3,7 @@ // Declare app level module which depends on views, and components angular.module('gocryptoweb', [ 'ngRoute', - 'gocryptoweb.view1', - 'gocryptoweb.view2' + 'gocryptoweb.controllers' ]). config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { $locationProvider.hashPrefix('!'); diff --git a/web/glorious-web/controllers.js b/web/glorious-web/controllers.js new file mode 100644 index 00000000..ea84afdc --- /dev/null +++ b/web/glorious-web/controllers.js @@ -0,0 +1,23 @@ +'use strict'; + +/* Controllers */ + +angular.module('gocryptoweb.controllers', []). + controller('HomeController', function ($scope, $http) { + $scope.working = true; + $http({ + method: 'GET', + url: '/data/all-enabled-currencies' + }). + success(function (data, status, headers, config) { + $scope.exchanges = data.data.exchanges; + }). + error(function (data, status, headers, config) { + console.log('error'); + }); + + }). + controller('MyCtrl1', function ($scope) { + // write Ctrl here + + }); diff --git a/web/glorious-web/index.js b/web/glorious-web/index.js new file mode 100644 index 00000000..218da370 --- /dev/null +++ b/web/glorious-web/index.js @@ -0,0 +1,9 @@ + +exports.index = function(req, res){ + res.render('index'); +}; + +exports.partials = function (req, res) { + var name = req.params.name; + res.render('partials/' + name); +}; \ No newline at end of file diff --git a/web/glorious-web/views/pages/index.ejs b/web/glorious-web/views/pages/index.ejs index dc80b817..ff23f2f1 100644 --- a/web/glorious-web/views/pages/index.ejs +++ b/web/glorious-web/views/pages/index.ejs @@ -1,7 +1,7 @@ - + <% include ../partials/head %> @@ -12,9 +12,9 @@
-
-

Hello world!

- +
+ + {{working}}
diff --git a/web/glorious-web/views/partials/footer.ejs b/web/glorious-web/views/partials/footer.ejs index 4d4d8d41..55675996 100644 --- a/web/glorious-web/views/partials/footer.ejs +++ b/web/glorious-web/views/partials/footer.ejs @@ -1,3 +1,3 @@ -

© Copyright 2016 GoCrypto Trader

\ No newline at end of file +

Copyright 2016 GoCrypto Trader

\ No newline at end of file From 22f3739e8c548e219086e3cf9203eb4b2d11960a Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 26 Jul 2016 20:47:06 +1000 Subject: [PATCH 23/24] Adds webserver with dashboard that gets all enabled exchanges and currencies --- web/.bowerrc | 3 + web/.gitignore | 7 + web/.jshintrc | 24 +++ web/.travis.yml | 14 ++ web/LICENSE | 22 +++ web/README.md | 24 +++ web/app/app.css | 30 ++++ web/{glorious-web => app}/app.js | 8 +- .../components/version/interpolate-filter.js | 9 + .../version/interpolate-filter_test.js | 15 ++ .../components/version/version-directive.js | 9 + .../version/version-directive_test.js | 17 ++ web/app/components/version/version.js | 8 + web/app/components/version/version_test.js | 11 ++ web/app/index-async.html | 58 +++++++ web/app/index.html | 63 +++++++ .../about.ejs => app/views/about/about.html} | 25 +-- web/app/views/about/about.js | 14 ++ web/app/views/about/about_test.js | 16 ++ web/app/views/home/home.html | 28 +++ web/app/views/home/home.js | 28 +++ web/app/views/home/home_test.js | 16 ++ web/bower.json | 15 ++ web/dashboard-contact.html | 49 ------ web/dashboard-marketdepth.html | 89 ---------- web/dashboard-ordermanagement.html | 70 -------- web/dashboard-reports.html | 89 ---------- web/dashboard-settings.html | 44 ----- web/e2e-tests/protractor.conf.js | 22 +++ web/e2e-tests/scenarios.js | 42 +++++ web/error.html | 37 ---- web/favicon.ico | Bin 139245 -> 0 bytes web/footer.html | 4 - web/glorious-web/Server.js | 39 ----- web/glorious-web/controllers.js | 23 --- web/glorious-web/index.js | 9 - web/glorious-web/package.json | 11 -- web/glorious-web/routes/index.js | 3 - web/glorious-web/views/pages/index.ejs | 26 --- web/glorious-web/views/pages/settings.ejs | 27 --- web/glorious-web/views/partials/footer.ejs | 3 - web/glorious-web/views/partials/head.ejs | 11 -- web/glorious-web/views/partials/header.ejs | 21 --- web/header.html | 19 -- web/index.html | 37 ---- web/karma.conf.js | 34 ++++ web/login.html | 6 - web/package.json | 38 ++++ web/server.js | 27 +++ web/static/css/cover.css | 163 ------------------ web/static/css/dashboard.css | 105 ----------- 51 files changed, 601 insertions(+), 911 deletions(-) create mode 100644 web/.bowerrc create mode 100644 web/.gitignore create mode 100644 web/.jshintrc create mode 100644 web/.travis.yml create mode 100644 web/LICENSE create mode 100644 web/README.md create mode 100644 web/app/app.css rename web/{glorious-web => app}/app.js (65%) create mode 100644 web/app/components/version/interpolate-filter.js create mode 100644 web/app/components/version/interpolate-filter_test.js create mode 100644 web/app/components/version/version-directive.js create mode 100644 web/app/components/version/version-directive_test.js create mode 100644 web/app/components/version/version.js create mode 100644 web/app/components/version/version_test.js create mode 100644 web/app/index-async.html create mode 100644 web/app/index.html rename web/{glorious-web/views/pages/about.ejs => app/views/about/about.html} (95%) create mode 100644 web/app/views/about/about.js create mode 100644 web/app/views/about/about_test.js create mode 100644 web/app/views/home/home.html create mode 100644 web/app/views/home/home.js create mode 100644 web/app/views/home/home_test.js create mode 100644 web/bower.json delete mode 100644 web/dashboard-contact.html delete mode 100644 web/dashboard-marketdepth.html delete mode 100644 web/dashboard-ordermanagement.html delete mode 100644 web/dashboard-reports.html delete mode 100644 web/dashboard-settings.html create mode 100644 web/e2e-tests/protractor.conf.js create mode 100644 web/e2e-tests/scenarios.js delete mode 100644 web/error.html delete mode 100644 web/favicon.ico delete mode 100644 web/footer.html delete mode 100644 web/glorious-web/Server.js delete mode 100644 web/glorious-web/controllers.js delete mode 100644 web/glorious-web/index.js delete mode 100644 web/glorious-web/package.json delete mode 100644 web/glorious-web/routes/index.js delete mode 100644 web/glorious-web/views/pages/index.ejs delete mode 100644 web/glorious-web/views/pages/settings.ejs delete mode 100644 web/glorious-web/views/partials/footer.ejs delete mode 100644 web/glorious-web/views/partials/head.ejs delete mode 100644 web/glorious-web/views/partials/header.ejs delete mode 100644 web/header.html delete mode 100644 web/index.html create mode 100644 web/karma.conf.js delete mode 100644 web/login.html create mode 100644 web/package.json create mode 100644 web/server.js delete mode 100644 web/static/css/cover.css delete mode 100644 web/static/css/dashboard.css diff --git a/web/.bowerrc b/web/.bowerrc new file mode 100644 index 00000000..8c58c8ef --- /dev/null +++ b/web/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "app/bower_components" +} \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 00000000..b702acc6 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,7 @@ +logs/* +!.gitkeep +node_modules/ +bower_components/ +tmp +.DS_Store +.idea \ No newline at end of file diff --git a/web/.jshintrc b/web/.jshintrc new file mode 100644 index 00000000..60f49fd3 --- /dev/null +++ b/web/.jshintrc @@ -0,0 +1,24 @@ +{ + "strict": "global", + "globals": { + // Angular + "angular": false, + + // Angular mocks + "module": false, + "inject": false, + + // Jasmine + "jasmine": false, + "describe": false, + "beforeEach": false, + "afterEach": false, + "it": false, + "expect": false, + + // Protractor + "browser": false, + "element": false, + "by": false + } +} diff --git a/web/.travis.yml b/web/.travis.yml new file mode 100644 index 00000000..a549d113 --- /dev/null +++ b/web/.travis.yml @@ -0,0 +1,14 @@ +language: node_js +node_js: + - '4.4' + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - npm start > /dev/null & + - npm run update-webdriver + - sleep 1 # give server time to start + +script: + - node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox + - node_modules/.bin/protractor e2e-tests/protractor.conf.js --browser=firefox diff --git a/web/LICENSE b/web/LICENSE new file mode 100644 index 00000000..b8de5aae --- /dev/null +++ b/web/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2010-2016 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/web/README.md b/web/README.md new file mode 100644 index 00000000..aa5ae109 --- /dev/null +++ b/web/README.md @@ -0,0 +1,24 @@ +### Prerequisites + +You need git to clone the angular-seed repository. You can get git from +[http://git-scm.com/](http://git-scm.com/). + +We also use a number of node.js tools to initialize and test angular-seed. You must have node.js and +its package manager (npm) installed. You can get them from [http://nodejs.org/](http://nodejs.org/). +### Install Dependencies + +``` +npm install +``` + + +### Run the Application + +The simplest way to start this server is: + +``` +npm start +``` + +Now browse to the app at `http://localhost/`. + diff --git a/web/app/app.css b/web/app/app.css new file mode 100644 index 00000000..c9252407 --- /dev/null +++ b/web/app/app.css @@ -0,0 +1,30 @@ +/* app css stylesheet */ + +.menu { + list-style: none; + border-bottom: 0.1em solid black; + margin-bottom: 2em; + padding: 0 0 0.5em; +} + +.menu:before { + content: "["; +} + +.menu:after { + content: "]"; +} + +.menu > li { + display: inline; +} + +.menu > li:before { + content: "|"; + padding-right: 0.3em; +} + +.menu > li:nth-child(1):before { + content: ""; + padding: 0; +} diff --git a/web/glorious-web/app.js b/web/app/app.js similarity index 65% rename from web/glorious-web/app.js rename to web/app/app.js index 55c994ec..fcbf7640 100644 --- a/web/glorious-web/app.js +++ b/web/app/app.js @@ -1,12 +1,14 @@ 'use strict'; // Declare app level module which depends on views, and components -angular.module('gocryptoweb', [ +angular.module('myApp', [ 'ngRoute', - 'gocryptoweb.controllers' + 'myApp.home', + 'myApp.about', + 'myApp.version' ]). config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { $locationProvider.hashPrefix('!'); - $routeProvider.otherwise({redirectTo: '/view1'}); + $routeProvider.otherwise({redirectTo: '/'}); }]); diff --git a/web/app/components/version/interpolate-filter.js b/web/app/components/version/interpolate-filter.js new file mode 100644 index 00000000..03bb1987 --- /dev/null +++ b/web/app/components/version/interpolate-filter.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.interpolate-filter', []) + +.filter('interpolate', ['version', function(version) { + return function(text) { + return String(text).replace(/\%VERSION\%/mg, version); + }; +}]); diff --git a/web/app/components/version/interpolate-filter_test.js b/web/app/components/version/interpolate-filter_test.js new file mode 100644 index 00000000..ff56c529 --- /dev/null +++ b/web/app/components/version/interpolate-filter_test.js @@ -0,0 +1,15 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('interpolate filter', function() { + beforeEach(module(function($provide) { + $provide.value('version', 'TEST_VER'); + })); + + it('should replace VERSION', inject(function(interpolateFilter) { + expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); + })); + }); +}); diff --git a/web/app/components/version/version-directive.js b/web/app/components/version/version-directive.js new file mode 100644 index 00000000..74088f8a --- /dev/null +++ b/web/app/components/version/version-directive.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.version-directive', []) + +.directive('appVersion', ['version', function(version) { + return function(scope, elm, attrs) { + elm.text(version); + }; +}]); diff --git a/web/app/components/version/version-directive_test.js b/web/app/components/version/version-directive_test.js new file mode 100644 index 00000000..4a59e119 --- /dev/null +++ b/web/app/components/version/version-directive_test.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('app-version directive', function() { + it('should print current version', function() { + module(function($provide) { + $provide.value('version', 'TEST_VER'); + }); + inject(function($compile, $rootScope) { + var element = $compile('')($rootScope); + expect(element.text()).toEqual('TEST_VER'); + }); + }); + }); +}); diff --git a/web/app/components/version/version.js b/web/app/components/version/version.js new file mode 100644 index 00000000..cb7a10f9 --- /dev/null +++ b/web/app/components/version/version.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('myApp.version', [ + 'myApp.version.interpolate-filter', + 'myApp.version.version-directive' +]) + +.value('version', '0.1'); diff --git a/web/app/components/version/version_test.js b/web/app/components/version/version_test.js new file mode 100644 index 00000000..4ca6880d --- /dev/null +++ b/web/app/components/version/version_test.js @@ -0,0 +1,11 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('version service', function() { + it('should return current version', inject(function(version) { + expect(version).toEqual('0.1'); + })); + }); +}); diff --git a/web/app/index-async.html b/web/app/index-async.html new file mode 100644 index 00000000..39a592fb --- /dev/null +++ b/web/app/index-async.html @@ -0,0 +1,58 @@ + + + + + + + + + + My AngularJS App + + + + + +
+ +
Angular seed app: v
+ + + diff --git a/web/app/index.html b/web/app/index.html new file mode 100644 index 00000000..66d52107 --- /dev/null +++ b/web/app/index.html @@ -0,0 +1,63 @@ + + + + + + + + + GoCrpto Trader + + + + + + + + + + + + + + + + + +
+ + +

Copyright 2016 GoCrypto Trader

+ + + + + + + + + + + diff --git a/web/glorious-web/views/pages/about.ejs b/web/app/views/about/about.html similarity index 95% rename from web/glorious-web/views/pages/about.ejs rename to web/app/views/about/about.html index 9a156802..d5c01f4c 100644 --- a/web/glorious-web/views/pages/about.ejs +++ b/web/app/views/about/about.html @@ -1,18 +1,4 @@ - - - - - - <% include ../partials/head %> - - - -
- <% include ../partials/header %> -
- -
-
+
@@ -190,11 +176,4 @@ Run the application!

-
- -
- <% include ../partials/footer %> -
- - - \ No newline at end of file + \ No newline at end of file diff --git a/web/app/views/about/about.js b/web/app/views/about/about.js new file mode 100644 index 00000000..88de9479 --- /dev/null +++ b/web/app/views/about/about.js @@ -0,0 +1,14 @@ +'use strict'; + +angular.module('myApp.about', ['ngRoute']) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/about', { + templateUrl: '/views/about/about.html', + controller: 'AboutController' + }); +}]) + +.controller('AboutController', [function() { + +}]); \ No newline at end of file diff --git a/web/app/views/about/about_test.js b/web/app/views/about/about_test.js new file mode 100644 index 00000000..07b34d6b --- /dev/null +++ b/web/app/views/about/about_test.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('myApp.view2 module', function() { + + beforeEach(module('myApp.view2')); + + describe('view2 controller', function(){ + + it('should ....', inject(function($controller) { + //spec body + var view2Ctrl = $controller('View2Ctrl'); + expect(view2Ctrl).toBeDefined(); + })); + + }); +}); \ No newline at end of file diff --git a/web/app/views/home/home.html b/web/app/views/home/home.html new file mode 100644 index 00000000..0eddc317 --- /dev/null +++ b/web/app/views/home/home.html @@ -0,0 +1,28 @@ +

Dashboard

+

All enabled currencies

+
+

{{exchange.exchangeName}}

+ + + + + + + + + + + + + + + + + + + +
CurrencyLastHighLowVolumeBidAsk
{{value.CryptoCurrency}}{{value.Last}}{{value.High}}{{value.Low}}{{value.Volume}}{{value.Bid}}{{value.Ask}}
+
+ +
+
\ No newline at end of file diff --git a/web/app/views/home/home.js b/web/app/views/home/home.js new file mode 100644 index 00000000..d2412903 --- /dev/null +++ b/web/app/views/home/home.js @@ -0,0 +1,28 @@ +'use strict'; + +angular.module('myApp.home', ['ngRoute']) + +.config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/', { + templateUrl: '/views/home/home.html', + controller: 'HomeController' + }); +}]) + +.controller('HomeController', function ($scope, $http) { + $scope.getDashboardData = function() { + $http({ + method: 'GET', + url: '/data/all-enabled-currencies' + }). + success(function (data, status, headers, config) { + $scope.exchanges = data.data; + }). + error(function (data, status, headers, config) { + console.log('error'); + }); + }; + + $scope.getDashboardData(); + +}); \ No newline at end of file diff --git a/web/app/views/home/home_test.js b/web/app/views/home/home_test.js new file mode 100644 index 00000000..14ba79b4 --- /dev/null +++ b/web/app/views/home/home_test.js @@ -0,0 +1,16 @@ +'use strict'; + +describe('myApp.view1 module', function() { + + beforeEach(module('myApp.view1')); + + describe('view1 controller', function(){ + + it('should ....', inject(function($controller) { + //spec body + var view1Ctrl = $controller('View1Ctrl'); + expect(view1Ctrl).toBeDefined(); + })); + + }); +}); \ No newline at end of file diff --git a/web/bower.json b/web/bower.json new file mode 100644 index 00000000..66d9d74e --- /dev/null +++ b/web/bower.json @@ -0,0 +1,15 @@ +{ + "name": "angular-seed", + "description": "A starter project for AngularJS", + "version": "0.0.0", + "homepage": "https://github.com/angular/angular-seed", + "license": "MIT", + "private": true, + "dependencies": { + "angular": "~1.5.0", + "angular-route": "~1.5.0", + "angular-loader": "~1.5.0", + "angular-mocks": "~1.5.0", + "html5-boilerplate": "^5.3.0" + } +} diff --git a/web/dashboard-contact.html b/web/dashboard-contact.html deleted file mode 100644 index c66f754a..00000000 --- a/web/dashboard-contact.html +++ /dev/null @@ -1,49 +0,0 @@ -{{template "header" .}} - - -
-
- -
-

Contact Us

- -

Contact us @ https://github.com/thrasher-/gocryptotrader

-
-

- -

-
-
-
-
-{{template "footer" .}} diff --git a/web/dashboard-marketdepth.html b/web/dashboard-marketdepth.html deleted file mode 100644 index 9171689b..00000000 --- a/web/dashboard-marketdepth.html +++ /dev/null @@ -1,89 +0,0 @@ -{{template "header" .}} - - -
-
- -
-

Current Market Depth & Analysis

- -
-
- Generic placeholder thumbnail -

Asset Allocation Pie Graph

- Currencies owned and tracked. -
-
- Generic placeholder thumbnail -

Profit/Loss/Initial Pie Graph

- Current portfolio health. -
-
- Generic placeholder thumbnail -

Exchanges Pie Graph

- Based on current daily volume and analysis. -
-
- Generic placeholder thumbnail -

Market Capitalisation Pie Graph

- In USD terms per deemed weighted currencies. -
-
- -

Tickers table

-
- - - - - - - - - - - - - - - - - - - -
ExchangeLAST - BTC/USDLAST - LTC/USDLAST - BTC/LTC
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
-
-
-
-
-{{template "footer" .}} diff --git a/web/dashboard-ordermanagement.html b/web/dashboard-ordermanagement.html deleted file mode 100644 index 66113ce1..00000000 --- a/web/dashboard-ordermanagement.html +++ /dev/null @@ -1,70 +0,0 @@ -{{template "header" .}} - - -
-
- -
-

Order Management

- -

-

Order Management Tools Go Here.

-

- -

Prior Trades

-
- - - - - - - - - - - - - - - - - - - -
DATE/TIMEEXCHANGECURRENCIES USEDPROFIT LOSS
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
-
-
-
-
-{{template "footer" .}} diff --git a/web/dashboard-reports.html b/web/dashboard-reports.html deleted file mode 100644 index ff1fe934..00000000 --- a/web/dashboard-reports.html +++ /dev/null @@ -1,89 +0,0 @@ -{{template "header" .}} - - -
-
- -
-

Reports

- -
-
- Generic placeholder thumbnail -

Asset Allocation Pie Graph

- Currencies owned and tracked. -
-
- Generic placeholder thumbnail -

Profit/Loss/Initial Pie Graph

- Current portfolio health. -
-
- Generic placeholder thumbnail -

Exchanges Pie Graph

- Based on current daily volume and analysis. -
-
- Generic placeholder thumbnail -

Market Capitalisation Pie Graph

- In USD terms per deemed weighted currencies. -
-
- -

Running Report. Subdivided in monthly/yearly/quarter

-
- - - - - - - - - - - - - - - - - - - -
Trade Execution dateTrade ExchangeTrade TypeInitial InvestmentPROFIT/LOSS
SOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERESOMETHING HERE
-
-
-
-
-{{template "footer" .}} diff --git a/web/dashboard-settings.html b/web/dashboard-settings.html deleted file mode 100644 index e0d8b562..00000000 --- a/web/dashboard-settings.html +++ /dev/null @@ -1,44 +0,0 @@ -{{template "header" .}} - - -
-
- -
-

Settings

- -

Settings Table

-
-
-
-{{template "footer" .}} diff --git a/web/e2e-tests/protractor.conf.js b/web/e2e-tests/protractor.conf.js new file mode 100644 index 00000000..13c5cb62 --- /dev/null +++ b/web/e2e-tests/protractor.conf.js @@ -0,0 +1,22 @@ +//jshint strict: false +exports.config = { + + allScriptsTimeout: 11000, + + specs: [ + '*.js' + ], + + capabilities: { + 'browserName': 'chrome' + }, + + baseUrl: 'http://localhost:8000/', + + framework: 'jasmine', + + jasmineNodeOpts: { + defaultTimeoutInterval: 30000 + } + +}; diff --git a/web/e2e-tests/scenarios.js b/web/e2e-tests/scenarios.js new file mode 100644 index 00000000..240d5f61 --- /dev/null +++ b/web/e2e-tests/scenarios.js @@ -0,0 +1,42 @@ +'use strict'; + +/* https://github.com/angular/protractor/blob/master/docs/toc.md */ + +describe('my app', function() { + + + it('should automatically redirect to /view1 when location hash/fragment is empty', function() { + browser.get('index.html'); + expect(browser.getLocationAbsUrl()).toMatch("/view1"); + }); + + + describe('view1', function() { + + beforeEach(function() { + browser.get('index.html#!/view1'); + }); + + + it('should render view1 when user navigates to /view1', function() { + expect(element.all(by.css('[ng-view] p')).first().getText()). + toMatch(/partial for view 1/); + }); + + }); + + + describe('view2', function() { + + beforeEach(function() { + browser.get('index.html#!/view2'); + }); + + + it('should render view2 when user navigates to /view2', function() { + expect(element.all(by.css('[ng-view] p')).first().getText()). + toMatch(/partial for view 2/); + }); + + }); +}); diff --git a/web/error.html b/web/error.html deleted file mode 100644 index 3a89b218..00000000 --- a/web/error.html +++ /dev/null @@ -1,37 +0,0 @@ -{{template "header" .}} - -
- -
- -
- -
-
- -
-
- -
-

ERROR

-

The error {{.Error}} has occured.

-

- -

-
- -
-
-
-
- -
- -
- -
- -{{template "footer" .}} diff --git a/web/favicon.ico b/web/favicon.ico deleted file mode 100644 index 2216206f901c754e58980769a0fd861bf5e171f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139245 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 z 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 T+c54SrXJoChVySe!)O05X}ipK diff --git a/web/footer.html b/web/footer.html deleted file mode 100644 index 37bdaade..00000000 --- a/web/footer.html +++ /dev/null @@ -1,4 +0,0 @@ -{{define "footer"}} - - -{{end}} \ No newline at end of file diff --git a/web/glorious-web/Server.js b/web/glorious-web/Server.js deleted file mode 100644 index f72cf164..00000000 --- a/web/glorious-web/Server.js +++ /dev/null @@ -1,39 +0,0 @@ -var express = require('express'); -var routes = require('./routes'); -var app = express(); - -// set the view engine to ejs -app.set('view engine', 'ejs'); - -// use res.render to load up an ejs view file - -// index page -app.get('/', function(req, res) { - res.render('pages/index', { - }); -}); - -// setting page -app.get('/settings', function(req, res) { - res.render('pages/settings', { - }); -}); - -// about page -app.get('/about', function(req, res) { - res.render('pages/about'); -}); - - -app.get('/data/all-enabled-currencies', function (req, res) { - request({ - url :'http://localhost:9050/exchanges/enabled/latest/all' - },function(err, resp, body){ - res.send(body); - }) - -}); - -app.listen(80, function(){ - console.log('CORS-enabled web server listening on port 80'); -}); \ No newline at end of file diff --git a/web/glorious-web/controllers.js b/web/glorious-web/controllers.js deleted file mode 100644 index ea84afdc..00000000 --- a/web/glorious-web/controllers.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -/* Controllers */ - -angular.module('gocryptoweb.controllers', []). - controller('HomeController', function ($scope, $http) { - $scope.working = true; - $http({ - method: 'GET', - url: '/data/all-enabled-currencies' - }). - success(function (data, status, headers, config) { - $scope.exchanges = data.data.exchanges; - }). - error(function (data, status, headers, config) { - console.log('error'); - }); - - }). - controller('MyCtrl1', function ($scope) { - // write Ctrl here - - }); diff --git a/web/glorious-web/index.js b/web/glorious-web/index.js deleted file mode 100644 index 218da370..00000000 --- a/web/glorious-web/index.js +++ /dev/null @@ -1,9 +0,0 @@ - -exports.index = function(req, res){ - res.render('index'); -}; - -exports.partials = function (req, res) { - var name = req.params.name; - res.render('partials/' + name); -}; \ No newline at end of file diff --git a/web/glorious-web/package.json b/web/glorious-web/package.json deleted file mode 100644 index 6ed86901..00000000 --- a/web/glorious-web/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name" : "gocryptotrader-website", - "version" : "0.0.1", - "scripts" : { - "start" : "node Server.js" - }, - "dependencies" : { - "express" : "latest", - "ejs":"latest" - } -} diff --git a/web/glorious-web/routes/index.js b/web/glorious-web/routes/index.js deleted file mode 100644 index c2d652f5..00000000 --- a/web/glorious-web/routes/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.index = function(req, res){ - res.render('layout'); -}; \ No newline at end of file diff --git a/web/glorious-web/views/pages/index.ejs b/web/glorious-web/views/pages/index.ejs deleted file mode 100644 index ff23f2f1..00000000 --- a/web/glorious-web/views/pages/index.ejs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - <% include ../partials/head %> - - - -
- <% include ../partials/header %> -
- -
-
- - {{working}} -
-
- -
- <% include ../partials/footer %> -
- - - \ No newline at end of file diff --git a/web/glorious-web/views/pages/settings.ejs b/web/glorious-web/views/pages/settings.ejs deleted file mode 100644 index 582d5c3a..00000000 --- a/web/glorious-web/views/pages/settings.ejs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - <% include ../partials/head %> - - - -
- <% include ../partials/header %> -
- -
-
-

Settings

-

This will be a visiual interface to edit the config file

-

Things like enabling exchanges, currencies and setting excahnge account settings

-
-
- -
- <% include ../partials/footer %> -
- - - \ No newline at end of file diff --git a/web/glorious-web/views/partials/footer.ejs b/web/glorious-web/views/partials/footer.ejs deleted file mode 100644 index 55675996..00000000 --- a/web/glorious-web/views/partials/footer.ejs +++ /dev/null @@ -1,3 +0,0 @@ - - -

Copyright 2016 GoCrypto Trader

\ No newline at end of file diff --git a/web/glorious-web/views/partials/head.ejs b/web/glorious-web/views/partials/head.ejs deleted file mode 100644 index 00ba9698..00000000 --- a/web/glorious-web/views/partials/head.ejs +++ /dev/null @@ -1,11 +0,0 @@ - - - -GoCrpto Trader - - - - - - - \ No newline at end of file diff --git a/web/glorious-web/views/partials/header.ejs b/web/glorious-web/views/partials/header.ejs deleted file mode 100644 index ce5d59b8..00000000 --- a/web/glorious-web/views/partials/header.ejs +++ /dev/null @@ -1,21 +0,0 @@ - - - \ No newline at end of file diff --git a/web/header.html b/web/header.html deleted file mode 100644 index 18cf5c47..00000000 --- a/web/header.html +++ /dev/null @@ -1,19 +0,0 @@ -{{define "header"}} - - - - - {{.Title}} - - - - - - - - {{.StaticStylesheet}} - - - - -{{end}} diff --git a/web/index.html b/web/index.html deleted file mode 100644 index f58206dc..00000000 --- a/web/index.html +++ /dev/null @@ -1,37 +0,0 @@ -{{template "header" .}} - -
- -
- -
- -
-
- -
-
- -
-

Template Trading Platform v0.0

-

Login Test

-

- Login Test Page -

-
- -
-
-
-
- -
- -
- -
- -{{template "footer" .}} diff --git a/web/karma.conf.js b/web/karma.conf.js new file mode 100644 index 00000000..7271e9fe --- /dev/null +++ b/web/karma.conf.js @@ -0,0 +1,34 @@ +//jshint strict: false +module.exports = function(config) { + config.set({ + + basePath: './app', + + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-route/angular-route.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'components/**/*.js', + 'view*/**/*.js' + ], + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine', + 'karma-junit-reporter' + ], + + junitReporter: { + outputFile: 'test_out/unit.xml', + suite: 'unit' + } + + }); +}; diff --git a/web/login.html b/web/login.html deleted file mode 100644 index 699df545..00000000 --- a/web/login.html +++ /dev/null @@ -1,6 +0,0 @@ -{{template "header" .}} -

- This is the login page.... -

-goto dashboard -{{template "footer" .}} diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..883369d4 --- /dev/null +++ b/web/package.json @@ -0,0 +1,38 @@ +{ + "name": "angular-seed", + "private": true, + "version": "0.0.0", + "description": "A starter project for AngularJS", + "repository": "https://github.com/angular/angular-seed", + "license": "MIT", + "devDependencies": { + "bower": "^1.7.7", + "http-server": "^0.9.0", + "jasmine-core": "^2.4.1", + "karma": "^0.13.22", + "karma-chrome-launcher": "^0.2.3", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.8", + "karma-junit-reporter": "^0.4.1", + "protractor": "^3.2.2", + "express" : "latest" + }, + "scripts": { + "postinstall": "bower install", + + "prestart": "npm install", + "start": "node server.js", + + "pretest": "npm install", + "test": "karma start karma.conf.js", + "test-single-run": "karma start karma.conf.js --single-run", + + "preupdate-webdriver": "npm install", + "update-webdriver": "webdriver-manager update", + + "preprotractor": "npm run update-webdriver", + "protractor": "protractor e2e-tests/protractor.conf.js", + + "update-index-async": "node -e \"var fs=require('fs'),indexFile='app/index-async.html',loaderFile='app/bower_components/angular-loader/angular-loader.min.js',loaderText=fs.readFileSync(loaderFile,'utf-8').split(/sourceMappingURL=angular-loader.min.js.map/).join('sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map'),indexText=fs.readFileSync(indexFile,'utf-8').split(/\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/).join('//@@NG_LOADER_START@@\\n'+loaderText+' //@@NG_LOADER_END@@');fs.writeFileSync(indexFile,indexText);\"" + } +} diff --git a/web/server.js b/web/server.js new file mode 100644 index 00000000..7f2bf597 --- /dev/null +++ b/web/server.js @@ -0,0 +1,27 @@ +var express = require('express') + , app = express(); +var request = require('request'); +var path = __dirname + '/app/'; + +app.use("/bower_components", express.static(path + '/bower_components')); + +app.get("/",function(req,res){ + res.sendFile(path + "index.html"); +}); + +app.use("/", express.static(path + '/')); + +app.get('/data/all-enabled-currencies', function (req, res) { + request({ + url :'http://localhost:9050/exchanges/enabled/latest/all' + },function(err, resp, body){ + res.send(body); + }) + +}); + + + +app.listen(80, function(){ + console.log('CORS-enabled web server listening on port 80'); +}); \ No newline at end of file diff --git a/web/static/css/cover.css b/web/static/css/cover.css deleted file mode 100644 index f4d789e9..00000000 --- a/web/static/css/cover.css +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Globals - */ - -/* Links */ -a, -a:focus, -a:hover { - color: #fff; -} - -/* Custom default button */ -.btn-default, -.btn-default:hover, -.btn-default:focus { - color: #333; - text-shadow: none; /* Prevent inheritence from `body` */ - background-color: #fff; - border: 1px solid #fff; -} - - -/* - * Base structure - */ - -html, -body { - height: 100%; - background-color: #333; -} -body { - color: #fff; - text-align: center; - text-shadow: 0 1px 3px rgba(0,0,0,.5); -} - -/* Extra markup and styles for table-esque vertical and horizontal centering */ -.site-wrapper { - display: table; - width: 100%; - height: 100%; /* For at least Firefox */ - min-height: 100%; - -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5); - box-shadow: inset 0 0 100px rgba(0,0,0,.5); -} -.site-wrapper-inner { - display: table-cell; - vertical-align: top; -} -.cover-container { - margin-right: auto; - margin-left: auto; -} - -/* Padding for spacing */ -.inner { - padding: 30px; -} - - -/* - * Header - */ -.masthead-brand { - margin-top: 10px; - margin-bottom: 10px; -} - -.masthead-nav > li { - display: inline-block; -} -.masthead-nav > li + li { - margin-left: 20px; -} -.masthead-nav > li > a { - padding-right: 0; - padding-left: 0; - font-size: 16px; - font-weight: bold; - color: #fff; /* IE8 proofing */ - color: rgba(255,255,255,.75); - border-bottom: 2px solid transparent; -} -.masthead-nav > li > a:hover, -.masthead-nav > li > a:focus { - background-color: transparent; - border-bottom-color: #a9a9a9; - border-bottom-color: rgba(255,255,255,.25); -} -.masthead-nav > .active > a, -.masthead-nav > .active > a:hover, -.masthead-nav > .active > a:focus { - color: #fff; - border-bottom-color: #fff; -} - -@media (min-width: 768px) { - .masthead-brand { - float: left; - } - .masthead-nav { - float: right; - } -} - - -/* - * Cover - */ - -.cover { - padding: 0 20px; -} -.cover .btn-lg { - padding: 10px 20px; - font-weight: bold; -} - - -/* - * Footer - */ - -.mastfoot { - color: #999; /* IE8 proofing */ - color: rgba(255,255,255,.5); -} - - -/* - * Affix and center - */ - -@media (min-width: 768px) { - /* Pull out the header and footer */ - .masthead { - position: fixed; - top: 0; - } - .mastfoot { - position: fixed; - bottom: 0; - } - /* Start the vertical centering */ - .site-wrapper-inner { - vertical-align: middle; - } - /* Handle the widths */ - .masthead, - .mastfoot, - .cover-container { - width: 100%; /* Must be percentage or pixels for horizontal alignment */ - } -} - -@media (min-width: 992px) { - .masthead, - .mastfoot, - .cover-container { - width: 700px; - } -} \ No newline at end of file diff --git a/web/static/css/dashboard.css b/web/static/css/dashboard.css deleted file mode 100644 index de315748..00000000 --- a/web/static/css/dashboard.css +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Base structure - */ - -/* Move down content because we have a fixed navbar that is 50px tall */ -body { - padding-top: 50px; -} - - -/* - * Global add-ons - */ - -.sub-header { - padding-bottom: 10px; - border-bottom: 1px solid #eee; -} - -/* - * Top navigation - * Hide default border to remove 1px line. - */ -.navbar-fixed-top { - border: 0; -} - -/* - * Sidebar - */ - -/* Hide for mobile, show later */ -.sidebar { - display: none; -} -@media (min-width: 768px) { - .sidebar { - position: fixed; - top: 51px; - bottom: 0; - left: 0; - z-index: 1000; - display: block; - padding: 20px; - overflow-x: hidden; - overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ - background-color: #f5f5f5; - border-right: 1px solid #eee; - } -} - -/* Sidebar navigation */ -.nav-sidebar { - margin-right: -21px; /* 20px padding + 1px border */ - margin-bottom: 20px; - margin-left: -20px; -} -.nav-sidebar > li > a { - padding-right: 20px; - padding-left: 20px; -} -.nav-sidebar > .active > a, -.nav-sidebar > .active > a:hover, -.nav-sidebar > .active > a:focus { - color: #fff; - background-color: #428bca; -} - - -/* - * Main content - */ - -.main { - padding: 20px; -} -@media (min-width: 768px) { - .main { - padding-right: 40px; - padding-left: 40px; - } -} -.main .page-header { - margin-top: 0; -} - - -/* - * Placeholder dashboard ideas - */ - -.placeholders { - margin-bottom: 30px; - text-align: center; -} -.placeholders h4 { - margin-bottom: 0; -} -.placeholder { - margin-bottom: 20px; -} -.placeholder img { - display: inline-block; - border-radius: 50%; -} \ No newline at end of file From 458030d1b12068cd726bf6b0c01e822a9d6fa906 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 26 Jul 2016 22:05:29 +1000 Subject: [PATCH 24/24] Fixes issue with extra argument --- poloniexwebsocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poloniexwebsocket.go b/poloniexwebsocket.go index 59e76f47..ba93bbbb 100644 --- a/poloniexwebsocket.go +++ b/poloniexwebsocket.go @@ -133,7 +133,7 @@ func PoloniexOnDepthOrTrade(args []interface{}, kwargs map[string]interface{}) { func (p *Poloniex) WebsocketClient() { for p.Enabled && p.Websocket { - c, err := turnpike.NewWebsocketClient(turnpike.JSON, POLONIEX_WEBSOCKET_ADDRESS, nil) + c, err := turnpike.NewWebsocketClient(turnpike.JSON, POLONIEX_WEBSOCKET_ADDRESS) if err != nil { log.Printf("%s Unable to connect to Websocket. Error: %s\n", p.GetName(), err) continue