Skip to content

Commit

Permalink
API following REST patterns v2
Browse files Browse the repository at this point in the history
  • Loading branch information
henrod committed Mar 3, 2017
1 parent b96532c commit 73fe9ec
Show file tree
Hide file tree
Showing 13 changed files with 487 additions and 234 deletions.
40 changes: 26 additions & 14 deletions api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http"

"github.com/Sirupsen/logrus"
"github.com/asaskevich/govalidator"
"github.com/gorilla/mux"
newrelic "github.com/newrelic/go-agent"
"github.com/spf13/viper"
Expand Down Expand Up @@ -73,60 +74,71 @@ func (a *App) getRouter() *mux.Router {
&VersionMiddleware{},
)).Methods("GET").Name("game")

r.Handle("/games", Chain(
r.HandleFunc("/games/{id}", Chain(
&GameHandler{App: a, Method: "upsert"},
&MetricsReporterMiddleware{App: a},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewParamKeyMiddleware(a, func(id string) bool { return govalidator.Matches(id, "^[^-][a-z0-9-]*$") }),
NewValidationMiddleware(func() interface{} { return &models.Game{} }),
)).Methods("PUT").Name("game")
).ServeHTTP).Methods("PUT").Name("game")

r.Handle("/offer-templates", Chain(
r.Handle("/templates", Chain(
&OfferTemplateHandler{App: a, Method: "list"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
)).Methods("GET").Name("offer_templates")

r.Handle("/offer-templates", Chain(
r.Handle("/templates", Chain(
&OfferTemplateHandler{App: a, Method: "insert"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewValidationMiddleware(func() interface{} { return &models.OfferTemplate{} }),
)).Methods("POST").Name("offer_templates")

r.Handle("/offer-templates/set-enabled", Chain(
&OfferTemplateHandler{App: a, Method: "set-enabled"},
r.Handle("/templates/{id}/enable", Chain(
&OfferTemplateHandler{App: a, Method: "enable"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewParamKeyMiddleware(a, govalidator.IsUUIDv4),
)).Methods("PUT").Name("offer_templates")

r.Handle("/templates/{id}/disable", Chain(
&OfferTemplateHandler{App: a, Method: "disable"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
&ValidationMiddleware{GetPayload: func() interface{} { return &models.OfferTemplateToUpdate{} }},
NewParamKeyMiddleware(a, govalidator.IsUUIDv4),
)).Methods("PUT").Name("offer_templates")

r.Handle("/offers", Chain(
&OfferRequestHandler{App: a, Method: "get-offers"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
)).Methods("GET").Name("offer")
)).Methods("GET").Name("offers")

r.Handle("/offer/claim", Chain(
&OfferRequestHandler{App: a, Method: "claim-offer"},
r.Handle("/offers/{id}/claim", Chain(
&OfferRequestHandler{App: a, Method: "claim"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewParamKeyMiddleware(a, govalidator.IsUUIDv4),
NewValidationMiddleware(func() interface{} { return &models.OfferToUpdate{} }),
)).Methods("PUT").Name("offer")
)).Methods("PUT").Name("offers")

r.Handle("/offer/last-seen-at", Chain(
&OfferRequestHandler{App: a, Method: "update-offer-last-seen-at"},
r.HandleFunc("/offers/{id}/impressions", Chain(
&OfferRequestHandler{App: a, Method: "impressions"},
&NewRelicMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewParamKeyMiddleware(a, govalidator.IsUUIDv4),
NewValidationMiddleware(func() interface{} { return &models.OfferToUpdate{} }),
)).Methods("PUT").Name("offer")
).ServeHTTP).Methods("POST").Name("offers")

return r
}
Expand Down
52 changes: 32 additions & 20 deletions api/game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package api_test

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"

Expand All @@ -27,15 +28,14 @@ var _ = Describe("Game Handler", func() {
recorder = httptest.NewRecorder()
})

Describe("PUT /games", func() {
Describe("PUT /games/{id}", func() {
It("should return status code of 200", func() {
id := uuid.NewV4().String()
gameReader := JSONFor(JSON{
"ID": id,
"Name": "Game Awesome Name",
"BundleID": "com.topfreegames.example",
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand All @@ -47,10 +47,11 @@ var _ = Describe("Game Handler", func() {
})

It("should return status code of 422 if missing parameter", func() {
id := uuid.NewV4().String()
gameReader := JSONFor(JSON{
"Name": "Game Awesome Name",
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand All @@ -60,7 +61,7 @@ var _ = Describe("Game Handler", func() {
Expect(err).NotTo(HaveOccurred())
Expect(obj["code"]).To(Equal("OFF-002"))
Expect(obj["error"]).To(Equal("ValidationFailedError"))
Expect(obj["description"]).To(Equal("ID: non zero value required;BundleID: non zero value required;"))
Expect(obj["description"]).To(Equal("BundleID: non zero value required;"))
})

It("should return status code of 422 if invalid name", func() {
Expand All @@ -69,12 +70,12 @@ var _ = Describe("Game Handler", func() {
reallyBigName += reallyBigName
}

id := "game-id"
gameReader := JSONFor(JSON{
"ID": "game-id",
"Name": reallyBigName,
"BundleID": "com.topfreegames.example",
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand All @@ -93,12 +94,12 @@ var _ = Describe("Game Handler", func() {
reallyBigName += reallyBigName
}

id := "game-id"
gameReader := JSONFor(JSON{
"ID": "game-id",
"Name": uuid.NewV4().String(),
"BundleID": reallyBigName,
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand All @@ -113,15 +114,14 @@ var _ = Describe("Game Handler", func() {
})

It("should return status code of 422 if invalid id", func() {
id := "abc123!@#xyz456"
id := "abc123!@$xyz456"
name := "Game Awesome Name"
bundleID := "com.tfg.example"
gameReader := JSONFor(JSON{
"ID": id,
"Name": name,
"BundleID": bundleID,
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand All @@ -131,35 +131,47 @@ var _ = Describe("Game Handler", func() {
Expect(err).NotTo(HaveOccurred())
Expect(obj["code"]).To(BeEquivalentTo("OFF-002"))
Expect(obj["error"]).To(Equal("ValidationFailedError"))
Expect(obj["description"]).To(ContainSubstring("ID: abc123!@#xyz456 does not validate as matches(^[^-][a-z0-9-]*$);"))
Expect(obj["description"]).To(ContainSubstring("ID: abc123!@$xyz456 does not validate;"))
})

It("should return status code of 422 if empty id", func() {
id := ""
It("should return status code of 422 if invalid id, even if is passed in body", func() {
id := "abc123!@$xyz456"
name := "Game Awesome Name"
bundleID := "com.tfg.example"
gameReader := JSONFor(JSON{
"ID": id,
"Name": name,
"BundleID": bundleID,
})
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expect(recorder.Code).To(Equal(http.StatusUnprocessableEntity))

var obj map[string]interface{}
err := json.Unmarshal([]byte(recorder.Body.String()), &obj)
Expect(err).NotTo(HaveOccurred())
Expect(obj["code"]).To(BeEquivalentTo("OFF-002"))
Expect(obj["error"]).To(Equal("ValidationFailedError"))
Expect(obj["description"]).To(ContainSubstring("ID: non zero value required;"))
Expect(obj["description"]).To(ContainSubstring("ID: abc123!@$xyz456 does not validate;"))
})

It("should return status code of 404 if id is not passed", func() {
gameReader := JSONFor(JSON{
"Name": "Game Awesome Name",
"BundleID": "com.topfreegames.example",
})
request, _ := http.NewRequest("PUT", "/games/", gameReader)

app.Router.ServeHTTP(recorder, request)

Expect(recorder.Code).To(Equal(http.StatusNotFound))
Expect(recorder.Body.String()).To(Equal("404 page not found\n"))
})

It("should return status code of 500 if some error occurred", func() {
id := uuid.NewV4().String()
gameReader := JSONFor(JSON{
"ID": uuid.NewV4().String(),
"Name": "Game Awesome Break",
"BundleID": "com.topfreegames.example",
})
Expand All @@ -169,7 +181,7 @@ var _ = Describe("Game Handler", func() {
Expect(err).NotTo(HaveOccurred())
app.DB = db
app.DB.(*runner.DB).DB.Close() // make DB connection unavailable
request, _ := http.NewRequest("PUT", "/games", gameReader)
request, _ := http.NewRequest("PUT", fmt.Sprintf("/games/%s", id), gameReader)

app.Router.ServeHTTP(recorder, request)

Expand Down
10 changes: 6 additions & 4 deletions api/offer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ func (h *OfferRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
switch h.Method {
case "get-offers":
h.getOffers(w, r)
case "claim-offer":
case "claim":
h.claimOffer(w, r)
case "update-offer-last-seen-at":
case "impressions":
h.updateOfferLastSeenAt(w, r)
}
}
Expand Down Expand Up @@ -69,9 +69,10 @@ func (h *OfferRequestHandler) getOffers(w http.ResponseWriter, r *http.Request)
func (h *OfferRequestHandler) claimOffer(w http.ResponseWriter, r *http.Request) {
mr := metricsReporterFromCtx(r.Context())
offer := offerToUpdateFromCtx(r.Context())
offerID := paramKeyFromContext(r.Context())
currentTime := h.App.Clock.GetTime()

contents, alreadyClaimed, err := models.ClaimOffer(h.App.DB, offer.ID, offer.PlayerID, offer.GameID, currentTime, mr)
contents, alreadyClaimed, err := models.ClaimOffer(h.App.DB, offerID, offer.PlayerID, offer.GameID, currentTime, mr)

if err != nil {
if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
Expand All @@ -95,9 +96,10 @@ func (h *OfferRequestHandler) claimOffer(w http.ResponseWriter, r *http.Request)
func (h *OfferRequestHandler) updateOfferLastSeenAt(w http.ResponseWriter, r *http.Request) {
mr := metricsReporterFromCtx(r.Context())
offer := offerToUpdateFromCtx(r.Context())
offerID := paramKeyFromContext(r.Context())
currentTime := h.App.Clock.GetTime()

err := models.UpdateOfferLastSeenAt(h.App.DB, offer.ID, offer.PlayerID, offer.GameID, currentTime, mr)
err := models.UpdateOfferLastSeenAt(h.App.DB, offerID, offer.PlayerID, offer.GameID, currentTime, mr)

if err != nil {
if modelNotFound, ok := err.(*e.ModelNotFoundError); ok {
Expand Down
18 changes: 9 additions & 9 deletions api/offer_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ func (g *OfferTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
case "insert":
g.insertOfferTemplate(w, r)
return
case "set-enabled":
g.setEnabledOfferTemplate(w, r)
case "enable":
g.setEnabledOfferTemplate(w, r, true)
case "disable":
g.setEnabledOfferTemplate(w, r, false)
return
case "list":
g.list(w, r)
Expand All @@ -41,8 +43,6 @@ func (g *OfferTemplateHandler) insertOfferTemplate(w http.ResponseWriter, r *htt
mr := metricsReporterFromCtx(r.Context())
ot := offerTemplateFromCtx(r.Context())

fmt.Printf("OFFER TEMPLATE %#v", ot)

var err error
err = mr.WithSegment(models.SegmentModel, func() error {
ot, err = models.InsertOfferTemplate(g.App.DB, ot, mr)
Expand Down Expand Up @@ -70,16 +70,16 @@ func (g *OfferTemplateHandler) insertOfferTemplate(w http.ResponseWriter, r *htt
return
}

WriteBytes(w, http.StatusOK, bytesRes)
WriteBytes(w, http.StatusCreated, bytesRes)
}

func (g *OfferTemplateHandler) setEnabledOfferTemplate(w http.ResponseWriter, r *http.Request) {
func (g *OfferTemplateHandler) setEnabledOfferTemplate(w http.ResponseWriter, r *http.Request, enable bool) {
mr := metricsReporterFromCtx(r.Context())
ot := offerTemplateToUpdateFromCtx(r.Context())
offerTemplateID := paramKeyFromContext(r.Context())

var err error
err = mr.WithSegment(models.SegmentModel, func() error {
return models.SetEnabledOfferTemplate(g.App.DB, ot.ID, ot.Enabled, mr)
return models.SetEnabledOfferTemplate(g.App.DB, offerTemplateID, enable, mr)
})

if err != nil {
Expand All @@ -91,7 +91,7 @@ func (g *OfferTemplateHandler) setEnabledOfferTemplate(w http.ResponseWriter, r
return
}

Write(w, http.StatusOK, ot.ID)
Write(w, http.StatusOK, offerTemplateID)
}

func (g *OfferTemplateHandler) list(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading

0 comments on commit 73fe9ec

Please sign in to comment.