Skip to content

Commit

Permalink
Reapply and Reinvite with cooldowns.
Browse files Browse the repository at this point in the history
Added cooldownBeforeApply, cooldownBeforeInvite for game.
Allow application and invitations to be created multiple times.
  • Loading branch information
heynemann committed Aug 1, 2016
1 parent c85787f commit 3a282db
Show file tree
Hide file tree
Showing 16 changed files with 814 additions and 110 deletions.
4 changes: 3 additions & 1 deletion api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ func (app *App) setConfigurationDefaults() {
app.Config.SetDefault("postgres.port", 5432)
app.Config.SetDefault("postgres.sslMode", "disable")
app.Config.SetDefault("webhooks.timeout", 2)
app.Config.SetDefault("khan.MaxPendingInvites", -1)
app.Config.SetDefault("khan.maxPendingInvites", -1)
app.Config.SetDefault("khan.defaultCooldownBeforeInvite", -1)
app.Config.SetDefault("khan.defaultCooldownBeforeApply", -1)
l.Debug("Configuration defaults set.")
}

Expand Down
153 changes: 106 additions & 47 deletions api/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package api

import (
"encoding/json"
"reflect"
"strings"
"time"

Expand All @@ -19,6 +18,10 @@ import (
"github.com/uber-go/zap"
)

type validatable interface {
Validate() []string
}

type gamePayload struct {
Name string
MembershipLevels map[string]interface{}
Expand All @@ -35,6 +38,23 @@ type gamePayload struct {
CooldownAfterDelete int
}

func (p *gamePayload) Validate() []string {
sortedLevels := util.SortLevels(p.MembershipLevels)
minMembershipLevel := sortedLevels[0].Value

var errors []string
if p.MinLevelToAcceptApplication < minMembershipLevel {
errors = append(errors, "minLevelToAcceptApplication should be greater or equal to minMembershipLevel")
}
if p.MinLevelToCreateInvitation < minMembershipLevel {
errors = append(errors, "minLevelToCreateInvitation should be greater or equal to minMembershipLevel")
}
if p.MinLevelToRemoveMember < minMembershipLevel {
errors = append(errors, "minLevelToRemoveMember should be greater or equal to minMembershipLevel")
}
return errors
}

type createGamePayload struct {
PublicID string
Name string
Expand All @@ -52,35 +72,27 @@ type createGamePayload struct {
CooldownAfterDelete int
}

func getAsInt(field string, payload interface{}) int {
v := reflect.ValueOf(payload)
fieldValue := v.FieldByName(field).Interface()
return fieldValue.(int)
}

func getAsJSON(field string, payload interface{}) map[string]interface{} {
v := reflect.ValueOf(payload)
fieldValue := v.FieldByName(field).Interface()
return fieldValue.(map[string]interface{})
}

func validateGamePayload(payload interface{}) []string {
sortedLevels := util.SortLevels(getAsJSON("MembershipLevels", payload))
func (p *createGamePayload) Validate() []string {
sortedLevels := util.SortLevels(p.MembershipLevels)
minMembershipLevel := sortedLevels[0].Value

var errors []string
if getAsInt("MinLevelToAcceptApplication", payload) < minMembershipLevel {
if p.MinLevelToAcceptApplication < minMembershipLevel {
errors = append(errors, "minLevelToAcceptApplication should be greater or equal to minMembershipLevel")
}
if getAsInt("MinLevelToCreateInvitation", payload) < minMembershipLevel {
if p.MinLevelToCreateInvitation < minMembershipLevel {
errors = append(errors, "minLevelToCreateInvitation should be greater or equal to minMembershipLevel")
}
if getAsInt("MinLevelToRemoveMember", payload) < minMembershipLevel {
if p.MinLevelToRemoveMember < minMembershipLevel {
errors = append(errors, "minLevelToRemoveMember should be greater or equal to minMembershipLevel")
}
return errors
}

func validateGamePayload(payload validatable) []string {
return payload.Validate()
}

func logPayloadErrors(l zap.Logger, errors []string) {
var fields []zap.Field
for _, err := range errors {
Expand All @@ -92,6 +104,62 @@ func logPayloadErrors(l zap.Logger, errors []string) {
)
}

type optionalParams struct {
maxPendingInvites int
cooldownBeforeApply int
cooldownBeforeInvite int
}

func getOptionalParameters(app *App, c *iris.Context) (*optionalParams, error) {
data := c.RequestCtx.Request.Body()
var jsonPayload map[string]interface{}
err := json.Unmarshal(data, &jsonPayload)
if err != nil {
return nil, err
}

var maxPendingInvites int
if val, ok := jsonPayload["maxPendingInvites"]; ok {
maxPendingInvites = int(val.(float64))
} else {
maxPendingInvites = app.Config.GetInt("khan.maxPendingInvites")
}

var cooldownBeforeInvite int
if val, ok := jsonPayload["cooldownBeforeInvite"]; ok {
cooldownBeforeInvite = int(val.(float64))
} else {
cooldownBeforeInvite = app.Config.GetInt("khan.defaultCooldownBeforeInvite")
}

var cooldownBeforeApply int
if val, ok := jsonPayload["cooldownBeforeApply"]; ok {
cooldownBeforeApply = int(val.(float64))
} else {
cooldownBeforeApply = app.Config.GetInt("khan.defaultCooldownBeforeApply")
}

return &optionalParams{
maxPendingInvites: maxPendingInvites,
cooldownBeforeInvite: cooldownBeforeInvite,
cooldownBeforeApply: cooldownBeforeApply,
}, nil
}

func getCreateGamePayload(app *App, c *iris.Context) (*createGamePayload, *optionalParams, error) {
var payload createGamePayload
if err := LoadJSONPayload(&payload, c); err != nil {
return nil, nil, err
}

optional, err := getOptionalParameters(app, c)
if err != nil {
return nil, nil, err
}

return &payload, optional, nil
}

// CreateGameHandler is the handler responsible for creating new games
func CreateGameHandler(app *App) func(c *iris.Context) {
return func(c *iris.Context) {
Expand All @@ -101,26 +169,19 @@ func CreateGameHandler(app *App) func(c *iris.Context) {
zap.String("operation", "createGame"),
)

var payload createGamePayload
if err := LoadJSONPayload(&payload, c); err != nil {
FailWith(400, err.Error(), c)
return
}

data := c.RequestCtx.Request.Body()
var jsonPayload map[string]interface{}
err := json.Unmarshal(data, &jsonPayload)
l.Debug("Retrieving parameters...")
payload, optional, err := getCreateGamePayload(app, c)
if err != nil {
l.Error("Failed to retrieve parameters.", zap.Error(err))
FailWith(400, err.Error(), c)
return
}

var maxPendingInvites int
if val, ok := jsonPayload["maxPendingInvites"]; ok {
maxPendingInvites = int(val.(float64))
} else {
maxPendingInvites = app.Config.GetInt("khan.MaxPendingInvites")
}
l.Debug(
"Parameters retrieved successfully.",
zap.Int("maxPendingInvites", optional.maxPendingInvites),
zap.Int("cooldownBeforeInvite", optional.cooldownBeforeInvite),
zap.Int("cooldownBeforeApply", optional.cooldownBeforeApply),
)

if payloadErrors := validateGamePayload(payload); len(payloadErrors) != 0 {
logPayloadErrors(l, payloadErrors)
Expand Down Expand Up @@ -155,7 +216,9 @@ func CreateGameHandler(app *App) func(c *iris.Context) {
payload.MaxClansPerPlayer,
payload.CooldownAfterDeny,
payload.CooldownAfterDelete,
maxPendingInvites,
optional.cooldownBeforeApply,
optional.cooldownBeforeInvite,
optional.maxPendingInvites,
false,
)

Expand Down Expand Up @@ -195,22 +258,13 @@ func UpdateGameHandler(app *App) func(c *iris.Context) {
return
}

data := c.RequestCtx.Request.Body()
var jsonPayload map[string]interface{}
err := json.Unmarshal(data, &jsonPayload)
optional, err := getOptionalParameters(app, c)
if err != nil {
FailWith(400, err.Error(), c)
return
}

var maxPendingInvites int
if val, ok := jsonPayload["maxPendingInvites"]; ok {
maxPendingInvites = int(val.(float64))
} else {
maxPendingInvites = app.Config.GetInt("khan.MaxPendingInvites")
}

if payloadErrors := validateGamePayload(payload); len(payloadErrors) != 0 {
if payloadErrors := validateGamePayload(&payload); len(payloadErrors) != 0 {
logPayloadErrors(l, payloadErrors)
errorString := strings.Join(payloadErrors[:], ", ")
FailWith(422, errorString, c)
Expand Down Expand Up @@ -243,7 +297,9 @@ func UpdateGameHandler(app *App) func(c *iris.Context) {
payload.MaxClansPerPlayer,
payload.CooldownAfterDeny,
payload.CooldownAfterDelete,
maxPendingInvites,
optional.cooldownBeforeApply,
optional.cooldownBeforeInvite,
optional.maxPendingInvites,
)

if err != nil {
Expand Down Expand Up @@ -272,6 +328,9 @@ func UpdateGameHandler(app *App) func(c *iris.Context) {
"maxClansPerPlayer": payload.MaxClansPerPlayer,
"cooldownAfterDeny": payload.CooldownAfterDeny,
"cooldownAfterDelete": payload.CooldownAfterDelete,
"cooldownBeforeApply": optional.cooldownBeforeApply,
"cooldownBeforeInvite": optional.cooldownBeforeInvite,
"maxPendingInvites": optional.maxPendingInvites,
}
app.DispatchHooks(gameID, models.GameUpdatedHook, successPayload)

Expand Down
26 changes: 25 additions & 1 deletion api/game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func getGamePayload(publicID, name string) map[string]interface{} {
"maxClansPerPlayer": 1,
"cooldownAfterDeny": 30,
"cooldownAfterDelete": 30,
"maxPendingInvites": 30,
}
}

Expand Down Expand Up @@ -88,6 +87,31 @@ var _ = Describe("Player API Handler", func() {
Expect(dbGame.MaxClansPerPlayer).To(Equal(payload["maxClansPerPlayer"]))
Expect(dbGame.CooldownAfterDeny).To(Equal(payload["cooldownAfterDeny"]))
Expect(dbGame.CooldownAfterDelete).To(Equal(payload["cooldownAfterDelete"]))
Expect(dbGame.CooldownBeforeInvite).To(Equal(0))
Expect(dbGame.CooldownBeforeApply).To(Equal(3600))
Expect(dbGame.MaxPendingInvites).To(Equal(-1))
})

It("Should create game with custom optional params", func() {
a := GetDefaultTestApp()

payload := getGamePayload("", "")
payload["maxPendingInvites"] = 27
payload["cooldownBeforeApply"] = 2874
payload["cooldownBeforeInvite"] = 2384
res := PostJSON(a, "/games", payload)

Expect(res.Raw().StatusCode).To(Equal(http.StatusOK))
var result map[string]interface{}
json.Unmarshal([]byte(res.Body().Raw()), &result)
Expect(result["success"]).To(BeTrue())
Expect(result["publicID"]).To(Equal(payload["publicID"].(string)))

dbGame, err := models.GetGameByPublicID(a.Db, payload["publicID"].(string))
Expect(err).NotTo(HaveOccurred())
Expect(dbGame.CooldownBeforeInvite).To(Equal(2384))
Expect(dbGame.CooldownBeforeApply).To(Equal(2874))
Expect(dbGame.MaxPendingInvites).To(Equal(27))
})

It("Should not create game if missing parameters", func() {
Expand Down
2 changes: 1 addition & 1 deletion api/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var _ = Describe("Healthcheck API Handler", func() {

It("Should respond with customized WORKING string", func() {
a := GetDefaultTestApp()
a.Config.SetDefault("healthcheck.workingText", "OTHERWORKING")
a.Config.Set("healthcheck.workingText", "OTHERWORKING")
res := Get(a, "/healthcheck")

Expect(res.Raw().StatusCode).To(Equal(http.StatusOK))
Expand Down
2 changes: 2 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ postgres:

khan:
maxPendingInvites: -1
defaultCooldownBeforeInvite: 0
defaultCooldownBeforeApply: 3600

healthcheck:
workingText: "WORKING"
Expand Down
11 changes: 11 additions & 0 deletions config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ postgres:
host: "localhost"
port: 5432
sslMode: "disable"

khan:
maxPendingInvites: -1
defaultCooldownBeforeInvite: 0
defaultCooldownBeforeApply: 3600

healthcheck:
workingText: "WORKING"

webhooks:
timeout: 2
Loading

0 comments on commit 3a282db

Please sign in to comment.