From d50bc73081c2b329f06d58a0821af48ed1a5891e Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 07:56:04 +1000 Subject: [PATCH 1/6] First working implementation --- client/draws/consume.go | 9 ++ client/draws/data.go | 160 +++++++++++++++++++++++++++ client/draws/live-cached.go | 58 ++++++++++ client/draws/live.go | 59 ++++++++++ client/main.go | 39 +++++++ client/server/server.go | 82 ++++++++++++++ client/templates/by-key.html.tmpl | 10 ++ client/templates/error-500.html.tmpl | 12 ++ client/templates/list.html.tmpl | 14 +++ 9 files changed, 443 insertions(+) create mode 100644 client/draws/consume.go create mode 100644 client/draws/data.go create mode 100644 client/draws/live-cached.go create mode 100644 client/draws/live.go create mode 100644 client/main.go create mode 100644 client/server/server.go create mode 100644 client/templates/by-key.html.tmpl create mode 100644 client/templates/error-500.html.tmpl create mode 100644 client/templates/list.html.tmpl diff --git a/client/draws/consume.go b/client/draws/consume.go new file mode 100644 index 0000000..eecd916 --- /dev/null +++ b/client/draws/consume.go @@ -0,0 +1,9 @@ +package draws + +import "github.com/fromz/codetest/client/results" + +// Fetcher fetches results +type Fetcher interface { + GetAll() ([]Result, error) + ByKey(key string) (Result, error) +} diff --git a/client/draws/data.go b/client/draws/data.go new file mode 100644 index 0000000..4ce7067 --- /dev/null +++ b/client/draws/data.go @@ -0,0 +1,160 @@ +package draws + +// Response is the response from the feed +type Reponse struct { + Results []Result `json:"result"` + Messages []interface{} `json:"messages"` +} + +// Result is the key result +type Result struct { + Type string `json:"type"` + Key string `json:"key"` + Name string `json:"name"` + Autoplayable string `json:"autoplayable"` + GameTypes []GameTypes `json:"game_types,omitempty"` + Draws []Draws `json:"draws,omitempty"` + Days []Day `json:"days,omitempty"` + Addons []interface{} `json:"addons,omitempty"` + QuickpickSizes []int `json:"quickpick_sizes,omitempty"` + Lottery Lottery `json:"lottery,omitempty"` + Draw Draw `json:"draw,omitempty"` +} + +// Draw is the type of draw +type Draw struct { + Name string `json:"name"` + Description string `json:"description"` + DrawNumber int `json:"draw_number"` + DrawStop string `json:"draw_stop"` + DrawDate string `json:"draw_date"` + Prize Prize `json:"prize"` + Offers []Offer `json:"offers"` + TermsAndConditionsURL string `json:"terms_and_conditions_url"` +} + +// Offer is the offer details +type Offer struct { + Name string `json:"name"` + Key string `json:"key"` + NumTickets int `json:"num_tickets"` + Price Price `json:"price"` + PricePerTicket Price `json:"price_per_ticket"` + Ribbon string `json:"ribbon"` + BonusPrize interface{} `json:"bonus_prize"` +} + +// Price is the cost +type Price struct { + Amount string `json:"amount"` + Currency string `json:"currency"` +} + +// Prize is the details of the prize +type Prize struct { + Type string `json:"type"` + CardTitle string `json:"card_title"` + Name string `json:"name"` + Description string `json:"description"` + Content PrizeContent `json:"content"` + Value Price `json:"value"` + ValueIsExact bool `json:"value_is_exact"` + HeroImage interface{} `json:"hero_image"` + CarouselImages []string `json:"carousel_images"` + FeatureDrawImage interface{} `json:"feature_draw_image"` + EdmImage string `json:"edm_image"` +} + +// PrizeContent is for details for customer +type PrizeContent struct { + SalesPitchHeading1 string `json:"sales_pitch_heading_1"` + SalesPitchSubHeading1 string `json:"sales_pitch_sub_heading_1"` + Paragraph1 string `json:"paragraph_1"` + Paragraph2 string `json:"paragraph_2"` + Paragraph3 string `json:"paragraph_3"` + Image string `json:"image"` + SalesPitchHeading2 string `json:"sales_pitch_heading_2"` + SalesPitchSubHeading2 string `json:"sales_pitch_sub_heading_2"` + Features []string `json:"features"` +} + +// Draws is the draws available +type Draws struct { + Name interface{} `json:"name"` + Date string `json:"date"` + Stop string `json:"stop"` + DrawNo int `json:"draw_no"` + PrizePool Price `json:"prize_pool"` + JackpotImage JackpotImage `json:"jackpot_image"` +} + +// JackpotImage is the image for the jackpot +type JackpotImage struct { + ImageName string `json:"image_name"` + ImageURL string `json:"image_url"` + SvgURL string `json:"svg_url"` + ImageWidth int `json:"image_width"` + ImageHeight int `json:"image_height"` + ContentDescription string `json:"content_description"` +} + +// GameTypes is the type of games +type GameTypes struct { + Key string `json:"key"` + Name string `json:"name"` + Description string `json:"description"` + GameOffers []GameOffer `json:"game_offers"` +} + +// GameOffer is the offer for the game +type GameOffer struct { + Key string `json:"key"` + Name string `json:"name"` + Description string `json:"description"` + Price Price `json:"price"` + MinGames int `json:"min_games"` + MaxGames int `json:"max_games"` + Multiple int `json:"multiple"` + Ordered bool `json:"ordered"` + GameIncrement GameIncrement `json:"game_increment"` + EquivalentGames int `json:"equivalent_games"` + NumberSets []NumberSet `json:"number_sets"` + DisplayRange interface{} `json:"display_range"` +} + +// NumberSet is the number set +type NumberSet struct { + First int `json:"first"` + Last int `json:"last"` + Sets []Set `json:"sets"` +} + +// Set is the set +type Set struct { + Name string `json:"name"` + Count int `json:"count"` +} + +// GameIncrement is the increment for the game +type GameIncrement struct { + Num4 int `json:"4"` +} + +// Lottery is the lottery detials +type Lottery struct { + ID string `json:"id"` + Name string `json:"name"` + Desc string `json:"desc"` + Multidraw bool `json:"multidraw"` + Type string `json:"type"` + IconURL string `json:"icon_url"` + IconWhiteURL string `json:"icon_white_url"` + PlayURL string `json:"play_url"` + LotteryID int `json:"lottery_id"` +} + +// Day is the day for the result +type Day struct { + Name string `json:"name"` + Value int `json:"value"` +} diff --git a/client/draws/live-cached.go b/client/draws/live-cached.go new file mode 100644 index 0000000..41dfc6d --- /dev/null +++ b/client/draws/live-cached.go @@ -0,0 +1,58 @@ +package draws + +import ( + "fmt" + "sync" +) + +// Cache caches another Fetchers results +type Cache struct { + sync.RWMutex + Feed Fetcher + Cache map[string]Result +} + +// Update updates the cache +func (c Cache) Update() error { + res, err := c.Feed.GetAll() + if err != nil { + return err + } + c.Lock() + for _, r := range res { + c.Cache[r.Key] = r + } + c.Unlock() + return nil +} + +// GetAll returns all Results in the cache +func (c Cache) GetAll() (res []Result, err error) { + c.RLock() + for _, r := range c.Cache { + res = append(res, r) + } + c.RUnlock() + return +} + +// ByKey returns a result by key +func (c Cache) ByKey(key string) (res Result, err error) { + c.RLock() + defer c.RUnlock() + res, ok := c.Cache[key] + if !ok { + err = fmt.Errorf("No result found for key: %s", key) + } + return +} + +// NewCachedFeed returns a cached feed fetcher +func NewCachedFeed(feed Fetcher) Fetcher { + c := Cache{ + Cache: map[string]Result{}, + Feed: feed, + } + c.Update() + return c +} diff --git a/client/draws/live.go b/client/draws/live.go new file mode 100644 index 0000000..3fa5f8b --- /dev/null +++ b/client/draws/live.go @@ -0,0 +1,59 @@ +package draws + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// ResultsLiveFeed is a fetcher that works directly on the external feed +type ResultsLiveFeed struct { + FeedServerPath string +} + +// getData fetches the data from the feed +func (r ResultsLiveFeed) getData() (jsonData Reponse, err error) { + resp, err := http.Get(r.FeedServerPath) + if err != nil { + return + } + + jsonData = Reponse{} + err = json.NewDecoder(resp.Body).Decode(&jsonData) + return +} + +// GetAll returns all results +func (r ResultsLiveFeed) GetAll() (res []Result, err error) { + jsonData, err := r.getData() + res = jsonData.Results + return +} + +// ByKey returns a Result by key +func (r ResultsLiveFeed) ByKey(key string) (res Result, err error) { + resp, err := http.Get(r.FeedServerPath) + if err != nil { + return + } + + jsonData := Reponse{} + err = json.NewDecoder(resp.Body).Decode(&jsonData) + if err != nil { + return + } + + for _, r := range jsonData.Results { + if r.Key == key { + return r, nil + } + } + return Result{}, fmt.Errorf("No result found for key: %s", key) +} + +// NewLiveFeed returns a new live feed fetcher +func NewLiveFeed(feedServerPath string) Fetcher { + return ResultsLiveFeed{ + FeedServerPath: feedServerPath, + } +} diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..bda7631 --- /dev/null +++ b/client/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/fromz/codetest/client/draws" + "github.com/fromz/codetest/client/server" + "html/template" + "net/http" + "path/filepath" +) + +func main() { + //run a web server + + templatePath := "templates" + feedServerPath := "http://127.0.0.1:80/" + bindTo := ":8081" + cacheFeed := true + + // Parse the error template in advance in case the error its attempting to display is due to templates not being found + t, err := template.ParseFiles(filepath.Join(templatePath, "error-500.html.tmpl")) + if err != nil { + return + } + + fetcher := draws.NewLiveFeed(feedServerPath) + if cacheFeed { + fetcher = draws.NewCachedFeed(fetcher) + } + + // Prepare a server and register its methods as handlers + s := server.HTML{ + Fetcher: fetcher, + TemplatePath: templatePath, + ParsedErrorTemplate: t, + } + http.HandleFunc("/", s.List) + http.HandleFunc("/key/", s.Key) + http.ListenAndServe(bindTo, nil) +} diff --git a/client/server/server.go b/client/server/server.go new file mode 100644 index 0000000..deebf62 --- /dev/null +++ b/client/server/server.go @@ -0,0 +1,82 @@ +package server + +import ( + "github.com/davecgh/go-spew/spew" + "github.com/fromz/codetest/client/draws" + "html/template" + "net/http" + "path/filepath" +) + +// HTML is the server type for HTML +type HTML struct { + Fetcher draws.Fetcher + // TODO implement a cache for parsed templates, with a goroutine monitoring either inotify refreshing the templates, or every x seconds rebuild + TemplatePath string + ParsedErrorTemplate *template.Template +} + +// ListData is the data for the List template +type ListData struct { + Results []draws.Result +} + +// List renders the list template +func (s HTML) List(w http.ResponseWriter, r *http.Request) { + res, err := s.Fetcher.GetAll() + if err != nil { + s.Error500(w, err, "") + return + } + + t, err := template.ParseFiles(filepath.Join(s.TemplatePath, "list.html.tmpl")) + if err != nil { + s.Error500(w, err, "") + return + } + + t.Execute(w, ListData{ + Results: res, + }) +} + +// KeyData is the data for the Key template +type KeyData struct { + Dump string + Result draws.Result +} + +// Key renders the Key template +func (s HTML) Key(w http.ResponseWriter, r *http.Request) { + key := r.URL.Path[len("/key/"):] + res, err := s.Fetcher.ByKey(key) + if err != nil { + s.Error500(w, err, "") + return + } + + str := spew.Sdump(res) + + t, err := template.ParseFiles(filepath.Join(s.TemplatePath, "by-key.html.tmpl")) + if err != nil { + s.Error500(w, err, "") + return + } + + t.Execute(w, KeyData{ + Dump: str, + Result: res, + }) +} + +// ErrorData is the data for the error template +type ErrorData struct { + Message string +} + +// Error500 renders the error 500 template +func (s HTML) Error500(w http.ResponseWriter, err error, msg string) { + s.ParsedErrorTemplate.Execute(w, ErrorData{ + Message: msg, + }) +} diff --git a/client/templates/by-key.html.tmpl b/client/templates/by-key.html.tmpl new file mode 100644 index 0000000..800adea --- /dev/null +++ b/client/templates/by-key.html.tmpl @@ -0,0 +1,10 @@ + + + + + {{ .Result.Name }} + + +{{ .Dump }} + + \ No newline at end of file diff --git a/client/templates/error-500.html.tmpl b/client/templates/error-500.html.tmpl new file mode 100644 index 0000000..f8a1d77 --- /dev/null +++ b/client/templates/error-500.html.tmpl @@ -0,0 +1,12 @@ + + + + + ERROR + + + +{{ .Message }} + + + \ No newline at end of file diff --git a/client/templates/list.html.tmpl b/client/templates/list.html.tmpl new file mode 100644 index 0000000..d333383 --- /dev/null +++ b/client/templates/list.html.tmpl @@ -0,0 +1,14 @@ + + + + + List of draws + + + +{{ range $key, $value := .Results }} +
  • {{ $value.Name }}
  • +{{ end }} + + + \ No newline at end of file From 3bc8ff1794bbca83286d477f9d2e88626b180b49 Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 07:58:37 +1000 Subject: [PATCH 2/6] Implemented sorting for the list template --- client/draws/consume.go | 2 -- client/server/server.go | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/draws/consume.go b/client/draws/consume.go index eecd916..0af983a 100644 --- a/client/draws/consume.go +++ b/client/draws/consume.go @@ -1,7 +1,5 @@ package draws -import "github.com/fromz/codetest/client/results" - // Fetcher fetches results type Fetcher interface { GetAll() ([]Result, error) diff --git a/client/server/server.go b/client/server/server.go index deebf62..c0c78e8 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -6,6 +6,7 @@ import ( "html/template" "net/http" "path/filepath" + "sort" ) // HTML is the server type for HTML @@ -29,6 +30,11 @@ func (s HTML) List(w http.ResponseWriter, r *http.Request) { return } + // Sort alphabetically + sort.Slice(res, func(i, j int) bool { + return res[i].Name < res[j].Name + }) + t, err := template.ParseFiles(filepath.Join(s.TemplatePath, "list.html.tmpl")) if err != nil { s.Error500(w, err, "") From 4b83acd4b87ed18a263facde28b9efb9e93fa148 Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 08:07:47 +1000 Subject: [PATCH 3/6] Bound viper flags --- client/main.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/client/main.go b/client/main.go index bda7631..81c4fac 100644 --- a/client/main.go +++ b/client/main.go @@ -3,31 +3,48 @@ package main import ( "github.com/fromz/codetest/client/draws" "github.com/fromz/codetest/client/server" + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + "github.com/spf13/viper" "html/template" "net/http" + "os" "path/filepath" ) func main() { - //run a web server + logger := logrus.Logger{} + // Viper allows us to migrate to YAML or TOML files fairly easily + pflag.String("templatePath", "templates", "The path to the templates directory") + pflag.String("feedServerPath", "http://127.0.0.1:80/", "The feed server address") + pflag.String("bindTo", ":8081", "The TCP info to bind to") + pflag.Bool("cacheFeed", true, "Whether or not to locally cache the feed") + pflag.Parse() + err := viper.BindPFlags(pflag.CommandLine) + if err != nil { + logger.Error(err) + os.Exit(1) + } - templatePath := "templates" - feedServerPath := "http://127.0.0.1:80/" - bindTo := ":8081" - cacheFeed := true + templatePath := viper.GetString("templatePath") + feedServerPath := viper.GetString("feedServerPath") + bindTo := viper.GetString("bindTo") + cacheFeed := viper.GetBool("cacheFeed") // Parse the error template in advance in case the error its attempting to display is due to templates not being found t, err := template.ParseFiles(filepath.Join(templatePath, "error-500.html.tmpl")) if err != nil { - return + logger.Error(err) + os.Exit(1) } + // Prepare a Draws fetcher for use by the HTML server fetcher := draws.NewLiveFeed(feedServerPath) if cacheFeed { fetcher = draws.NewCachedFeed(fetcher) } - // Prepare a server and register its methods as handlers + // Prepare a server and register its methods as handlers. if more complicated, I'd utilize a framework. s := server.HTML{ Fetcher: fetcher, TemplatePath: templatePath, From 06c9f5ecbc98b051ccf155ac489e023812488bc6 Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 08:14:44 +1000 Subject: [PATCH 4/6] Added a monitor to update the cache with --- client/draws/live-cached.go | 25 ++++++++++++++++++++----- client/draws/live.go | 5 ++++- client/main.go | 9 ++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/client/draws/live-cached.go b/client/draws/live-cached.go index 41dfc6d..f1fd325 100644 --- a/client/draws/live-cached.go +++ b/client/draws/live-cached.go @@ -2,14 +2,17 @@ package draws import ( "fmt" + "github.com/sirupsen/logrus" "sync" + "time" ) // Cache caches another Fetchers results type Cache struct { sync.RWMutex - Feed Fetcher - Cache map[string]Result + Feed Fetcher + Cache map[string]Result + Logger *logrus.Logger } // Update updates the cache @@ -26,6 +29,16 @@ func (c Cache) Update() error { return nil } +// Monitor ticks every 30 seconds to trigger a cache update. This could be done with a retry-backoff algorithm as well. +func (c Cache) Monitor() { + for range time.Tick(time.Second * 30) { + err := c.Update() + if err != nil { + c.Logger.Error(err) + } + } +} + // GetAll returns all Results in the cache func (c Cache) GetAll() (res []Result, err error) { c.RLock() @@ -48,11 +61,13 @@ func (c Cache) ByKey(key string) (res Result, err error) { } // NewCachedFeed returns a cached feed fetcher -func NewCachedFeed(feed Fetcher) Fetcher { +func NewCachedFeed(feed Fetcher, logger *logrus.Logger) Fetcher { c := Cache{ - Cache: map[string]Result{}, - Feed: feed, + Cache: map[string]Result{}, + Feed: feed, + Logger: logger, } c.Update() + go c.Monitor() return c } diff --git a/client/draws/live.go b/client/draws/live.go index 3fa5f8b..c31f90f 100644 --- a/client/draws/live.go +++ b/client/draws/live.go @@ -3,12 +3,14 @@ package draws import ( "encoding/json" "fmt" + "github.com/sirupsen/logrus" "net/http" ) // ResultsLiveFeed is a fetcher that works directly on the external feed type ResultsLiveFeed struct { FeedServerPath string + Logger *logrus.Logger } // getData fetches the data from the feed @@ -52,8 +54,9 @@ func (r ResultsLiveFeed) ByKey(key string) (res Result, err error) { } // NewLiveFeed returns a new live feed fetcher -func NewLiveFeed(feedServerPath string) Fetcher { +func NewLiveFeed(feedServerPath string, logger *logrus.Logger) Fetcher { return ResultsLiveFeed{ FeedServerPath: feedServerPath, + Logger: logger, } } diff --git a/client/main.go b/client/main.go index 81c4fac..e84ecc7 100644 --- a/client/main.go +++ b/client/main.go @@ -13,7 +13,9 @@ import ( ) func main() { - logger := logrus.Logger{} + // Pass a logger around for centralized logging if necessary + logger := logrus.New() + // Viper allows us to migrate to YAML or TOML files fairly easily pflag.String("templatePath", "templates", "The path to the templates directory") pflag.String("feedServerPath", "http://127.0.0.1:80/", "The feed server address") @@ -26,6 +28,7 @@ func main() { os.Exit(1) } + // Bind the passed in config to local variables templatePath := viper.GetString("templatePath") feedServerPath := viper.GetString("feedServerPath") bindTo := viper.GetString("bindTo") @@ -39,9 +42,9 @@ func main() { } // Prepare a Draws fetcher for use by the HTML server - fetcher := draws.NewLiveFeed(feedServerPath) + fetcher := draws.NewLiveFeed(feedServerPath, logger) if cacheFeed { - fetcher = draws.NewCachedFeed(fetcher) + fetcher = draws.NewCachedFeed(fetcher, logger) } // Prepare a server and register its methods as handlers. if more complicated, I'd utilize a framework. From 350b2e014ffcdba19d32d1a0a51e504bc0dd6fce Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 08:18:54 +1000 Subject: [PATCH 5/6] Some changes --- client/draws/live-cached.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/draws/live-cached.go b/client/draws/live-cached.go index f1fd325..7ccfee6 100644 --- a/client/draws/live-cached.go +++ b/client/draws/live-cached.go @@ -7,7 +7,7 @@ import ( "time" ) -// Cache caches another Fetchers results +// Cache caches another Fetchers results - if more complex data feed, or larger data set, an ARC mechanism might be useful. E.g. hashicorps type Cache struct { sync.RWMutex Feed Fetcher From b6e32171c70cc8bad63718a6fd5415710aaa4b15 Mon Sep 17 00:00:00 2001 From: Kieran McDougall Date: Thu, 21 Sep 2017 08:51:40 +1000 Subject: [PATCH 6/6] Better logging --- .idea/codetest.iml | 10 + .idea/libraries/GOPATH__codetest_.xml | 23 ++ .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 561 ++++++++++++++++++++++++++ README.md | 5 + client/main.go | 13 +- client/server/logger.go | 21 + client/server/server.go | 13 +- main.go | 1 + 11 files changed, 660 insertions(+), 8 deletions(-) create mode 100644 .idea/codetest.iml create mode 100644 .idea/libraries/GOPATH__codetest_.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 client/server/logger.go diff --git a/.idea/codetest.iml b/.idea/codetest.iml new file mode 100644 index 0000000..8240566 --- /dev/null +++ b/.idea/codetest.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/GOPATH__codetest_.xml b/.idea/libraries/GOPATH__codetest_.xml new file mode 100644 index 0000000..b65c215 --- /dev/null +++ b/.idea/libraries/GOPATH__codetest_.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e8860d2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + ApexVCS + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..34b1a90 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..510b687 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,561 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + gameO + ServeMux + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/client/server/server.go + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 24c006c..165677c 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,8 @@ ln -s `pwd`/hooks/pre-commit .git/hooks ```bash go get -v ./... && go build -v ``` + +# Running the client +Available in client/ dir. `go run main.go` will use defaults that work on most systems, otherwise arguments are available. +* `/` is a list that links to the key/ template +* `/key/RESULT.KEY` \ No newline at end of file diff --git a/client/main.go b/client/main.go index e84ecc7..f299cf1 100644 --- a/client/main.go +++ b/client/main.go @@ -48,12 +48,19 @@ func main() { } // Prepare a server and register its methods as handlers. if more complicated, I'd utilize a framework. + server.RegisterLogger(logger) s := server.HTML{ Fetcher: fetcher, TemplatePath: templatePath, ParsedErrorTemplate: t, } - http.HandleFunc("/", s.List) - http.HandleFunc("/key/", s.Key) - http.ListenAndServe(bindTo, nil) + + h := http.NewServeMux() + h.HandleFunc("/", s.List) + h.HandleFunc("/key/", s.Key) + logger.Infof("Listening on: %s", bindTo) + err = http.ListenAndServe(bindTo, server.Logger(h)) + if err != nil { + logger.WithError(err).Errorf("Error binding to: %s", bindTo) + } } diff --git a/client/server/logger.go b/client/server/logger.go new file mode 100644 index 0000000..95573ef --- /dev/null +++ b/client/server/logger.go @@ -0,0 +1,21 @@ +package server + +import ( + "github.com/sirupsen/logrus" + "net/http" +) + +var logger *logrus.Logger + +// RegisterLogger makes logrus available locally +func RegisterLogger(logrus *logrus.Logger) { + logger = logrus +} + +// Logger wrapper +func Logger(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.Infof("Serving request %s", r.URL.Path) + h.ServeHTTP(w, r) + }) +} diff --git a/client/server/server.go b/client/server/server.go index c0c78e8..6e817d0 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -3,6 +3,7 @@ package server import ( "github.com/davecgh/go-spew/spew" "github.com/fromz/codetest/client/draws" + "github.com/sirupsen/logrus" "html/template" "net/http" "path/filepath" @@ -15,6 +16,7 @@ type HTML struct { // TODO implement a cache for parsed templates, with a goroutine monitoring either inotify refreshing the templates, or every x seconds rebuild TemplatePath string ParsedErrorTemplate *template.Template + Logger *logrus.Logger } // ListData is the data for the List template @@ -26,7 +28,7 @@ type ListData struct { func (s HTML) List(w http.ResponseWriter, r *http.Request) { res, err := s.Fetcher.GetAll() if err != nil { - s.Error500(w, err, "") + s.Error500(w, r, err, "") return } @@ -37,7 +39,7 @@ func (s HTML) List(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles(filepath.Join(s.TemplatePath, "list.html.tmpl")) if err != nil { - s.Error500(w, err, "") + s.Error500(w, r, err, "") return } @@ -57,7 +59,7 @@ func (s HTML) Key(w http.ResponseWriter, r *http.Request) { key := r.URL.Path[len("/key/"):] res, err := s.Fetcher.ByKey(key) if err != nil { - s.Error500(w, err, "") + s.Error500(w, r, err, "") return } @@ -65,7 +67,7 @@ func (s HTML) Key(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles(filepath.Join(s.TemplatePath, "by-key.html.tmpl")) if err != nil { - s.Error500(w, err, "") + s.Error500(w, r, err, "") return } @@ -81,7 +83,8 @@ type ErrorData struct { } // Error500 renders the error 500 template -func (s HTML) Error500(w http.ResponseWriter, err error, msg string) { +func (s HTML) Error500(w http.ResponseWriter, r *http.Request, err error, msg string) { + s.Logger.WithError(err).Errorf("Serving URL %s", r.URL.Path) s.ParsedErrorTemplate.Execute(w, ErrorData{ Message: msg, }) diff --git a/main.go b/main.go index 1a496d3..a454ec8 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ func baseHandler(w http.ResponseWriter, r *http.Request) { w.Write(fileContents) } +// This probably needs work too func main() { r := mux.NewRouter() log.Println("Waiting...")