From d898a595e9bac0ac8008ac3296fbb93ef1d3aba8 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Mon, 28 Nov 2022 15:13:43 +0200 Subject: [PATCH 1/8] Add solution --- coolfacts/cmd/coolfacts_server/server.go | 64 ++++++++++++++---------- coolfacts/coolfact/fact.go | 3 +- coolfacts/coolfact/service.go | 32 ++++++++---- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/coolfacts/cmd/coolfacts_server/server.go b/coolfacts/cmd/coolfacts_server/server.go index 140e336..497614b 100644 --- a/coolfacts/cmd/coolfacts_server/server.go +++ b/coolfacts/cmd/coolfacts_server/server.go @@ -6,31 +6,34 @@ import ( "log" "net/http" "strings" + + "github.com/FTBpro/go-workshop/coolfacts/coolfact" ) type FactsService interface { - // TODO: add methods declerations - // 1. getFacts - returns a slice of fact.Fact and an error + GetFacts() ([]coolfact.Fact, error) } type server struct { - // TODO: add factsService field + factsService FactsService } -func NewServer() *server { - // TODO: returns an initializes server with the factsService - return &server{} +func NewServer(factsService FactsService) *server { + return &server{ + factsService: factsService, + } } func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Println("incoming request", r.Method, r.URL.Path) - // TODO: add case for GET /facts, that will call to `HandleGetFacts` switch r.Method { case http.MethodGet: switch strings.ToLower(r.URL.Path) { case "/ping": s.HandlePing(w) + case "/facts": + s.HandleGetFacts(w) default: err := fmt.Errorf("path %q wasn't found", r.URL.Path) s.HandleNotFound(w, err) @@ -41,6 +44,17 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +func (s *server) HandlePing(w http.ResponseWriter) { + log.Println("Handling Ping ...") + + w.WriteHeader(http.StatusOK) + + if _, err := fmt.Fprint(w, "PONG"); err != nil { + fmt.Printf("ERROR writing to ResponseWriter: %s\n", err) + return + } +} + func (s *server) HandleGetFacts(w http.ResponseWriter) { log.Println("Handling getFact ...") @@ -50,29 +64,27 @@ func (s *server) HandleGetFacts(w http.ResponseWriter) { return } - // TODO: - // 1. format the facts to a json response - // 2. write status 200 - // 3. set content type application/json - // 4. write json response: - // { - // "facts": [ - // { - // "id": "..." - // "description": "..." - // }, - // ... - // ] -} + // we first format the facts to map[string]interface. + formattedFacts := make([]map[string]interface{}, len(facts)) + for i, coolFact := range facts { + formattedFacts[i] = map[string]interface{}{ + "image": coolFact.Image, + "description": coolFact.Description, + } + } -func (s *server) HandlePing(w http.ResponseWriter) { - log.Println("Handling Ping ...") + response := map[string]interface{}{ + "facts": formattedFacts, + } + // write status and content-type + // status must be written before the body w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") - if _, err := fmt.Fprint(w, "PONG"); err != nil { - fmt.Printf("ERROR writing to ResponseWriter: %s\n", err) - return + // write the body. We use json encoding + if err := json.NewEncoder(w).Encode(response); err != nil { + fmt.Printf("HandleGetFacts ERROR writing response: %s", err) } } diff --git a/coolfacts/coolfact/fact.go b/coolfacts/coolfact/fact.go index 6836f08..53369b0 100644 --- a/coolfacts/coolfact/fact.go +++ b/coolfacts/coolfact/fact.go @@ -1,5 +1,6 @@ package coolfact type Fact struct { - // TODO: add fields for the entity fact: description and an image (strings), and createdAt (time.Time) + Image string + Description string } diff --git a/coolfacts/coolfact/service.go b/coolfacts/coolfact/service.go index 389c309..051e3aa 100644 --- a/coolfacts/coolfact/service.go +++ b/coolfacts/coolfact/service.go @@ -1,18 +1,28 @@ -package coolfact +package inmem -type Repository interface { - // TODO: add functions decleration - // - getFacts. Returns a slice of Fact and an error -} +import ( + "github.com/FTBpro/go-workshop/coolfacts/coolfact" +) -type service struct { - // TODO: add field factsRepo +type factsRepo struct { + facts []coolfact.Fact } -func NewService(factsRepo Repository) *service { - // TODO: init a new service with factsRepo +func NewFactsRepository() *factsRepo { + return &factsRepo{ + facts: []coolfact.Fact{ + { + Image: "https://images2.minutemediacdn.com/image/upload/v1556645500/shape/cover/entertainment/D5aliXvWsAEcYoK-fe997566220c082b98030508e654948e.jpg", + Description: "Did you know sonic is a hedgehog?!", + }, + { + Image: "https://images2.minutemediacdn.com/image/upload/v1556641470/shape/cover/entertainment/uncropped-Screen-Shot-2019-04-30-at-122411-PM-3b804f143c543dfab4b75c81833bed1b.jpg", + Description: "You won't believe what happened to Arya!", + }, + }, + } } -func (s *service) GetFacts() ([]Fact, error) { - // TODO: implement getFacts, using the factsRepo +func (r *factsRepo) GetFacts() ([]coolfact.Fact, error) { + return r.facts, nil } From 45b065e937f3223ac564e4b375ece3fa8c828ee1 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Mon, 28 Nov 2022 15:27:05 +0200 Subject: [PATCH 2/8] aaaaa --- coolfacts/coolfact/service.go | 38 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/coolfacts/coolfact/service.go b/coolfacts/coolfact/service.go index 051e3aa..15711ff 100644 --- a/coolfacts/coolfact/service.go +++ b/coolfacts/coolfact/service.go @@ -1,28 +1,26 @@ -package inmem +package coolfact -import ( - "github.com/FTBpro/go-workshop/coolfacts/coolfact" -) +import "fmt" -type factsRepo struct { - facts []coolfact.Fact +type Repository interface { + GetFacts() ([]Fact, error) } -func NewFactsRepository() *factsRepo { - return &factsRepo{ - facts: []coolfact.Fact{ - { - Image: "https://images2.minutemediacdn.com/image/upload/v1556645500/shape/cover/entertainment/D5aliXvWsAEcYoK-fe997566220c082b98030508e654948e.jpg", - Description: "Did you know sonic is a hedgehog?!", - }, - { - Image: "https://images2.minutemediacdn.com/image/upload/v1556641470/shape/cover/entertainment/uncropped-Screen-Shot-2019-04-30-at-122411-PM-3b804f143c543dfab4b75c81833bed1b.jpg", - Description: "You won't believe what happened to Arya!", - }, - }, +type service struct { + factsRepo Repository +} + +func NewService(factsRepo Repository) *service { + return &service{ + factsRepo: factsRepo, } } -func (r *factsRepo) GetFacts() ([]coolfact.Fact, error) { - return r.facts, nil +func (s *service) GetFacts() ([]Fact, error) { + facts, err := s.factsRepo.GetFacts() + if err != nil { + return nil, fmt.Errorf("factsService.GetFacts: %w", err) + } + + return facts, nil } From 2ba4e989b69ef2d5ac38615d4db828f93ff5ce3e Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Mon, 28 Nov 2022 15:29:23 +0200 Subject: [PATCH 3/8] aaaaa #2 --- coolfacts/inmem/factsrepo.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/coolfacts/inmem/factsrepo.go b/coolfacts/inmem/factsrepo.go index f763df4..051e3aa 100644 --- a/coolfacts/inmem/factsrepo.go +++ b/coolfacts/inmem/factsrepo.go @@ -9,9 +9,20 @@ type factsRepo struct { } func NewFactsRepository() *factsRepo { - // TODO: init facts repo + return &factsRepo{ + facts: []coolfact.Fact{ + { + Image: "https://images2.minutemediacdn.com/image/upload/v1556645500/shape/cover/entertainment/D5aliXvWsAEcYoK-fe997566220c082b98030508e654948e.jpg", + Description: "Did you know sonic is a hedgehog?!", + }, + { + Image: "https://images2.minutemediacdn.com/image/upload/v1556641470/shape/cover/entertainment/uncropped-Screen-Shot-2019-04-30-at-122411-PM-3b804f143c543dfab4b75c81833bed1b.jpg", + Description: "You won't believe what happened to Arya!", + }, + }, + } } func (r *factsRepo) GetFacts() ([]coolfact.Fact, error) { - // TODO: implement + return r.facts, nil } From 29bf178b4761f1877fba295c048c1f226edaeab9 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Fri, 2 Dec 2022 15:16:16 +0200 Subject: [PATCH 4/8] change to topic --- coolfacts/inmem/factsrepo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coolfacts/inmem/factsrepo.go b/coolfacts/inmem/factsrepo.go index 051e3aa..6c89ab0 100644 --- a/coolfacts/inmem/factsrepo.go +++ b/coolfacts/inmem/factsrepo.go @@ -12,11 +12,11 @@ func NewFactsRepository() *factsRepo { return &factsRepo{ facts: []coolfact.Fact{ { - Image: "https://images2.minutemediacdn.com/image/upload/v1556645500/shape/cover/entertainment/D5aliXvWsAEcYoK-fe997566220c082b98030508e654948e.jpg", + Topic: "Games", Description: "Did you know sonic is a hedgehog?!", }, { - Image: "https://images2.minutemediacdn.com/image/upload/v1556641470/shape/cover/entertainment/uncropped-Screen-Shot-2019-04-30-at-122411-PM-3b804f143c543dfab4b75c81833bed1b.jpg", + Topic: "TV", Description: "You won't believe what happened to Arya!", }, }, From db815ae6f0734c696870f692331c0b4dcc3676ce Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Fri, 2 Dec 2022 19:43:37 +0200 Subject: [PATCH 5/8] add arg --- coolfacts/cmd/coolfacts_server/main.go | 15 ++++++++++++++- coolfacts/inmem/factsrepo.go | 13 ++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/coolfacts/cmd/coolfacts_server/main.go b/coolfacts/cmd/coolfacts_server/main.go index cf9ec1a..13d8ca4 100644 --- a/coolfacts/cmd/coolfacts_server/main.go +++ b/coolfacts/cmd/coolfacts_server/main.go @@ -12,7 +12,7 @@ import ( func main() { fmt.Println("Hello, Server!") - factsRepo := inmem.NewFactsRepository() + factsRepo := inmem.NewFactsRepository(seedFacts()...) service := coolfact.NewService(factsRepo) server := NewServer(service) @@ -22,3 +22,16 @@ func main() { panic(fmt.Errorf("server crashed! err: %w", err)) } } + +func seedFacts() []coolfact.Fact { + return []coolfact.Fact{ + { + Topic: "Games", + Description: "Did you know sonic is a hedgehog?!", + }, + { + Topic: "TV", + Description: "You won't believe what happened to Arya!", + }, + } +} diff --git a/coolfacts/inmem/factsrepo.go b/coolfacts/inmem/factsrepo.go index 6c89ab0..f879c95 100644 --- a/coolfacts/inmem/factsrepo.go +++ b/coolfacts/inmem/factsrepo.go @@ -8,18 +8,9 @@ type factsRepo struct { facts []coolfact.Fact } -func NewFactsRepository() *factsRepo { +func NewFactsRepository(facts ...coolfact.Fact) *factsRepo { return &factsRepo{ - facts: []coolfact.Fact{ - { - Topic: "Games", - Description: "Did you know sonic is a hedgehog?!", - }, - { - Topic: "TV", - Description: "You won't believe what happened to Arya!", - }, - }, + facts: facts, } } From 4e0794525e2c7e0886fbf6a0875949871d8a04e8 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Fri, 2 Dec 2022 19:53:00 +0200 Subject: [PATCH 6/8] add doc --- coolfacts/docs/ex3-get-facts.md | 50 ++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/coolfacts/docs/ex3-get-facts.md b/coolfacts/docs/ex3-get-facts.md index 5c43469..7d5c5d7 100644 --- a/coolfacts/docs/ex3-get-facts.md +++ b/coolfacts/docs/ex3-get-facts.md @@ -63,7 +63,7 @@ func main() { fmt.Println("Hello, Server!") // new initializations - factsRepo := inmem.NewFactsRepository() + factsRepo := inmem.NewFactsRepository(seedFacts()...) service := coolfact.NewService(factsRepo) server := NewServer(service) @@ -73,6 +73,19 @@ func main() { panic(fmt.Errorf("server crashed! err: %w", err)) } } + +func seedFacts() []coolfact.Fact { + return []coolfact.Fact{ + { + Topic: "Games", + Description: "Did you know sonic is a hedgehog?!", + }, + { + Topic: "TV", + Description: "You won't believe what happened to Arya!", + }, + } +} ``` We first can notice the new imports: ```go @@ -81,7 +94,22 @@ We first can notice the new imports: ``` we are importing packages from our own module. We have the module path and then the path to the package we want to import. The module path is `github.com/FTBpro/go-workshop/coolfacts`. -In the next lines we initializing the repo, service and the server. A pacage name is only the last param, and each type in Go has a name composed from the package name and the type identifier. For example, we call `inmem.NewFactsRepository()`. The package name is `inmem`, and the type identifier is `inmem.NewFactsRepository()` +In the next lines we initializing the repo, service and the server. A pacage name is only the last param, and each type in Go has a name composed from the package name and the type identifier. For example, we call `inmem.NewFactsRepository`. The package name is `inmem`, and the type identifier is `inmem.NewFactsRepository()`. + +You can note that we are initialzing the repo with facts: `inmem.NewFactsRepository(seedFacts()...)`. The `inmem.NewFactsRepository` signature is: +```go +func NewFactsRepository(facts ...coolfact.Fact) *factsRepo { +``` +The 3 dots indicates that it is a _variadic_ function. It can be called with any number of trailing arguments. You might already notices that `fmt.Println` is a variadic function: +```go +func Println(a ...any) (n int, err error) { +``` + +If you already have multiple args in a slice, you apply them to a variadic function using `func(slice...)`. This is why we call the function like this: +```go +inmem.NewFactsRepository(seedFacts()...) +``` + What you will have to complete is: ## Step 1 - package `coolfact` @@ -106,8 +134,7 @@ In service.go we have the service which will handle the "BL" for the application ## Step 2 - Package `inmem` ### file `inmem/factsrepo.go`: Here we will implement the facts repository. Currently, only with functionality to return facts. -- Implement `NewFactsRepository` - - Just so we will have initial data, initialize the repo with two facts. +- Implement `NewFactsRepository`. - Implement method `GetFacts`. ## Step 3 - `cmd/server.go` @@ -185,20 +212,11 @@ func (s *service) GetFacts() ([]Fact, error) { In GetFacts the service calls the repo. Notice that if there is an error, the service wraps it and adding some context, so we will have friendlier message. ## Step 2 - The repo -We initialize the `factsRepo` with a slice including 2 cool facts +We initialize the `factsRepo` with the arg slice: ```go -func NewFactsRepository() *factsRepo { +func NewFactsRepository(facts ...coolfact.Fact) *factsRepo { return &factsRepo{ - facts: []coolfact.Fact{ - { - Image: "https://images2.minutemediacdn.com/image/upload/v1556645500/shape/cover/entertainment/D5aliXvWsAEcYoK-fe997566220c082b98030508e654948e.jpg", - Description: "Did you know sonic is a hedgehog?!", - }, - { - Image: "https://images2.minutemediacdn.com/image/upload/v1556641470/shape/cover/entertainment/uncropped-Screen-Shot-2019-04-30-at-122411-PM-3b804f143c543dfab4b75c81833bed1b.jpg", - Description: "You won't believe what happened to Arya!", - }, - }, + facts: facts, } } From 08e9e4636a42d92934a75dba4ef5d8fcd53f14f7 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Sat, 10 Dec 2022 12:38:45 +0200 Subject: [PATCH 7/8] extract --- coolfacts/cmd/coolfacts_server/server.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/coolfacts/cmd/coolfacts_server/server.go b/coolfacts/cmd/coolfacts_server/server.go index a6c0157..6a2c439 100644 --- a/coolfacts/cmd/coolfacts_server/server.go +++ b/coolfacts/cmd/coolfacts_server/server.go @@ -71,9 +71,7 @@ func (s *server) HandleGetFacts(w http.ResponseWriter, _ *http.Request) { } } - response := map[string]interface{}{ - "facts": formattedFacts, - } + response := s.formatGetFactsResponse(facts) // write status and content-type // status must be written before the body @@ -114,3 +112,17 @@ func (s *server) HandleError(w http.ResponseWriter, err error) { fmt.Printf("HandleGetFacts ERROR writing response: %s", err) } } + +func (s *server) formatGetFactsResponse(facts []coolfact.Fact) map[string]interface{} { + formattedFacts := make([]map[string]interface{}, len(facts)) + for i, coolFact := range facts { + formattedFacts[i] = map[string]interface{}{ + "topic": coolFact.Topic, + "description": coolFact.Description, + } + } + + return map[string]interface{}{ + "facts": formattedFacts, + } +} From 9f9dbf626039a1bb1b9e7bea1ac2d52d819e34c7 Mon Sep 17 00:00:00 2001 From: Oren Rosen Date: Sat, 10 Dec 2022 18:13:28 +0200 Subject: [PATCH 8/8] doc --- coolfacts/docs/ex3-get-facts.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coolfacts/docs/ex3-get-facts.md b/coolfacts/docs/ex3-get-facts.md index b1930c1..15b3337 100644 --- a/coolfacts/docs/ex3-get-facts.md +++ b/coolfacts/docs/ex3-get-facts.md @@ -264,10 +264,11 @@ As mentioned before, you will implement a new API for the `server`, what you wil - Write status 200. - Set "content-type" header to "application/json". -### Building and Running +## Building and Running If everything is implemented well, this is what the final result should look like when running the program: -![factsgif](https://user-images.githubusercontent.com/5252381/204143457-6eaf59d3-6c52-4fbb-8d2a-19d22436cbd8.gif) +![v3-get-facts](https://user-images.githubusercontent.com/5252381/206864380-9890158e-c841-4d9a-b772-cd779388e58e.gif) + # Full walkthrough In the following section you fill find a full walkthrough. Use it in case you are stuck.