Skip to content

Commit fb6f85d

Browse files
committed
Adding service
1 parent 95a1694 commit fb6f85d

File tree

8 files changed

+167
-32
lines changed

8 files changed

+167
-32
lines changed

cmd/gopherapi/main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"log"
77
"net/http"
88

9+
"github.com/friendsofgo/gopherapi/pkg/adding"
10+
"github.com/friendsofgo/gopherapi/pkg/fetching"
11+
912
"github.com/friendsofgo/gopherapi/pkg/storage/inmem"
1013

1114
sample "github.com/friendsofgo/gopherapi/cmd/sample-data"
@@ -18,13 +21,16 @@ func main() {
1821
withData := flag.Bool("withData", false, "initialize the api with some gophers")
1922
flag.Parse()
2023

21-
var gophers map[string]*gopher.Gopher
24+
var gophers map[string]gopher.Gopher
2225
if *withData {
2326
gophers = sample.Gophers
2427
}
2528

26-
repo := inmem.NewGopherRepository(gophers)
27-
s := server.New(repo)
29+
repo := inmem.NewRepository(gophers)
30+
fetching := fetching.NewService(repo)
31+
adding := adding.NewService(repo)
32+
33+
s := server.New(fetching, adding)
2834

2935
fmt.Println("The gopher server is on tap now: http://localhost:8080")
3036
log.Fatal(http.ListenAndServe(":8080", s.Router()))

cmd/sample-data/data.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@ import (
44
gopher "github.com/friendsofgo/gopherapi/pkg"
55
)
66

7-
var Gophers = map[string]*gopher.Gopher{
8-
"01D3XZ3ZHCP3KG9VT4FGAD8KDR": &gopher.Gopher{
7+
var Gophers = map[string]gopher.Gopher{
8+
"01D3XZ3ZHCP3KG9VT4FGAD8KDR": gopher.Gopher{
99
ID: "01D3XZ3ZHCP3KG9VT4FGAD8KDR",
1010
Name: "Jenny",
1111
Age: 18,
1212
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/0ceb2c10fc0c30575c18ff1defa1ffd41501bc62.png",
1313
},
14-
"01D3XZ7CN92AKS9HAPSZ4D5DP9": &gopher.Gopher{
14+
"01D3XZ7CN92AKS9HAPSZ4D5DP9": gopher.Gopher{
1515
ID: "01D3XZ7CN92AKS9HAPSZ4D5DP9",
1616
Name: "Billy",
1717
Age: 24,
1818
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/13c7d425111a501600db8587b52bb292836c5bee.png",
1919
},
20-
"01D3XZ89NFJZ9QT2DHVD462AC2": &gopher.Gopher{
20+
"01D3XZ89NFJZ9QT2DHVD462AC2": gopher.Gopher{
2121
ID: "01D3XZ89NFJZ9QT2DHVD462AC2",
2222
Name: "Rainbow",
2323
Age: 48,
2424
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/b9e8d637c91c089fd56d7b159825fc9089377118.png",
2525
},
26-
"01D3XZ8JXHTDA6XY05EVJVE9Z2": &gopher.Gopher{
26+
"01D3XZ8JXHTDA6XY05EVJVE9Z2": gopher.Gopher{
2727
ID: "01D3XZ8JXHTDA6XY05EVJVE9Z2",
2828
Name: "Bjorn",
2929
Age: 32,

pkg/adding/service.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package adding
2+
3+
import (
4+
gopher "github.com/friendsofgo/gopherapi/pkg"
5+
)
6+
7+
// Service provides adding operations.
8+
type Service interface {
9+
AddGopher(ID, name, image string, age int) error
10+
}
11+
12+
type service struct {
13+
repository gopher.Repository
14+
}
15+
16+
// NewService creates an adding service with the necessary dependencies
17+
func NewService(repository gopher.Repository) Service {
18+
return &service{repository}
19+
}
20+
21+
// AddGopher adds the given gopher to storage
22+
func (s *service) AddGopher(ID, name, image string, age int) error {
23+
g := gopher.New(ID, name, image, age)
24+
return s.repository.CreateGopher(g)
25+
}

pkg/fetching/service.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package fetching
2+
3+
import gopher "github.com/friendsofgo/gopherapi/pkg"
4+
5+
// Service provides fetching operations.
6+
type Service interface {
7+
FetchGophers() ([]gopher.Gopher, error)
8+
FetchGopherByID(ID string) (*gopher.Gopher, error)
9+
}
10+
11+
type service struct {
12+
repository gopher.Repository
13+
}
14+
15+
// NewService creates a fetching service with the necessary dependencies
16+
func NewService(repository gopher.Repository) Service {
17+
return &service{repository}
18+
}
19+
20+
// FetchGophers returns all gophers
21+
func (s *service) FetchGophers() ([]gopher.Gopher, error) {
22+
return s.repository.FetchGophers()
23+
}
24+
25+
// FetchGopherByID returns a gopher
26+
func (s *service) FetchGopherByID(ID string) (*gopher.Gopher, error) {
27+
return s.repository.FetchGopherByID(ID)
28+
}

pkg/gopher.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,26 @@ type Gopher struct {
88
Age int `json:"age,omitempty"`
99
}
1010

11-
// Repository provides access to the gopher storage
12-
type GopherRepository interface {
11+
// New creates a gopher
12+
func New(ID, name, image string, age int) *Gopher {
13+
return &Gopher{
14+
ID: ID,
15+
Name: name,
16+
Image: image,
17+
Age: age,
18+
}
19+
}
20+
21+
//Repository provides access to the gopher storage
22+
type Repository interface {
1323
// CreateGopher saves a given gopher
1424
CreateGopher(g *Gopher) error
1525
// FetchGophers return all gophers saved in storage
16-
FetchGophers() ([]*Gopher, error)
26+
FetchGophers() ([]Gopher, error)
1727
// DeleteGopher remove gopher with given ID
1828
DeleteGopher(ID string) error
1929
// UpdateGopher modify gopher with given ID and given new data
20-
UpdateGopher(ID string, g *Gopher) error
30+
UpdateGopher(ID string, g Gopher) error
2131
// FetchGopherByID returns the gopher with given ID
2232
FetchGopherByID(ID string) (*Gopher, error)
2333
}

pkg/server/api.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,34 @@ import (
44
"encoding/json"
55
"net/http"
66

7-
gopher "github.com/friendsofgo/gopherapi/pkg"
7+
"github.com/friendsofgo/gopherapi/pkg/adding"
8+
"github.com/friendsofgo/gopherapi/pkg/fetching"
89

910
"github.com/gorilla/mux"
1011
)
1112

1213
type api struct {
13-
router http.Handler
14-
repository gopher.GopherRepository
14+
router http.Handler
15+
fetching fetching.Service
16+
adding adding.Service
1517
}
1618

1719
// Server representation of gopher server
1820
type Server interface {
1921
Router() http.Handler
2022
FetchGophers(w http.ResponseWriter, r *http.Request)
2123
FetchGopher(w http.ResponseWriter, r *http.Request)
24+
AddGopher(w http.ResponseWriter, r *http.Request)
2225
}
2326

2427
// New initialize the server
25-
func New(repo gopher.GopherRepository) Server {
26-
a := &api{repository: repo}
28+
func New(fS fetching.Service, aS adding.Service) Server {
29+
a := &api{fetching: fS, adding: aS}
2730

2831
r := mux.NewRouter()
2932
r.HandleFunc("/gophers", a.FetchGophers).Methods(http.MethodGet)
3033
r.HandleFunc("/gophers/{ID:[a-zA-Z0-9_]+}", a.FetchGopher).Methods(http.MethodGet)
34+
r.HandleFunc("/gophers", a.AddGopher).Methods(http.MethodPost)
3135

3236
a.router = r
3337
return a
@@ -39,7 +43,7 @@ func (a *api) Router() http.Handler {
3943

4044
// FetchGophers return a list of all gophers
4145
func (a *api) FetchGophers(w http.ResponseWriter, r *http.Request) {
42-
gophers, _ := a.repository.FetchGophers()
46+
gophers, _ := a.fetching.FetchGophers()
4347

4448
w.Header().Set("Content-Type", "application/json")
4549
json.NewEncoder(w).Encode(gophers)
@@ -48,7 +52,7 @@ func (a *api) FetchGophers(w http.ResponseWriter, r *http.Request) {
4852
// FetchGopher return a gopher by ID
4953
func (a *api) FetchGopher(w http.ResponseWriter, r *http.Request) {
5054
vars := mux.Vars(r)
51-
gopher, err := a.repository.FetchGopherByID(vars["ID"])
55+
gopher, err := a.fetching.FetchGopherByID(vars["ID"])
5256
w.Header().Set("Content-Type", "application/json")
5357
if err != nil {
5458
w.WriteHeader(http.StatusNotFound) // We use not found for simplicity
@@ -58,3 +62,32 @@ func (a *api) FetchGopher(w http.ResponseWriter, r *http.Request) {
5862

5963
json.NewEncoder(w).Encode(gopher)
6064
}
65+
66+
type addGopherRequest struct {
67+
ID string `json:"ID"`
68+
Name string `json:"name"`
69+
Image string `json:"image"`
70+
Age int `json:"age"`
71+
}
72+
73+
// AddGopher save a gopher
74+
func (a *api) AddGopher(w http.ResponseWriter, r *http.Request) {
75+
decoder := json.NewDecoder(r.Body)
76+
77+
var g addGopherRequest
78+
err := decoder.Decode(&g)
79+
80+
if err != nil {
81+
w.WriteHeader(http.StatusInternalServerError)
82+
json.NewEncoder(w).Encode("Error unmarshalling request body")
83+
return
84+
}
85+
86+
if err := a.adding.AddGopher(g.ID, g.Name, g.Image, g.Age); err != nil {
87+
w.WriteHeader(http.StatusInternalServerError)
88+
json.NewEncoder(w).Encode("Can't create a gopher")
89+
return
90+
}
91+
92+
w.WriteHeader(http.StatusCreated)
93+
}

pkg/server/api_test.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package server
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"io/ioutil"
78
"net/http"
89
"net/http/httptest"
910
"testing"
1011

12+
"github.com/friendsofgo/gopherapi/pkg/adding"
13+
"github.com/friendsofgo/gopherapi/pkg/fetching"
14+
1115
sample "github.com/friendsofgo/gopherapi/cmd/sample-data"
1216
gopher "github.com/friendsofgo/gopherapi/pkg"
1317
"github.com/friendsofgo/gopherapi/pkg/storage/inmem"
@@ -19,8 +23,7 @@ func TestFetchGophers(t *testing.T) {
1923
t.Fatalf("could not created request: %v", err)
2024
}
2125

22-
repo := inmem.NewGopherRepository(sample.Gophers)
23-
s := New(repo)
26+
s := buildServer()
2427

2528
rec := httptest.NewRecorder()
2629

@@ -36,7 +39,7 @@ func TestFetchGophers(t *testing.T) {
3639
t.Fatalf("could not read response: %v", err)
3740
}
3841

39-
var got []*gopher.Gopher
42+
var got []gopher.Gopher
4043
err = json.Unmarshal(b, &got)
4144
if err != nil {
4245
t.Fatalf("could not unmarshall response %v", err)
@@ -69,8 +72,7 @@ func TestFetchGopher(t *testing.T) {
6972
t.Fatalf("could not created request: %v", err)
7073
}
7174

72-
repo := inmem.NewGopherRepository(sample.Gophers)
73-
s := New(repo)
75+
s := buildServer()
7476

7577
rec := httptest.NewRecorder()
7678
s.Router().ServeHTTP(rec, req)
@@ -102,6 +104,29 @@ func TestFetchGopher(t *testing.T) {
102104

103105
}
104106

107+
func TestAddGopher(t *testing.T) {
108+
bodyJSON := []byte(`{
109+
"ID": "01DCBP0R0MSNZY975ZQF1DCQCH",
110+
"name": "Eustaqio",
111+
"image": "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/f73f25d73c06cc81c482821391a85c4b7dd34ba5.png",
112+
"age": 99
113+
}`)
114+
req, err := http.NewRequest("POST", "/gophers", bytes.NewBuffer(bodyJSON))
115+
if err != nil {
116+
t.Fatalf("could not created request: %v", err)
117+
}
118+
s := buildServer()
119+
rec := httptest.NewRecorder()
120+
121+
s.AddGopher(rec, req)
122+
res := rec.Result()
123+
defer res.Body.Close()
124+
125+
if res.StatusCode != http.StatusCreated {
126+
t.Errorf("expected %d, got: %d", http.StatusCreated, res.StatusCode)
127+
}
128+
}
129+
105130
func gopherSample() *gopher.Gopher {
106131
return &gopher.Gopher{
107132
ID: "01D3XZ3ZHCP3KG9VT4FGAD8KDR",
@@ -110,3 +135,10 @@ func gopherSample() *gopher.Gopher {
110135
Image: "https://storage.googleapis.com/gopherizeme.appspot.com/gophers/0ceb2c10fc0c30575c18ff1defa1ffd41501bc62.png",
111136
}
112137
}
138+
139+
func buildServer() Server {
140+
repo := inmem.NewRepository(sample.Gophers)
141+
fetching := fetching.NewService(repo)
142+
adding := adding.NewService(repo)
143+
return New(fetching, adding)
144+
}

pkg/storage/inmem/repository.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import (
99

1010
type gopherRepository struct {
1111
mtx sync.RWMutex
12-
gophers map[string]*gopher.Gopher
12+
gophers map[string]gopher.Gopher
1313
}
1414

15-
func NewGopherRepository(gophers map[string]*gopher.Gopher) gopher.GopherRepository {
15+
// NewRepository creates a inmem repository with the necessary dependencies
16+
func NewRepository(gophers map[string]gopher.Gopher) gopher.Repository {
1617
if gophers == nil {
17-
gophers = make(map[string]*gopher.Gopher)
18+
gophers = make(map[string]gopher.Gopher)
1819
}
1920

2021
return &gopherRepository{
@@ -28,14 +29,14 @@ func (r *gopherRepository) CreateGopher(g *gopher.Gopher) error {
2829
if err := r.checkIfExists(g.ID); err != nil {
2930
return err
3031
}
31-
r.gophers[g.ID] = g
32+
r.gophers[g.ID] = *g
3233
return nil
3334
}
3435

35-
func (r *gopherRepository) FetchGophers() ([]*gopher.Gopher, error) {
36+
func (r *gopherRepository) FetchGophers() ([]gopher.Gopher, error) {
3637
r.mtx.Lock()
3738
defer r.mtx.Unlock()
38-
values := make([]*gopher.Gopher, 0, len(r.gophers))
39+
values := make([]gopher.Gopher, 0, len(r.gophers))
3940
for _, value := range r.gophers {
4041
values = append(values, value)
4142
}
@@ -50,7 +51,7 @@ func (r *gopherRepository) DeleteGopher(ID string) error {
5051
return nil
5152
}
5253

53-
func (r *gopherRepository) UpdateGopher(ID string, g *gopher.Gopher) error {
54+
func (r *gopherRepository) UpdateGopher(ID string, g gopher.Gopher) error {
5455
r.mtx.Lock()
5556
defer r.mtx.Unlock()
5657
r.gophers[ID] = g
@@ -63,7 +64,7 @@ func (r *gopherRepository) FetchGopherByID(ID string) (*gopher.Gopher, error) {
6364

6465
for _, v := range r.gophers {
6566
if v.ID == ID {
66-
return v, nil
67+
return &v, nil
6768
}
6869
}
6970

0 commit comments

Comments
 (0)