rename utils.SanitizeSortExpressions -> utils.SanitizeSorts, add query complexity limit
This commit is contained in:
parent
44372ecc81
commit
95dbbc4d8a
|
@ -1,4 +1,4 @@
|
|||
.env.development
|
||||
.env.production
|
||||
.env
|
||||
.netrc
|
||||
.idea
|
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
|||
generate-graphql-schema:
|
||||
gqlgen:
|
||||
bash ./scripts/gqlgen-generate.sh
|
||||
dev:
|
||||
bash ./scripts/dev.sh
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg dailyplayerstats.FetchConfi
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > dailyplayerstats.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = dailyplayerstats.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg dailytribestats.FetchConfig
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > dailytribestats.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = dailytribestats.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg ennoblement.FetchConfig) ([
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > ennoblement.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = ennoblement.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package httpdelivery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/tribalwarshelp/api/middleware"
|
||||
"github.com/tribalwarshelp/shared/models"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
|
@ -15,7 +19,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
playgroundTTL = time.Hour / time.Second
|
||||
endpointMain = "/graphql"
|
||||
endpointPlayground = "/"
|
||||
playgroundTTL = time.Hour / time.Second
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -27,16 +33,15 @@ func Attach(cfg Config) error {
|
|||
if cfg.Resolver == nil {
|
||||
return fmt.Errorf("Graphql resolver cannot be nil")
|
||||
}
|
||||
gqlHandler := graphqlHandler(cfg.Resolver)
|
||||
cfg.RouterGroup.GET("/graphql", gqlHandler)
|
||||
cfg.RouterGroup.POST("/graphql", gqlHandler)
|
||||
cfg.RouterGroup.GET("/", playgroundHandler())
|
||||
gqlHandler := graphqlHandler(prepareConfig(cfg.Resolver))
|
||||
cfg.RouterGroup.GET(endpointMain, gqlHandler)
|
||||
cfg.RouterGroup.POST(endpointMain, gqlHandler)
|
||||
cfg.RouterGroup.GET(endpointPlayground, playgroundHandler())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Defining the GraphQL handler
|
||||
func graphqlHandler(r *resolvers.Resolver) gin.HandlerFunc {
|
||||
cfg := generated.Config{Resolvers: r}
|
||||
func graphqlHandler(cfg generated.Config) gin.HandlerFunc {
|
||||
srv := handler.New(generated.NewExecutableSchema(cfg))
|
||||
|
||||
srv.AddTransport(transport.GET{})
|
||||
|
@ -46,6 +51,15 @@ func graphqlHandler(r *resolvers.Resolver) gin.HandlerFunc {
|
|||
srv.Use(extension.AutomaticPersistedQuery{
|
||||
Cache: lru.New(100),
|
||||
})
|
||||
srv.SetQueryCache(lru.New(1000))
|
||||
srv.Use(&extension.ComplexityLimit{
|
||||
Func: func(ctx context.Context, rc *graphql.OperationContext) int {
|
||||
if middleware.CanExceedLimit(ctx) {
|
||||
return 500000
|
||||
}
|
||||
return 1000
|
||||
},
|
||||
})
|
||||
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Cache-Control", "no-store, must-revalidate")
|
||||
|
@ -55,10 +69,167 @@ func graphqlHandler(r *resolvers.Resolver) gin.HandlerFunc {
|
|||
|
||||
// Defining the Playground handler
|
||||
func playgroundHandler() gin.HandlerFunc {
|
||||
h := playground.Handler("Playground", "/graphql")
|
||||
h := playground.Handler("Playground", endpointMain)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Cache-Control", fmt.Sprintf(`public, max-age=%d`, playgroundTTL))
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareConfig(r *resolvers.Resolver) generated.Config {
|
||||
return generated.Config{
|
||||
Resolvers: r,
|
||||
Complexity: getComplexityRoot(),
|
||||
}
|
||||
}
|
||||
|
||||
func getComplexityRoot() generated.ComplexityRoot {
|
||||
complexityRoot := generated.ComplexityRoot{}
|
||||
complexityRoot.Player.NameChanges = func(childComplexity int) int {
|
||||
return 50 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.DailyPlayerStats = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.DailyPlayerStatsFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 300 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.DailyTribeStats = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.DailyTribeStatsFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 300 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Ennoblements = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.EnnoblementFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 300 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.PlayerHistory = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.PlayerHistoryFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 150 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.TribeHistory = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.TribeHistoryFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 100 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.TribeChanges = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.TribeChangeFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 300 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.SearchPlayer = func(
|
||||
childComplexity int,
|
||||
version string,
|
||||
name *string,
|
||||
id *int,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 400 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.SearchTribe = func(
|
||||
childComplexity int,
|
||||
version string,
|
||||
query string,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 400 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Players = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.PlayerFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 150 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Tribes = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.TribeFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 100 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Villages = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.VillageFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 300 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.ServerStats = func(
|
||||
childComplexity int,
|
||||
server string,
|
||||
filter *models.ServerStatsFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 100 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Server = func(childComplexity int, key string) int {
|
||||
return 50 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Servers = func(
|
||||
childComplexity int,
|
||||
filter *models.ServerFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 250 + childComplexity
|
||||
}
|
||||
complexityRoot.Query.Versions = func(
|
||||
childComplexity int,
|
||||
filter *models.VersionFilter,
|
||||
limit *int,
|
||||
offset *int,
|
||||
sort []string,
|
||||
) int {
|
||||
return 100 + childComplexity
|
||||
}
|
||||
return complexityRoot
|
||||
}
|
||||
|
|
25
main.go
25
main.go
|
@ -74,7 +74,7 @@ func main() {
|
|||
})
|
||||
defer func() {
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal("Database disconnecting:", err)
|
||||
log.Fatal("While disconnecting from the database:", err)
|
||||
}
|
||||
}()
|
||||
if strings.ToUpper(os.Getenv("LOG_DB_QUERIES")) == "TRUE" {
|
||||
|
@ -124,15 +124,19 @@ func main() {
|
|||
ServerUsecase: serverUcase,
|
||||
})
|
||||
graphql := router.Group("")
|
||||
graphql.Use(middleware.DataLoadersToContext(middleware.DataLoadersToContextConfig{
|
||||
ServerRepo: serverRepo,
|
||||
},
|
||||
dataloaders.Config{
|
||||
PlayerRepo: playerRepo,
|
||||
TribeRepo: tribeRepo,
|
||||
VillageRepo: villageRepo,
|
||||
VersionRepo: versionRepo,
|
||||
}))
|
||||
graphql.Use(
|
||||
middleware.DataLoadersToContext(
|
||||
middleware.DataLoadersToContextConfig{
|
||||
ServerRepo: serverRepo,
|
||||
},
|
||||
dataloaders.Config{
|
||||
PlayerRepo: playerRepo,
|
||||
TribeRepo: tribeRepo,
|
||||
VillageRepo: villageRepo,
|
||||
VersionRepo: versionRepo,
|
||||
},
|
||||
),
|
||||
)
|
||||
graphql.Use(middleware.LimitWhitelist(middleware.LimitWhitelistConfig{
|
||||
IPAddresses: strings.Split(os.Getenv("LIMIT_WHITELIST"), ","),
|
||||
}))
|
||||
|
@ -160,7 +164,6 @@ func main() {
|
|||
}
|
||||
|
||||
go func() {
|
||||
// service connections
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
}
|
||||
|
|
|
@ -31,16 +31,16 @@ func DataLoadersToContext(dltcc DataLoadersToContextConfig, cfg dataloaders.Conf
|
|||
Columns: []string{utils.Underscore("versionCode"), "key"},
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, &gqlerror.Error{
|
||||
c.JSON(http.StatusInternalServerError, &gqlerror.Error{
|
||||
Message: err.Error(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
for _, server := range servers {
|
||||
serverDataLoaders[server.Key] = dataloaders.NewServerDataLoaders(server.Key, cfg)
|
||||
if _, ok := versionDataLoaders[server.VersionCode]; !ok {
|
||||
versionDataLoaders[server.VersionCode] = dataloaders.NewVersionDataLoaders(server.VersionCode, cfg)
|
||||
for _, serv := range servers {
|
||||
serverDataLoaders[serv.Key] = dataloaders.NewServerDataLoaders(serv.Key, cfg)
|
||||
if _, ok := versionDataLoaders[serv.VersionCode]; !ok {
|
||||
versionDataLoaders[serv.VersionCode] = dataloaders.NewVersionDataLoaders(serv.VersionCode, cfg)
|
||||
}
|
||||
}
|
||||
ctx = StoreServerDataLoadersInContext(ctx, serverDataLoaders)
|
||||
|
|
|
@ -27,7 +27,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg player.FetchConfig) ([]*mod
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > player.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = player.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,6 @@ func (ucase *usecase) SearchPlayer(ctx context.Context, cfg player.SearchPlayerC
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > player.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = player.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.SearchPlayer(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg playerhistory.FetchConfig)
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > playerhistory.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = playerhistory.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg server.FetchConfig) ([]*mod
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > server.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = server.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg serverstats.FetchConfig) ([
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > serverstats.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = serverstats.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg tribe.FetchConfig) ([]*mode
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > tribe.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = tribe.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,6 @@ func (ucase *usecase) SearchTribe(ctx context.Context, cfg tribe.SearchTribeConf
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > tribe.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = tribe.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.SearchTribe(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg tribechange.FetchConfig) ([
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > tribechange.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = tribechange.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg tribehistory.FetchConfig) (
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > tribehistory.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = tribehistory.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
|
|
@ -9,9 +9,8 @@ var (
|
|||
sortexprRegex = regexp.MustCompile(`^[\p{L}\_\.]+$`)
|
||||
)
|
||||
|
||||
func SanitizeSortExpression(expr string) string {
|
||||
trimmed := strings.TrimSpace(expr)
|
||||
splitted := strings.Split(trimmed, " ")
|
||||
func SanitizeSort(expr string) string {
|
||||
splitted := strings.Split(strings.TrimSpace(expr), " ")
|
||||
length := len(splitted)
|
||||
if length != 2 || !sortexprRegex.Match([]byte(splitted[0])) {
|
||||
return ""
|
||||
|
@ -30,13 +29,13 @@ func SanitizeSortExpression(expr string) string {
|
|||
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)
|
||||
func SanitizeSorts(sorts []string) []string {
|
||||
sanitized := []string{}
|
||||
for _, sort := range sorts {
|
||||
sanitizedSort := SanitizeSort(sort)
|
||||
if sanitizedSort != "" {
|
||||
sanitized = append(sanitized, sanitizedSort)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
return sanitized
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg version.FetchConfig) ([]*mo
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > version.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = version.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ func (ucase *usecase) Fetch(ctx context.Context, cfg village.FetchConfig) ([]*mo
|
|||
if !middleware.CanExceedLimit(ctx) && (cfg.Limit > village.PaginationLimit || cfg.Limit <= 0) {
|
||||
cfg.Limit = village.PaginationLimit
|
||||
}
|
||||
cfg.Sort = utils.SanitizeSortExpressions(cfg.Sort)
|
||||
cfg.Sort = utils.SanitizeSorts(cfg.Sort)
|
||||
return ucase.repo.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue