Skip to content

Commit

Permalink
Update offer templates
Browse files Browse the repository at this point in the history
  • Loading branch information
henrod committed Mar 8, 2017
1 parent 70c5bc3 commit 1c9c8ef
Show file tree
Hide file tree
Showing 19 changed files with 457 additions and 152 deletions.
2 changes: 1 addition & 1 deletion api/api_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var _ = BeforeEach(func() {
})

var _ = AfterEach(func() {
err := app.DB.(*runner.Tx).Rollback()
err := app.DB.(*runner.Tx).AutoRollback()
Expect(err).NotTo(HaveOccurred())
app.DB = db
})
Expand Down
13 changes: 12 additions & 1 deletion api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,26 @@ func (a *App) getRouter() *mux.Router {
&VersionMiddleware{},
)).Methods("GET").Name("offer_templates")

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

r.HandleFunc("/templates/{id}/update", Chain(
&OfferTemplateHandler{App: a, Method: "update"},
&NewRelicMiddleware{App: a},
&AuthMiddleware{App: a},
&LoggingMiddleware{App: a},
&VersionMiddleware{},
NewParamKeyMiddleware(a, govalidator.IsUUIDv4),
NewValidationMiddleware(func() interface{} { return &models.OfferTemplate{} }),
).ServeHTTP).Methods("POST").Name("offer_templates")

r.Handle("/templates/{id}/enable", Chain(
&OfferTemplateHandler{App: a, Method: "enable"},
&NewRelicMiddleware{App: a},
Expand Down
2 changes: 1 addition & 1 deletion api/game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ var _ = Describe("Game Handler", func() {
var obj []map[string]interface{}
err := json.Unmarshal([]byte(recorder.Body.String()), &obj)
Expect(err).NotTo(HaveOccurred())
Expect(obj).To(HaveLen(5))
Expect(obj).To(HaveLen(7))
for i := 0; i < len(obj); i++ {
Expect(obj[i]).To(HaveKey("id"))
Expect(obj[i]).To(HaveKey("name"))
Expand Down
15 changes: 12 additions & 3 deletions api/offer_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type OfferTemplateHandler struct {
func (g *OfferTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch g.Method {
case "insert":
g.insertOfferTemplate(w, r)
g.insert(w, r, true)
return
case "enable":
g.setEnabledOfferTemplate(w, r, true)
Expand All @@ -37,12 +37,17 @@ func (g *OfferTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
case "list":
g.list(w, r)
return
case "update":
g.insert(w, r, false)
return
}
}

func (g *OfferTemplateHandler) insertOfferTemplate(w http.ResponseWriter, r *http.Request) {
func (g *OfferTemplateHandler) insert(w http.ResponseWriter, r *http.Request, onlyInsert bool) {
mr := metricsReporterFromCtx(r.Context())
ot := offerTemplateFromCtx(r.Context())
offerTemplateKey := paramKeyFromContext(r.Context())
ot.Key = offerTemplateKey
userEmail := userEmailFromContext(r.Context())

logger := g.App.Logger.WithFields(logrus.Fields{
Expand All @@ -53,8 +58,9 @@ func (g *OfferTemplateHandler) insertOfferTemplate(w http.ResponseWriter, r *htt
})

var err error

err = mr.WithSegment(models.SegmentModel, func() error {
ot, err = models.InsertOfferTemplate(g.App.DB, ot, mr)
ot, err = models.InsertOfferTemplate(g.App.DB, ot, onlyInsert, mr)
return err
})

Expand Down Expand Up @@ -85,6 +91,9 @@ func (g *OfferTemplateHandler) insertOfferTemplate(w http.ResponseWriter, r *htt
WriteBytes(w, http.StatusCreated, bytesRes)
}

func (g *OfferTemplateHandler) update(w http.ResponseWriter, r *http.Request) {
}

func (g *OfferTemplateHandler) setEnabledOfferTemplate(w http.ResponseWriter, r *http.Request, enable bool) {
mr := metricsReporterFromCtx(r.Context())
offerTemplateID := paramKeyFromContext(r.Context())
Expand Down
204 changes: 147 additions & 57 deletions api/offer_template_test.go

Large diffs are not rendered by default.

114 changes: 111 additions & 3 deletions docs/source/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,123 @@ Offers API
## Offer Template Routes

### Insert Offer Template
`POST /templates`
`POST /templates/:key/insert`

Insert a new Offer Template into database.
Insert a new Offer Template into database. key is an `uuidv4`. If trying to insert when another offer template exists with the same key and is enabled, this returns a conflict error.

* Payload
```
{
"name": [string], // required, 255 characters max
"productId": [string], // required, 255 characters max
"gameId": [string], // required, matches ^[^-][a-z0-9-]*$
"contents": [json], // required
"placement": [string], // required, 255 characters max
"period": { // required
"every": [string], // required
"max": [int] // required
},
"frequency": { // required
"every": [string], // required
"max": [int] // required
},
"trigger": { // required
"from": [int], // required
"to": [int] // required
},
"metadata": [json] // optional
}
```

* Field Descriptions
- **name**: Prettier game identifier to show on UI.
- **key**: Identifies an offer template. It is common between the templates versions, meaning it keeps the same when an offer is updated.
- **productId**: Identifier of the item to be bought on PlayStore or AppStore.
- **gameId**: ID of the game this template was made for (must exist on Games table on DB).
- **contents**: What the offer provides (ex.: { "gem": 5, "gold": 100 }).
- **metadata**: Any information the Front wants to access later.
- **period**: Enable player to buy offer every x times, at most y times. <ul><li>every: decimal number with unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"</li><li>max: maximum number of times this offer can be bought by the player</li></ul>If "every" is an empty string, then the offer can be bought max times with no time restriction. If "max" is 0, then the offer can be bought infinite times with time restriction. They can't be "" and 0 at the same time.
- **frequency**: Enable player to see offer on UI x/unit of time, at most y times. <ul><li>every: decimal number with unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"</li><li>max: maximum number of times this offer can be seen by the player</li></ul>If "every" is an empty string, then the offer can be seen max times with no time restriction. If "max" is 0, then the offer can be seen infinite times with time restriction. They can't be "" and 0 at the same time.
- **trigger**: Time when the offer is available.
- **enabled**: True if the offer is enabled.
- **placement**: Where the offer is shown in the UI.

* Success Response
* Code: `200`
* Content:
```
{
"id": [uuidv4], // offer template unique identifier
"key": [uuidv4],
"name": [string],
"productId": [string],
"gameId": [string],
"contents": [json],
"metadata": [json],
"placement": [string],
"period": {
"every": [string],
"max": [int]
},
"frequency": {
"every": [string],
"max": [int]
},
"trigger": {
"from": [int],
"to": [int]
},
"enabled": true // created templates are enabled by default
}
```

* Error response

It will return an error if the request has missing or invalid arguments

* Code: `422`
* Content:
```
{
"error": [string], // error
"code": [string], // error code
"description": [string] // error description
}
```

It will return an error if there is another offer template with the same key that is enabled

* Code: `409`
* Content:
```
{
"error": [string], // error
"code": [string], // error code
"description": [string] // error description
}
```

It will return an error if the query on db (insert) failed

* Code: `500`
* Content:
```
{
"error": [string], // error
"code": [string], // error code
"description": [string] // error description
}
```

### Update Offer Template
`POST /templates/:key/update`

Update Offer Template into database. key is an `uuidv4`. If key doesn't exist or there is a disabled offer template with given key, it inserts. If there is an enabled template with the given key, this template becomes disabled and the new template is inserted.

* Payload
```
{
"name": [string], // required, 255 characters max
"key": [uuidv4], // required
"productId": [string], // required, 255 characters max
"gameId": [string], // required, matches ^[^-][a-z0-9-]*$
"contents": [json], // required
Expand Down
5 changes: 2 additions & 3 deletions features/godogs_step_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func theFollowingOfferTemplatesExistInTheGame(gameID string, otArgs *gherkin.Dat
}

if _, err := models.GetEnabledOfferTemplateByKeyAndGame(app.DB, ot.Key, ot.GameID, nil); err != nil {
if _, err = models.InsertOfferTemplate(app.DB, ot, nil); err != nil {
if _, err = models.InsertOfferTemplate(app.DB, ot, true, nil); err != nil {
return err
}
}
Expand Down Expand Up @@ -336,7 +336,6 @@ func toJSON(str string) dat.JSON {
func anOfferTemplateIsCreatedInTheGameWithNameKeyPidContentsMetadataPeriodFreqTriggerPlace(gameID, name, key, pid, contents, metadata, period, freq, trigger, place string) error {
payload := map[string]interface{}{
"gameId": gameID,
"key": key,
"name": name,
"productId": pid,
"contents": toJSON(contents),
Expand All @@ -347,7 +346,7 @@ func anOfferTemplateIsCreatedInTheGameWithNameKeyPidContentsMetadataPeriodFreqTr
"placement": place,
}
var err error
lastStatus, lastBody, err = performRequest(app, "POST", "/templates", payload)
lastStatus, lastBody, err = performRequest(app, "POST", fmt.Sprintf("/templates/%s/insert", key), payload)

return err
}
Expand Down
2 changes: 1 addition & 1 deletion features/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func insertOfferTemplate(db runner.Connection, name, gameID string) error {
Trigger: dat.JSON([]byte(`{"from": 1487280506875, "to": 1487366964730}`)),
Placement: "popup",
}
_, err := models.InsertOfferTemplate(app.DB, offerTemplate, nil)
_, err := models.InsertOfferTemplate(app.DB, offerTemplate, true, nil)
return err
}

Expand Down
1 change: 0 additions & 1 deletion features/offer/offer-template-creation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ Feature: Create an offer template
Examples:
| name | key | pid | contents | metadata | period | frequency | trigger | placement | error |
| | 05f69798-7720-43d2-acd4-92fe0c11e2b1 | com.tfg.sample | { 'x': 1 } | { 'y': 2 } | { 'max': 1 } | { 'every': '50s' } | { 'from': 1486678078, 'to': 1486678079} | popup | The name is required to create a new offer template. |
| oc3 | | com.tfg.sample | { 'x': 1 } | { 'y': 2 } | { 'max': 1 } | { 'every': '50s' } | { 'from': 1486678078, 'to': 1486678079} | popup | The key is required to create a new offer template. |
| oc3 | 05f69798-7720-43d2-acd4-92fe0c11e2b1 | | { 'x': 1 } | { 'y': 2 } | { 'max': 1 } | { 'every': '50s' } | { 'from': 1486678078, 'to': 1486678079} | popup | The product id is required to create a new offer template. |
| oc3 | 05f69798-7720-43d2-acd4-92fe0c11e2b1 | com.tfg.sample | | { 'y': 2 } | { 'max': 1 } | { 'every': '50s' } | { 'from': 1486678078, 'to': 1486678079} | popup | The contents are required to create a new offer template. |
| oc3 | 05f69798-7720-43d2-acd4-92fe0c11e2b1 | com.tfg.sample | { 'x': 1 } | { 'y': 2 } | | { 'every': '50s' } | { 'from': 1486678078, 'to': 1486678079} | popup | The period is required to create a new offer template. |
Expand Down
8 changes: 8 additions & 0 deletions fixtures/games.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@
id: limited-offers-game
name: game-4
created_at: 2016-01-01 12:30:12
-
id: offers-game-to-update
name: offers-game-3
created_at: 2016-01-01 12:30:12
-
id: unique-offers-game
name: unique-game
created_at: 2016-01-01 12:30:12
28 changes: 27 additions & 1 deletion fixtures/offer_templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,37 @@
key: 471c730b-b8ed-4caa-a245-f46822914c8c
name: template-10
product_id: com.tfg.sample
game_id: offers-game
game_id: unique-offers-game
contents: '{"gems": 5, "gold": 100}'
metadata: '{}'
period: '{"every": "12h"}'
frequency: '{"max": 2}'
trigger: '{"from": 1486678000, "to": 1486679000}'
placement: unique-place
enabled: true
-
id: 8080cd21-6ed7-44d2-b243-8e8161d27c9c
key: 6b792eb5-f103-4e8e-9561-aa322e06243a
name: template-to-update
product_id: com.tfg.sample
game_id: offers-game-to-update
contents: '{"gems": 5, "gold": 100}'
metadata: '{}'
period: '{"every": "12h"}'
frequency: '{"max": 2}'
trigger: '{"from": 1486678000, "to": 1486679000}'
placement: popup
enabled: true
-
id: 3479fa18-6739-4ea8-a011-24ae40ce1c02
key: f8f8c92a-fb88-4a7d-8a5b-b8de66d7b5ac
name: template-to-update
product_id: com.tfg.sample
game_id: offers-game-to-update
contents: '{"gems": 5, "gold": 100}'
metadata: '{}'
period: '{"every": "12h"}'
frequency: '{"max": 2}'
trigger: '{"from": 1486678000, "to": 1486679000}'
placement: store
enabled: true
6 changes: 3 additions & 3 deletions migrations/migrations.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion models/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ func UpsertGame(db runner.Connection, game *Game, t time.Time, mr *MixedMetricsR
}
game.UpdatedAt = dat.NullTimeFrom(t)
return mr.WithDatastoreSegment("games", SegmentUpsert, func() error {
return db.
err := db.
Upsert("games").
Columns("id", "name", "updated_at", "metadata").
Record(game).
Where("id=$1", game.ID).
Returning("created_at", "updated_at").
QueryStruct(game)
return err
})
}
2 changes: 1 addition & 1 deletion models/game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ var _ = Describe("Games Model", func() {
It("Should return the full list of games", func() {
games, err := models.ListGames(db, nil)
Expect(err).NotTo(HaveOccurred())
Expect(games).To(HaveLen(5))
Expect(games).To(HaveLen(7))
})
})

Expand Down
3 changes: 1 addition & 2 deletions models/models_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ var _ = BeforeSuite(func() {

err = oTesting.LoadFixtures(conn)
Expect(err).NotTo(HaveOccurred())

})

var _ = BeforeEach(func() {
Expand All @@ -42,7 +41,7 @@ var _ = BeforeEach(func() {
})

var _ = AfterEach(func() {
err := db.Rollback()
err := db.AutoRollback()
Expect(err).NotTo(HaveOccurred())
db = nil
})
Expand Down
Loading

0 comments on commit 1c9c8ef

Please sign in to comment.