From 01fdd80f7f51fa65ccff9268a3e6e263f6c68a97 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Sat, 21 Nov 2020 09:13:25 +0100 Subject: [PATCH] [WIP] - refactor limit, sort, offset - rename middleware.MayExceedLimit to CanExceedLimit - update the DailyTribeStats/DailyPlayerStats repository and usecase - rename utils.SanitizeSort to utils.SanitizeSortExpression - add two new utils - SanitizeSortExpressions and FindStringWithPrefix --- dailyplayerstats/repository.go | 3 + dailyplayerstats/repository/pg_repository.go | 42 ++++--- dailyplayerstats/usecase.go | 2 +- .../usecase/dailyplayerstats_usecase.go | 39 ++++--- dailytribestats/repository.go | 3 + dailytribestats/repository/pg_repository.go | 32 +++--- dailytribestats/usecase.go | 2 +- .../usecase/dailytribestats_usecase.go | 34 +++--- dev.sh | 2 +- ennoblement/usecase/ennoblement_usecase.go | 4 +- graphql/generated/generated.go | 103 +++++++++++++++--- graphql/resolvers/daily_player_stats.go | 26 ++++- graphql/resolvers/daily_tribe_stats.go | 26 ++++- graphql/schema/daily_player_stats.graphql | 9 +- graphql/schema/daily_tribe_stats.graphql | 9 +- middleware/limit_whitelist.go | 12 +- player/usecase/player_usecase.go | 6 +- .../usecase/playerhistory_usecase.go | 4 +- server/usecase/server_usecase.go | 5 +- serverstats/usecase/serverstats_usecase.go | 4 +- tribe/usecase/tribe_usecase.go | 4 +- tribechange/usecase/tribechange_usecase.go | 4 +- tribehistory/usecase/tribehistory_usecase.go | 4 +- utils/find_string_with_prefix.go | 12 ++ utils/sanitize_sort.go | 28 ++++- version/usecase/version_usecase.go | 4 +- village/usecase/village_usecase.go | 8 +- 27 files changed, 302 insertions(+), 129 deletions(-) create mode 100644 utils/find_string_with_prefix.go diff --git a/dailyplayerstats/repository.go b/dailyplayerstats/repository.go index 2ac5ff7..2158b6a 100644 --- a/dailyplayerstats/repository.go +++ b/dailyplayerstats/repository.go @@ -10,6 +10,9 @@ type FetchConfig struct { Server string Filter *models.DailyPlayerStatsFilter Count bool + Sort []string + Limit int + Offset int } type Repository interface { diff --git a/dailyplayerstats/repository/pg_repository.go b/dailyplayerstats/repository/pg_repository.go index ace6ead..49941fe 100644 --- a/dailyplayerstats/repository/pg_repository.go +++ b/dailyplayerstats/repository/pg_repository.go @@ -8,6 +8,7 @@ import ( "github.com/go-pg/pg/v10" "github.com/pkg/errors" "github.com/tribalwarshelp/api/dailyplayerstats" + "github.com/tribalwarshelp/api/utils" "github.com/tribalwarshelp/shared/models" ) @@ -23,39 +24,36 @@ func (repo *pgRepository) Fetch(ctx context.Context, cfg dailyplayerstats.FetchC var err error data := []*models.DailyPlayerStats{} total := 0 - query := repo.WithParam("SERVER", pg.Safe(cfg.Server)).Model(&data).Context(ctx) + query := repo. + WithParam("SERVER", pg.Safe(cfg.Server)). + Model(&data). + Context(ctx). + Order(cfg.Sort...). + Limit(cfg.Limit). + Offset(cfg.Offset) + playerRequired := utils.FindStringWithPrefix(cfg.Sort, "player") != "" + tribeRequired := utils.FindStringWithPrefix(cfg.Sort, "tribe") != "" if cfg.Filter != nil { query = query. - WhereStruct(cfg.Filter). - Limit(cfg.Filter.Limit). - Offset(cfg.Filter.Offset) - - order := []string{} - - if cfg.Filter.Sort != "" { - order = append(order, cfg.Filter.Sort) - } + WhereStruct(cfg.Filter) if cfg.Filter.PlayerFilter != nil { - query = query.Relation("Player._").WhereStruct(cfg.Filter.PlayerFilter) - - if cfg.Filter.PlayerFilter.Sort != "" { - order = append(order, fmt.Sprintf("player.%s", cfg.Filter.PlayerFilter.Sort)) - } + playerRequired = true + query = query.WhereStruct(cfg.Filter.PlayerFilter) if cfg.Filter.PlayerFilter.TribeFilter != nil { + tribeRequired = true query = query. - Join("LEFT JOIN ?SERVER.tribes AS tribe ON tribe.id = player.tribe_id"). WhereStruct(cfg.Filter.PlayerFilter.TribeFilter) - - if cfg.Filter.PlayerFilter.TribeFilter.Sort != "" { - order = append(order, fmt.Sprintf("tribe.%s", cfg.Filter.PlayerFilter.TribeFilter.Sort)) - } } } - - query = query.Order(order...) + } + if playerRequired { + query = query.Relation("Player._") + } + if tribeRequired { + query = query.Join("LEFT JOIN ?SERVER.tribes AS tribe ON tribe.id = player.tribe_id") } if cfg.Count { diff --git a/dailyplayerstats/usecase.go b/dailyplayerstats/usecase.go index 63782d8..24cf1ec 100644 --- a/dailyplayerstats/usecase.go +++ b/dailyplayerstats/usecase.go @@ -7,5 +7,5 @@ import ( ) type Usecase interface { - Fetch(ctx context.Context, server string, filter *models.DailyPlayerStatsFilter) ([]*models.DailyPlayerStats, int, error) + Fetch(ctx context.Context, cfg FetchConfig) ([]*models.DailyPlayerStats, int, error) } diff --git a/dailyplayerstats/usecase/dailyplayerstats_usecase.go b/dailyplayerstats/usecase/dailyplayerstats_usecase.go index d0c2d4a..ed7bcca 100644 --- a/dailyplayerstats/usecase/dailyplayerstats_usecase.go +++ b/dailyplayerstats/usecase/dailyplayerstats_usecase.go @@ -17,23 +17,32 @@ func New(repo dailyplayerstats.Repository) dailyplayerstats.Usecase { return &usecase{repo} } -func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.DailyPlayerStatsFilter) ([]*models.DailyPlayerStats, int, error) { - if filter == nil { - filter = &models.DailyPlayerStatsFilter{} +func (ucase *usecase) Fetch(ctx context.Context, cfg dailyplayerstats.FetchConfig) ([]*models.DailyPlayerStats, int, error) { + if cfg.Filter == nil { + cfg.Filter = &models.DailyPlayerStatsFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > dailyplayerstats.PaginationLimit || filter.Limit <= 0) { - filter.Limit = dailyplayerstats.PaginationLimit + + if cfg.Filter.Limit > 0 { + cfg.Limit = cfg.Filter.Limit } - filter.Sort = utils.SanitizeSort(filter.Sort) - if filter.PlayerFilter != nil { - filter.PlayerFilter.Sort = utils.SanitizeSort(filter.PlayerFilter.Sort) - if filter.PlayerFilter.TribeFilter != nil { - filter.PlayerFilter.TribeFilter.Sort = utils.SanitizeSort(filter.PlayerFilter.TribeFilter.Sort) + if cfg.Filter.Offset > 0 { + cfg.Offset = cfg.Filter.Offset + } + if cfg.Filter.Sort != "" { + cfg.Sort = append(cfg.Sort, cfg.Filter.Sort) + } + if cfg.Filter.PlayerFilter != nil { + if cfg.Filter.PlayerFilter.Sort != "" { + cfg.Sort = append(cfg.Sort, "player."+cfg.Filter.PlayerFilter.Sort) + } + if cfg.Filter.PlayerFilter.TribeFilter != nil && cfg.Filter.PlayerFilter.TribeFilter.Sort != "" { + cfg.Sort = append(cfg.Sort, "tribe."+cfg.Filter.PlayerFilter.TribeFilter.Sort) } } - return ucase.repo.Fetch(ctx, dailyplayerstats.FetchConfig{ - Server: server, - Filter: filter, - Count: true, - }) + + if !middleware.CanExceedLimit(ctx) && (cfg.Limit > dailyplayerstats.PaginationLimit || cfg.Limit <= 0) { + cfg.Limit = dailyplayerstats.PaginationLimit + } + cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort) + return ucase.repo.Fetch(ctx, cfg) } diff --git a/dailytribestats/repository.go b/dailytribestats/repository.go index d67a796..605986d 100644 --- a/dailytribestats/repository.go +++ b/dailytribestats/repository.go @@ -10,6 +10,9 @@ type FetchConfig struct { Server string Filter *models.DailyTribeStatsFilter Count bool + Sort []string + Limit int + Offset int } type Repository interface { diff --git a/dailytribestats/repository/pg_repository.go b/dailytribestats/repository/pg_repository.go index 4bd1afd..4f8606b 100644 --- a/dailytribestats/repository/pg_repository.go +++ b/dailytribestats/repository/pg_repository.go @@ -8,6 +8,7 @@ import ( "github.com/go-pg/pg/v10" "github.com/pkg/errors" "github.com/tribalwarshelp/api/dailytribestats" + "github.com/tribalwarshelp/api/utils" "github.com/tribalwarshelp/shared/models" ) @@ -23,29 +24,26 @@ func (repo *pgRepository) Fetch(ctx context.Context, cfg dailytribestats.FetchCo var err error data := []*models.DailyTribeStats{} total := 0 - query := repo.WithParam("SERVER", pg.Safe(cfg.Server)).Model(&data).Context(ctx) + query := repo. + WithParam("SERVER", pg.Safe(cfg.Server)). + Model(&data). + Context(ctx). + Order(cfg.Sort...). + Limit(cfg.Limit). + Offset(cfg.Offset) + tribeRequired := utils.FindStringWithPrefix(cfg.Sort, "tribe") != "" if cfg.Filter != nil { query = query. - WhereStruct(cfg.Filter). - Limit(cfg.Filter.Limit). - Offset(cfg.Filter.Offset) - - order := []string{} - - if cfg.Filter.Sort != "" { - order = append(order, cfg.Filter.Sort) - } + WhereStruct(cfg.Filter) if cfg.Filter.TribeFilter != nil { - query = query.Relation("Tribe._").WhereStruct(cfg.Filter.TribeFilter) - - if cfg.Filter.TribeFilter.Sort != "" { - order = append(order, fmt.Sprintf("tribe.%s", cfg.Filter.TribeFilter.Sort)) - } + query = query.WhereStruct(cfg.Filter.TribeFilter) + tribeRequired = true } - - query = query.Order(order...) + } + if tribeRequired { + query = query.Relation("Tribe._") } if cfg.Count { diff --git a/dailytribestats/usecase.go b/dailytribestats/usecase.go index 58a75c5..3f4d215 100644 --- a/dailytribestats/usecase.go +++ b/dailytribestats/usecase.go @@ -7,5 +7,5 @@ import ( ) type Usecase interface { - Fetch(ctx context.Context, server string, filter *models.DailyTribeStatsFilter) ([]*models.DailyTribeStats, int, error) + Fetch(ctx context.Context, cfg FetchConfig) ([]*models.DailyTribeStats, int, error) } diff --git a/dailytribestats/usecase/dailytribestats_usecase.go b/dailytribestats/usecase/dailytribestats_usecase.go index 0cfe8de..5d6fb53 100644 --- a/dailytribestats/usecase/dailytribestats_usecase.go +++ b/dailytribestats/usecase/dailytribestats_usecase.go @@ -17,20 +17,28 @@ func New(repo dailytribestats.Repository) dailytribestats.Usecase { return &usecase{repo} } -func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.DailyTribeStatsFilter) ([]*models.DailyTribeStats, int, error) { - if filter == nil { - filter = &models.DailyTribeStatsFilter{} +func (ucase *usecase) Fetch(ctx context.Context, cfg dailytribestats.FetchConfig) ([]*models.DailyTribeStats, int, error) { + if cfg.Filter == nil { + cfg.Filter = &models.DailyTribeStatsFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > dailytribestats.PaginationLimit || filter.Limit <= 0) { - filter.Limit = dailytribestats.PaginationLimit + if cfg.Filter.Limit > 0 { + cfg.Limit = cfg.Filter.Limit } - filter.Sort = utils.SanitizeSort(filter.Sort) - if filter.TribeFilter != nil { - filter.TribeFilter.Sort = utils.SanitizeSort(filter.TribeFilter.Sort) + if cfg.Filter.Offset > 0 { + cfg.Offset = cfg.Filter.Offset } - return ucase.repo.Fetch(ctx, dailytribestats.FetchConfig{ - Server: server, - Filter: filter, - Count: true, - }) + if cfg.Filter.Sort != "" { + cfg.Sort = append(cfg.Sort, cfg.Filter.Sort) + } + if cfg.Filter.TribeFilter != nil { + if cfg.Filter.TribeFilter.Sort != "" { + cfg.Sort = append(cfg.Sort, "tribe."+cfg.Filter.TribeFilter.Sort) + } + } + + if !middleware.CanExceedLimit(ctx) && (cfg.Limit > dailytribestats.PaginationLimit || cfg.Limit <= 0) { + cfg.Limit = dailytribestats.PaginationLimit + } + cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort) + return ucase.repo.Fetch(ctx, cfg) } diff --git a/dev.sh b/dev.sh index 20b5548..de810ed 100755 --- a/dev.sh +++ b/dev.sh @@ -1,4 +1,4 @@ #!/bin/sh export MODE=development export GIN_MODE=debug -go run main.go \ No newline at end of file +go run -race main.go \ No newline at end of file diff --git a/ennoblement/usecase/ennoblement_usecase.go b/ennoblement/usecase/ennoblement_usecase.go index b84a138..58afde6 100644 --- a/ennoblement/usecase/ennoblement_usecase.go +++ b/ennoblement/usecase/ennoblement_usecase.go @@ -21,10 +21,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.E if filter == nil { filter = &models.EnnoblementFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > ennoblement.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > ennoblement.PaginationLimit || filter.Limit <= 0) { filter.Limit = ennoblement.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, ennoblement.FetchConfig{ Server: server, Filter: filter, diff --git a/graphql/generated/generated.go b/graphql/generated/generated.go index 268257c..550b55e 100644 --- a/graphql/generated/generated.go +++ b/graphql/generated/generated.go @@ -216,8 +216,8 @@ type ComplexityRoot struct { } Query struct { - DailyPlayerStats func(childComplexity int, server string, filter *models.DailyPlayerStatsFilter) int - DailyTribeStats func(childComplexity int, server string, filter *models.DailyTribeStatsFilter) int + DailyPlayerStats func(childComplexity int, server string, filter *models.DailyPlayerStatsFilter, limit *int, offset *int, sort []string) int + DailyTribeStats func(childComplexity int, server string, filter *models.DailyTribeStatsFilter, limit *int, offset *int, sort []string) int Ennoblements func(childComplexity int, server string, filter *models.EnnoblementFilter) int LangVersion func(childComplexity int, tag models.VersionCode) int LangVersions func(childComplexity int, filter *models.VersionFilter) int @@ -579,8 +579,8 @@ type PlayerHistoryRecordResolver interface { Tribe(ctx context.Context, obj *models.PlayerHistory) (*models.Tribe, error) } type QueryResolver interface { - DailyPlayerStats(ctx context.Context, server string, filter *models.DailyPlayerStatsFilter) (*DailyPlayerStats, error) - DailyTribeStats(ctx context.Context, server string, filter *models.DailyTribeStatsFilter) (*DailyTribeStats, error) + DailyPlayerStats(ctx context.Context, server string, filter *models.DailyPlayerStatsFilter, limit *int, offset *int, sort []string) (*DailyPlayerStats, error) + DailyTribeStats(ctx context.Context, server string, filter *models.DailyTribeStatsFilter, limit *int, offset *int, sort []string) (*DailyTribeStats, error) Ennoblements(ctx context.Context, server string, filter *models.EnnoblementFilter) (*EnnoblementList, error) LiveEnnoblements(ctx context.Context, server string) ([]*models.LiveEnnoblement, error) Players(ctx context.Context, server string, filter *models.PlayerFilter) (*PlayerList, error) @@ -1477,7 +1477,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.DailyPlayerStats(childComplexity, args["server"].(string), args["filter"].(*models.DailyPlayerStatsFilter)), true + return e.complexity.Query.DailyPlayerStats(childComplexity, args["server"].(string), args["filter"].(*models.DailyPlayerStatsFilter), args["limit"].(*int), args["offset"].(*int), args["sort"].([]string)), true case "Query.dailyTribeStats": if e.complexity.Query.DailyTribeStats == nil { @@ -1489,7 +1489,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.DailyTribeStats(childComplexity, args["server"].(string), args["filter"].(*models.DailyTribeStatsFilter)), true + return e.complexity.Query.DailyTribeStats(childComplexity, args["server"].(string), args["filter"].(*models.DailyTribeStatsFilter), args["limit"].(*int), args["offset"].(*int), args["sort"].([]string)), true case "Query.ennoblements": if e.complexity.Query.Ennoblements == nil { @@ -3371,15 +3371,18 @@ input DailyPlayerStatsFilter { createDateLT: Time createDateLTE: Time - offset: Int - limit: Int - sort: String + offset: Int @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - ` + "`" + `offset` + "`" + `.") + limit: Int @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - ` + "`" + `limit` + "`" + `.") + sort: String @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - ` + "`" + `sort` + "`" + `.") } extend type Query { dailyPlayerStats( server: String! filter: DailyPlayerStatsFilter + limit: Int + offset: Int + sort: [String!] ): DailyPlayerStats! } `, BuiltIn: false}, @@ -3416,15 +3419,18 @@ input DailyTribeStatsFilter { createDateLT: Time createDateLTE: Time - offset: Int - limit: Int - sort: String + offset: Int @deprecated(reason: "Use a new variable added to the query dailyTribeStats - ` + "`" + `offset` + "`" + `.") + limit: Int @deprecated(reason: "Use a new variable added to the query dailyTribeStats - ` + "`" + `limit` + "`" + `.") + sort: String @deprecated(reason: "Use a new variable added to the query dailyTribeStats - ` + "`" + `sort` + "`" + `.") } extend type Query { dailyTribeStats( server: String! filter: DailyTribeStatsFilter + limit: Int + offset: Int + sort: [String!] ): DailyTribeStats! } `, BuiltIn: false}, @@ -4337,6 +4343,33 @@ func (ec *executionContext) field_Query_dailyPlayerStats_args(ctx context.Contex } } args["filter"] = arg1 + var arg2 *int + if tmp, ok := rawArgs["limit"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("limit")) + arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["limit"] = arg2 + var arg3 *int + if tmp, ok := rawArgs["offset"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("offset")) + arg3, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["offset"] = arg3 + var arg4 []string + if tmp, ok := rawArgs["sort"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sort")) + arg4, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["sort"] = arg4 return args, nil } @@ -4361,6 +4394,33 @@ func (ec *executionContext) field_Query_dailyTribeStats_args(ctx context.Context } } args["filter"] = arg1 + var arg2 *int + if tmp, ok := rawArgs["limit"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("limit")) + arg2, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["limit"] = arg2 + var arg3 *int + if tmp, ok := rawArgs["offset"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("offset")) + arg3, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["offset"] = arg3 + var arg4 []string + if tmp, ok := rawArgs["sort"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sort")) + arg4, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["sort"] = arg4 return args, nil } @@ -8904,7 +8964,7 @@ func (ec *executionContext) _Query_dailyPlayerStats(ctx context.Context, field g fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().DailyPlayerStats(rctx, args["server"].(string), args["filter"].(*models.DailyPlayerStatsFilter)) + return ec.resolvers.Query().DailyPlayerStats(rctx, args["server"].(string), args["filter"].(*models.DailyPlayerStatsFilter), args["limit"].(*int), args["offset"].(*int), args["sort"].([]string)) }) if err != nil { ec.Error(ctx, err) @@ -8946,7 +9006,7 @@ func (ec *executionContext) _Query_dailyTribeStats(ctx context.Context, field gr fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().DailyTribeStats(rctx, args["server"].(string), args["filter"].(*models.DailyTribeStatsFilter)) + return ec.resolvers.Query().DailyTribeStats(rctx, args["server"].(string), args["filter"].(*models.DailyTribeStatsFilter), args["limit"].(*int), args["offset"].(*int), args["sort"].([]string)) }) if err != nil { ec.Error(ctx, err) @@ -25522,6 +25582,21 @@ func (ec *executionContext) marshalOInt2ᚕintᚄ(ctx context.Context, sel ast.S return ret } +func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalInt(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.SelectionSet, v *int) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return graphql.MarshalInt(*v) +} + func (ec *executionContext) marshalOLiveEnnoblement2ᚕᚖgithubᚗcomᚋtribalwarshelpᚋsharedᚋmodelsᚐLiveEnnoblementᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.LiveEnnoblement) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/graphql/resolvers/daily_player_stats.go b/graphql/resolvers/daily_player_stats.go index 0e36283..5ab6761 100644 --- a/graphql/resolvers/daily_player_stats.go +++ b/graphql/resolvers/daily_player_stats.go @@ -3,6 +3,7 @@ package resolvers import ( "context" + "github.com/tribalwarshelp/api/dailyplayerstats" "github.com/tribalwarshelp/api/graphql/generated" "github.com/tribalwarshelp/shared/models" ) @@ -15,9 +16,30 @@ func (r *dailyPlayerStatsRecordResolver) Player(ctx context.Context, obj *models return getPlayer(ctx, obj.PlayerID), nil } -func (r *queryResolver) DailyPlayerStats(ctx context.Context, server string, filter *models.DailyPlayerStatsFilter) (*generated.DailyPlayerStats, error) { +func (r *queryResolver) DailyPlayerStats(ctx context.Context, + server string, + filter *models.DailyPlayerStatsFilter, + limit *int, + offset *int, + sort []string) (*generated.DailyPlayerStats, error) { + defLimit := 0 + defOffset := 0 + if limit == nil { + limit = &defLimit + } + if offset == nil { + offset = &defOffset + } + var err error list := &generated.DailyPlayerStats{} - list.Items, list.Total, err = r.DailyPlayerStatsUcase.Fetch(ctx, server, filter) + list.Items, list.Total, err = r.DailyPlayerStatsUcase.Fetch(ctx, dailyplayerstats.FetchConfig{ + Server: server, + Filter: filter, + Sort: sort, + Limit: *limit, + Offset: *offset, + Count: true, + }) return list, err } diff --git a/graphql/resolvers/daily_tribe_stats.go b/graphql/resolvers/daily_tribe_stats.go index de9e26f..cdc086d 100644 --- a/graphql/resolvers/daily_tribe_stats.go +++ b/graphql/resolvers/daily_tribe_stats.go @@ -3,6 +3,7 @@ package resolvers import ( "context" + "github.com/tribalwarshelp/api/dailytribestats" "github.com/tribalwarshelp/api/graphql/generated" "github.com/tribalwarshelp/shared/models" ) @@ -15,9 +16,30 @@ func (r *dailyTribeStatsRecordResolver) Tribe(ctx context.Context, obj *models.D return getTribe(ctx, obj.TribeID), nil } -func (r *queryResolver) DailyTribeStats(ctx context.Context, server string, filter *models.DailyTribeStatsFilter) (*generated.DailyTribeStats, error) { +func (r *queryResolver) DailyTribeStats(ctx context.Context, + server string, + filter *models.DailyTribeStatsFilter, + limit *int, + offset *int, + sort []string) (*generated.DailyTribeStats, error) { + defLimit := 0 + defOffset := 0 + if limit == nil { + limit = &defLimit + } + if offset == nil { + offset = &defOffset + } + var err error list := &generated.DailyTribeStats{} - list.Items, list.Total, err = r.DailyTribeStatsUcase.Fetch(ctx, server, filter) + list.Items, list.Total, err = r.DailyTribeStatsUcase.Fetch(ctx, dailytribestats.FetchConfig{ + Server: server, + Filter: filter, + Sort: sort, + Limit: *limit, + Offset: *offset, + Count: true, + }) return list, err } diff --git a/graphql/schema/daily_player_stats.graphql b/graphql/schema/daily_player_stats.graphql index edf93fb..bf848b6 100644 --- a/graphql/schema/daily_player_stats.graphql +++ b/graphql/schema/daily_player_stats.graphql @@ -30,14 +30,17 @@ input DailyPlayerStatsFilter { createDateLT: Time createDateLTE: Time - offset: Int - limit: Int - sort: String + offset: Int @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - `offset`.") + limit: Int @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - `limit`.") + sort: String @deprecated(reason: "Use a new variable added to the query dailyPlayerStats - `sort`.") } extend type Query { dailyPlayerStats( server: String! filter: DailyPlayerStatsFilter + limit: Int + offset: Int + sort: [String!] ): DailyPlayerStats! } diff --git a/graphql/schema/daily_tribe_stats.graphql b/graphql/schema/daily_tribe_stats.graphql index 7b3b057..554099e 100644 --- a/graphql/schema/daily_tribe_stats.graphql +++ b/graphql/schema/daily_tribe_stats.graphql @@ -31,14 +31,17 @@ input DailyTribeStatsFilter { createDateLT: Time createDateLTE: Time - offset: Int - limit: Int - sort: String + offset: Int @deprecated(reason: "Use a new variable added to the query dailyTribeStats - `offset`.") + limit: Int @deprecated(reason: "Use a new variable added to the query dailyTribeStats - `limit`.") + sort: String @deprecated(reason: "Use a new variable added to the query dailyTribeStats - `sort`.") } extend type Query { dailyTribeStats( server: String! filter: DailyTribeStatsFilter + limit: Int + offset: Int + sort: [String!] ): DailyTribeStats! } diff --git a/middleware/limit_whitelist.go b/middleware/limit_whitelist.go index e45e883..c317a43 100644 --- a/middleware/limit_whitelist.go +++ b/middleware/limit_whitelist.go @@ -16,24 +16,24 @@ func LimitWhitelist(cfg LimitWhitelistConfig) gin.HandlerFunc { return func(c *gin.Context) { ctx := c.Request.Context() clientIP := c.ClientIP() - mayExceedLimit := false + canExceedLimit := false for _, ip := range cfg.IPAddresses { if ip == clientIP { - mayExceedLimit = true + canExceedLimit = true break } } - ctx = StoreLimitWhitelistDataInContext(ctx, mayExceedLimit) + ctx = StoreLimitWhitelistDataInContext(ctx, canExceedLimit) c.Request = c.Request.WithContext(ctx) c.Next() } } -func StoreLimitWhitelistDataInContext(ctx context.Context, mayExceedLimit bool) context.Context { - return context.WithValue(ctx, limitWhitelistContextKey, mayExceedLimit) +func StoreLimitWhitelistDataInContext(ctx context.Context, canExceedLimit bool) context.Context { + return context.WithValue(ctx, limitWhitelistContextKey, canExceedLimit) } -func MayExceedLimit(ctx context.Context) bool { +func CanExceedLimit(ctx context.Context) bool { whitelisted := ctx.Value(limitWhitelistContextKey) if whitelisted == nil { return false diff --git a/player/usecase/player_usecase.go b/player/usecase/player_usecase.go index d76fae8..55f7692 100644 --- a/player/usecase/player_usecase.go +++ b/player/usecase/player_usecase.go @@ -22,12 +22,12 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.P if filter == nil { filter = &models.PlayerFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > player.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > player.PaginationLimit || filter.Limit <= 0) { filter.Limit = player.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) if filter.TribeFilter != nil { - filter.TribeFilter.Sort = utils.SanitizeSort(filter.TribeFilter.Sort) + filter.TribeFilter.Sort = utils.SanitizeSortExpression(filter.TribeFilter.Sort) } return ucase.repo.Fetch(ctx, player.FetchConfig{ Server: server, diff --git a/playerhistory/usecase/playerhistory_usecase.go b/playerhistory/usecase/playerhistory_usecase.go index 0c58c8c..6da0b7c 100644 --- a/playerhistory/usecase/playerhistory_usecase.go +++ b/playerhistory/usecase/playerhistory_usecase.go @@ -21,10 +21,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.P if filter == nil { filter = &models.PlayerHistoryFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > playerhistory.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > playerhistory.PaginationLimit || filter.Limit <= 0) { filter.Limit = playerhistory.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, playerhistory.FetchConfig{ Server: server, Filter: filter, diff --git a/server/usecase/server_usecase.go b/server/usecase/server_usecase.go index 4b636bb..8d539b0 100644 --- a/server/usecase/server_usecase.go +++ b/server/usecase/server_usecase.go @@ -6,7 +6,6 @@ import ( "github.com/tribalwarshelp/api/middleware" "github.com/tribalwarshelp/api/server" - "github.com/tribalwarshelp/api/utils" "github.com/tribalwarshelp/shared/models" ) @@ -22,7 +21,7 @@ func (ucase *usecase) Fetch(ctx context.Context, filter *models.ServerFilter) ([ if filter == nil { filter = &models.ServerFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > server.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > server.PaginationLimit || filter.Limit <= 0) { filter.Limit = server.PaginationLimit } if len(filter.LangVersionTag) > 0 { @@ -31,7 +30,7 @@ func (ucase *usecase) Fetch(ctx context.Context, filter *models.ServerFilter) ([ if len(filter.LangVersionTagNEQ) > 0 { filter.VersionCodeNEQ = append(filter.VersionCode, filter.LangVersionTagNEQ...) } - filter.Sort = utils.SanitizeSort(filter.Sort) + // filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, server.FetchConfig{ Count: true, Filter: filter, diff --git a/serverstats/usecase/serverstats_usecase.go b/serverstats/usecase/serverstats_usecase.go index 3c944a6..93d8ac2 100644 --- a/serverstats/usecase/serverstats_usecase.go +++ b/serverstats/usecase/serverstats_usecase.go @@ -21,10 +21,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.S if filter == nil { filter = &models.ServerStatsFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > serverstats.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > serverstats.PaginationLimit || filter.Limit <= 0) { filter.Limit = serverstats.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, serverstats.FetchConfig{ Server: server, Filter: filter, diff --git a/tribe/usecase/tribe_usecase.go b/tribe/usecase/tribe_usecase.go index 4cd3900..3f240ef 100644 --- a/tribe/usecase/tribe_usecase.go +++ b/tribe/usecase/tribe_usecase.go @@ -22,10 +22,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.T if filter == nil { filter = &models.TribeFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > tribe.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > tribe.PaginationLimit || filter.Limit <= 0) { filter.Limit = tribe.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, tribe.FetchConfig{ Filter: filter, Server: server, diff --git a/tribechange/usecase/tribechange_usecase.go b/tribechange/usecase/tribechange_usecase.go index 1453f2b..a43a8e0 100644 --- a/tribechange/usecase/tribechange_usecase.go +++ b/tribechange/usecase/tribechange_usecase.go @@ -21,10 +21,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.T if filter == nil { filter = &models.TribeChangeFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > tribechange.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > tribechange.PaginationLimit || filter.Limit <= 0) { filter.Limit = tribechange.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, tribechange.FetchConfig{ Server: server, Filter: filter, diff --git a/tribehistory/usecase/tribehistory_usecase.go b/tribehistory/usecase/tribehistory_usecase.go index d296f2d..a6d0411 100644 --- a/tribehistory/usecase/tribehistory_usecase.go +++ b/tribehistory/usecase/tribehistory_usecase.go @@ -21,10 +21,10 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.T if filter == nil { filter = &models.TribeHistoryFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > tribehistory.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > tribehistory.PaginationLimit || filter.Limit <= 0) { filter.Limit = tribehistory.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, tribehistory.FetchConfig{ Server: server, Filter: filter, diff --git a/utils/find_string_with_prefix.go b/utils/find_string_with_prefix.go new file mode 100644 index 0000000..4a4bd45 --- /dev/null +++ b/utils/find_string_with_prefix.go @@ -0,0 +1,12 @@ +package utils + +import "strings" + +func FindStringWithPrefix(sl []string, prefix string) string { + for _, s := range sl { + if strings.HasPrefix(s, prefix) { + return s + } + } + return "" +} diff --git a/utils/sanitize_sort.go b/utils/sanitize_sort.go index 7a990dd..ebc0f87 100644 --- a/utils/sanitize_sort.go +++ b/utils/sanitize_sort.go @@ -6,19 +6,37 @@ import ( ) var ( - sortRegex = regexp.MustCompile(`^[\p{L}\_]+$`) + sortexprRegex = regexp.MustCompile(`^[\p{L}\_\.]+$`) ) -func SanitizeSort(sort string) string { - trimmed := strings.TrimSpace(sort) +func SanitizeSortExpression(expr string) string { + trimmed := strings.TrimSpace(expr) splitted := strings.Split(trimmed, " ") length := len(splitted) - if length != 2 || !sortRegex.Match([]byte(splitted[0])) { + if length != 2 || !sortexprRegex.Match([]byte(splitted[0])) { return "" } + table := "" + column := splitted[0] + if strings.Contains(splitted[0], ".") { + columnAndTable := strings.Split(splitted[0], ".") + table = columnAndTable[0] + "." + column = columnAndTable[1] + } keyword := "ASC" if strings.ToUpper(splitted[1]) == "DESC" { keyword = "DESC" } - return strings.ToLower(Underscore(splitted[0])) + " " + keyword + return strings.ToLower(table+Underscore(column)) + " " + keyword +} + +func SanitizeSortExpressions(exprs []string) []string { + filtered := []string{} + for _, expr := range exprs { + sanitized := SanitizeSortExpression(expr) + if sanitized != "" { + filtered = append(filtered, sanitized) + } + } + return filtered } diff --git a/version/usecase/version_usecase.go b/version/usecase/version_usecase.go index 1a2228b..cbf6c4b 100644 --- a/version/usecase/version_usecase.go +++ b/version/usecase/version_usecase.go @@ -25,7 +25,7 @@ func (ucase *usecase) Fetch(ctx context.Context, filter *models.VersionFilter) ( if filter == nil { filter = &models.VersionFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > version.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > version.PaginationLimit || filter.Limit <= 0) { filter.Limit = version.PaginationLimit } if len(filter.Tag) > 0 { @@ -34,7 +34,7 @@ func (ucase *usecase) Fetch(ctx context.Context, filter *models.VersionFilter) ( if len(filter.TagNEQ) > 0 { filter.CodeNEQ = append(filter.Code, filter.TagNEQ...) } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) return ucase.repo.Fetch(ctx, version.FetchConfig{ Filter: filter, Count: true, diff --git a/village/usecase/village_usecase.go b/village/usecase/village_usecase.go index ff926d4..1dac79b 100644 --- a/village/usecase/village_usecase.go +++ b/village/usecase/village_usecase.go @@ -22,14 +22,14 @@ func (ucase *usecase) Fetch(ctx context.Context, server string, filter *models.V if filter == nil { filter = &models.VillageFilter{} } - if !middleware.MayExceedLimit(ctx) && (filter.Limit > village.PaginationLimit || filter.Limit <= 0) { + if !middleware.CanExceedLimit(ctx) && (filter.Limit > village.PaginationLimit || filter.Limit <= 0) { filter.Limit = village.PaginationLimit } - filter.Sort = utils.SanitizeSort(filter.Sort) + filter.Sort = utils.SanitizeSortExpression(filter.Sort) if filter.PlayerFilter != nil { - filter.PlayerFilter.Sort = utils.SanitizeSort(filter.PlayerFilter.Sort) + filter.PlayerFilter.Sort = utils.SanitizeSortExpression(filter.PlayerFilter.Sort) if filter.PlayerFilter.TribeFilter != nil { - filter.PlayerFilter.TribeFilter.Sort = utils.SanitizeSort(filter.PlayerFilter.TribeFilter.Sort) + filter.PlayerFilter.TribeFilter.Sort = utils.SanitizeSortExpression(filter.PlayerFilter.TribeFilter.Sort) } } return ucase.repo.Fetch(ctx, village.FetchConfig{