From 52fde2bf19df4e5e8cf6eba0e313443b13a14999 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 22 Aug 2019 18:54:06 -0300 Subject: [PATCH] Returning pending applications and invites in retrieveClan route (#46) * Removing unused struct * Returning pending applications and invites in retrieveClan route * Fixing bug on query * Returning bad request if route inputs are invalid * Fixing tests * Changing order strings --- api/clan.go | 41 +++++++++++++++++++++ models/clan.go | 89 ++++++++++++++++++++++++++++++++++++--------- models/clan_test.go | 18 ++++----- 3 files changed, 122 insertions(+), 26 deletions(-) diff --git a/api/clan.go b/api/clan.go index 28f88bf2..187bf32f 100644 --- a/api/clan.go +++ b/api/clan.go @@ -8,6 +8,8 @@ package api import ( + "fmt" + "strconv" "strings" "time" @@ -747,6 +749,44 @@ func RetrieveClanHandler(app *App) func(c echo.Context) error { gameID := c.Param("gameID") publicID := c.Param("clanPublicID") shortID := c.QueryParam("shortID") + maxPendingApplications := c.QueryParam("maxPendingApplications") + maxPendingInvites := c.QueryParam("maxPendingInvites") + pendingApplicationsOrder := c.QueryParam("pendingApplicationsOrder") + pendingInvitesOrder := c.QueryParam("pendingInvitesOrder") + + options := models.NewDefaultGetClanDetailsOptions(app.Config) + if maxPendingApplications != "" { + maxApps, err := strconv.ParseUint(maxPendingApplications, 10, 16) + if err != nil { + return FailWith(400, err.Error(), c) + } + if int(maxApps) > options.MaxPendingApplications { + return FailWith(400, fmt.Sprintf("Maximum pending applications above allowed (%v).", options.MaxPendingApplications), c) + } + options.MaxPendingApplications = int(maxApps) + } + if maxPendingInvites != "" { + maxInvs, err := strconv.ParseUint(maxPendingInvites, 10, 16) + if err != nil { + return FailWith(400, err.Error(), c) + } + if int(maxInvs) > options.MaxPendingInvites { + return FailWith(400, fmt.Sprintf("Maximum pending invites above allowed (%v).", options.MaxPendingInvites), c) + } + options.MaxPendingInvites = int(maxInvs) + } + if pendingApplicationsOrder != "" { + if !models.IsValidOrder(pendingApplicationsOrder) { + return FailWith(400, fmt.Sprintf("Pending applications order is invalid (valid orders are %s or %s).", models.Newest, models.Oldest), c) + } + options.PendingApplicationsOrder = pendingApplicationsOrder + } + if pendingInvitesOrder != "" { + if !models.IsValidOrder(pendingInvitesOrder) { + return FailWith(400, fmt.Sprintf("Pending invites order is invalid (valid orders are %s or %s).", models.Newest, models.Oldest), c) + } + options.PendingInvitesOrder = pendingInvitesOrder + } db := app.Db(c.StdContext()) @@ -805,6 +845,7 @@ func RetrieveClanHandler(app *App) func(c echo.Context) error { gameID, clan, game.MaxClansPerPlayer, + options, ) if err != nil { diff --git a/models/clan.go b/models/clan.go index e0875d58..4f2d0d56 100644 --- a/models/clan.go +++ b/models/clan.go @@ -16,6 +16,8 @@ import ( "os" "strings" + "github.com/spf13/viper" + "github.com/globalsign/mgo/bson" "github.com/go-gorp/gorp" workers "github.com/jrallison/go-workers" @@ -53,6 +55,52 @@ type Clan struct { DeletedAt int64 `db:"deleted_at" json:"deletedAt" bson:"deletedAt"` } +// Newest is the constant "newest" +const Newest string = "newest" + +// Oldest is the constant "oldest" +const Oldest string = "oldest" + +// IsValidOrder returns whether the input is equal to Newest or Oldest +func IsValidOrder(order string) bool { + return order == Newest || order == Oldest +} + +func getSQLOrderFromSemanticOrder(semantic string) (sql string) { + if semantic == Newest { + sql = "DESC" + } else { + sql = "ASC" + } + return +} + +// GetClanDetailsOptions holds options to change the output of GetClanDetails() +type GetClanDetailsOptions struct { + MaxPendingApplications int + MaxPendingInvites int + PendingApplicationsOrder string + PendingInvitesOrder string +} + +// NewDefaultGetClanDetailsOptions returns a new options structure with default values for GetClanDetails() +func NewDefaultGetClanDetailsOptions(config *viper.Viper) *GetClanDetailsOptions { + maxPendingApplicationsKey := "getClanDetails.defaultOptions.maxPendingApplications" + maxPendingInvitesKey := "getClanDetails.defaultOptions.maxPendingInvites" + pendingApplicationsOrderKey := "getClanDetails.defaultOptions.pendingApplicationsOrder" + pendingInvitesOrderKey := "getClanDetails.defaultOptions.pendingInvitesOrder" + config.SetDefault(maxPendingApplicationsKey, 100) + config.SetDefault(maxPendingInvitesKey, 100) + config.SetDefault(pendingApplicationsOrderKey, Newest) + config.SetDefault(pendingInvitesOrderKey, Newest) + return &GetClanDetailsOptions{ + MaxPendingApplications: config.GetInt(maxPendingApplicationsKey), + MaxPendingInvites: config.GetInt(maxPendingInvitesKey), + PendingApplicationsOrder: config.GetString(pendingApplicationsOrderKey), + PendingInvitesOrder: config.GetString(pendingInvitesOrderKey), + } +} + //ToJSON returns the clan as JSON func (c *Clan) ToJSON() ([]byte, error) { w := jwriter.Writer{} @@ -688,8 +736,8 @@ func GetClanMembers(db DB, gameID, publicID string) (map[string]interface{}, err } // GetClanDetails returns all details for a given clan by its game id and public id -func GetClanDetails(db DB, gameID string, clan *Clan, maxClansPerPlayer int) (map[string]interface{}, error) { - query := ` +func GetClanDetails(db DB, gameID string, clan *Clan, maxClansPerPlayer int, options *GetClanDetailsOptions) (map[string]interface{}, error) { + query := fmt.Sprintf(` SELECT c.game_id GameID, c.public_id ClanPublicID, c.name ClanName, c.metadata ClanMetadata, @@ -709,9 +757,25 @@ func GetClanDetails(db DB, gameID string, clan *Clan, maxClansPerPlayer int) (ma FROM clans c INNER JOIN players o ON c.owner_id=o.id LEFT OUTER JOIN ( - SELECT * - FROM memberships im - WHERE im.clan_id=$2 AND im.deleted_at=0 AND (im.approved=true OR im.denied=true OR im.banned=true) + ( + SELECT * + FROM memberships im + WHERE im.clan_id=$2 AND im.deleted_at=0 AND im.approved=false AND im.denied=false AND im.banned=false AND im.requestor_id=im.player_id + ORDER BY im.id %s + LIMIT $3 + ) + UNION ALL ( + SELECT * + FROM memberships im + WHERE im.clan_id=$2 AND im.deleted_at=0 AND im.approved=false AND im.denied=false AND im.banned=false AND im.requestor_id<>im.player_id + ORDER BY im.id %s + LIMIT $4 + ) + UNION ALL ( + SELECT * + FROM memberships im + WHERE im.clan_id=$2 AND im.deleted_at=0 AND (im.approved=true OR im.denied=true OR im.banned=true) + ) ) m ON m.clan_id=c.id LEFT OUTER JOIN players r ON m.requestor_id=r.id LEFT OUTER JOIN players a ON m.approver_id=a.id @@ -719,9 +783,10 @@ func GetClanDetails(db DB, gameID string, clan *Clan, maxClansPerPlayer int) (ma LEFT OUTER JOIN players y ON m.denier_id=y.id WHERE c.game_id=$1 AND c.id=$2 - ` + `, getSQLOrderFromSemanticOrder(options.PendingApplicationsOrder), getSQLOrderFromSemanticOrder(options.PendingInvitesOrder)) + var details []clanDetailsDAO - _, err := db.Select(&details, query, gameID, clan.ID) + _, err := db.Select(&details, query, gameID, clan.ID, options.MaxPendingApplications, options.MaxPendingInvites) if err != nil { return nil, err } @@ -840,16 +905,6 @@ func GetClansSummaries(db DB, gameID string, publicIDs []string) ([]map[string]i return resultClans, nil } -type mongoResult struct { - OK int `bson:"ok"` - WaitedMS int `bson:"waitedMS"` - Cursor struct { - ID interface{} `bson:"id"` - NS string `bson:"ns"` - FirstBatch []bson.Raw `bson:"firstBatch"` - } `bson:"cursor"` -} - func min(x, y int) int { if x < y { return x diff --git a/models/clan_test.go b/models/clan_test.go index fa13185a..22284ecc 100644 --- a/models/clan_test.go +++ b/models/clan_test.go @@ -16,6 +16,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" uuid "github.com/satori/go.uuid" + "github.com/spf13/viper" "github.com/topfreegames/extensions/mongo/interfaces" . "github.com/topfreegames/khan/models" "github.com/topfreegames/khan/testing" @@ -750,7 +751,7 @@ var _ = Describe("Clan Model", func() { _, clan, _, _, _, err := GetClanWithMemberships( testDb, 10, 3, 4, 5, gameID, uuid.NewV4().String(), ) - clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1) + clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(err).NotTo(HaveOccurred()) clanPlayers, err := GetClanMembers(testDb, clan.GameID, clan.PublicID) @@ -769,7 +770,7 @@ var _ = Describe("Clan Model", func() { _, clan, _, _, _, err := GetClanWithMemberships( testDb, 0, 3, 4, 5, gameID, uuid.NewV4().String(), ) - clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1) + clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(err).NotTo(HaveOccurred()) clanPlayers, err := GetClanMembers(testDb, clan.GameID, clan.PublicID) @@ -788,7 +789,7 @@ var _ = Describe("Clan Model", func() { ) Expect(err).NotTo(HaveOccurred()) - clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1) + clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(err).NotTo(HaveOccurred()) Expect(clanData["name"]).To(Equal(clan.Name)) Expect(clanData["metadata"]).To(Equal(clan.Metadata)) @@ -801,9 +802,8 @@ var _ = Describe("Clan Model", func() { pendingApplications := clanData["memberships"].(map[string]interface{})["pendingApplications"].([]map[string]interface{}) Expect(len(pendingApplications)).To(Equal(0)) - //We do not return pending invites or applications anymore pendingInvites := clanData["memberships"].(map[string]interface{})["pendingInvites"].([]map[string]interface{}) - Expect(len(pendingInvites)).To(Equal(0)) + Expect(len(pendingInvites)).To(Equal(5)) banned := clanData["memberships"].(map[string]interface{})["banned"].([]map[string]interface{}) Expect(len(banned)).To(Equal(4)) @@ -893,7 +893,7 @@ var _ = Describe("Clan Model", func() { _, err = testDb.Update(memberships[9]) Expect(err).NotTo(HaveOccurred()) - clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1) + clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(err).NotTo(HaveOccurred()) Expect(clanData["name"]).To(Equal(clan.Name)) Expect(clanData["metadata"]).To(Equal(clan.Metadata)) @@ -925,7 +925,7 @@ var _ = Describe("Clan Model", func() { _, err = testDb.Update(clan) Expect(err).NotTo(HaveOccurred()) - clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1) + clanData, err := GetClanDetails(testDb, clan.GameID, clan, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(err).NotTo(HaveOccurred()) Expect(clanData["name"]).To(Equal(clan.Name)) Expect(clanData["metadata"]).To(Equal(clan.Metadata)) @@ -937,7 +937,7 @@ var _ = Describe("Clan Model", func() { }) It("Should fail if clan does not exist", func() { - clanData, err := GetClanDetails(testDb, "fake-game-id", &Clan{PublicID: "fake-public-id"}, 1) + clanData, err := GetClanDetails(testDb, "fake-game-id", &Clan{PublicID: "fake-public-id"}, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(clanData).To(BeNil()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("Clan was not found with id: fake-public-id")) @@ -964,7 +964,7 @@ var _ = Describe("Clan Model", func() { }) It("Should fail if clan does not exist", func() { - clanData, err := GetClanDetails(testDb, "fake-game-id", &Clan{PublicID: "fake-public-id"}, 1) + clanData, err := GetClanDetails(testDb, "fake-game-id", &Clan{PublicID: "fake-public-id"}, 1, NewDefaultGetClanDetailsOptions(viper.New())) Expect(clanData).To(BeNil()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("Clan was not found with id: fake-public-id"))