From be52ae19c9118296c52c18708c9abe13b93ca1c1 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Thu, 18 Jun 2020 16:44:02 +0200 Subject: [PATCH] add langversionloader, update dataloaders_to_context middleware, add to server graphql type LangVersion field --- go.mod | 5 +- go.sum | 15 ++ graphql/dataloaders/data_loaders.go | 56 +++++ graphql/dataloaders/langversionloader_gen.go | 224 ++++++++++++++++++ ...{dataloaders.go => server_data_loaders.go} | 15 +- graphql/generated/generated.go | 55 +++-- graphql/resolvers/ennoblement.go | 6 +- graphql/resolvers/player.go | 2 +- graphql/resolvers/resolver.go | 2 + graphql/resolvers/server.go | 11 + graphql/resolvers/village.go | 2 +- graphql/schema/server.graphql | 2 +- main.go | 7 +- middleware/dataloaders_to_context.go | 28 ++- 14 files changed, 378 insertions(+), 52 deletions(-) create mode 100644 graphql/dataloaders/data_loaders.go create mode 100644 graphql/dataloaders/langversionloader_gen.go rename graphql/dataloaders/{dataloaders.go => server_data_loaders.go} (85%) diff --git a/go.mod b/go.mod index b6316ef..1ba936d 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,12 @@ require ( github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pkg/errors v0.9.1 github.com/segmentio/encoding v0.1.13 // indirect - github.com/tribalwarshelp/shared v0.0.0-20200607152914-8ab83c6d1364 + github.com/tribalwarshelp/shared v0.0.0-20200618135703-8b0fda79025b + github.com/vektah/dataloaden v0.3.0 // indirect github.com/vektah/gqlparser/v2 v2.0.1 + golang.org/x/mod v0.3.0 // indirect golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34 // indirect + golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index db1bf03..b4429d5 100644 --- a/go.sum +++ b/go.sum @@ -170,6 +170,8 @@ github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYm github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/tribalwarshelp/shared v0.0.0-20200607152914-8ab83c6d1364 h1:Pi3n/0FkrHrBO0oW9Lp6kRAEc4qKe7p8lohrEv4GOXE= github.com/tribalwarshelp/shared v0.0.0-20200607152914-8ab83c6d1364/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y= +github.com/tribalwarshelp/shared v0.0.0-20200618135703-8b0fda79025b h1:megohIF+3rsva+9CNxlhdAbMKX0kjUaig2P5TSxH0fQ= +github.com/tribalwarshelp/shared v0.0.0-20200618135703-8b0fda79025b/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -177,6 +179,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84= +github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o= github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= github.com/vmihailenco/bufpool v0.1.5/go.mod h1:fL9i/PRTuS7AELqAHwSU1Zf1c70xhkhGe/cD5ud9pJk= @@ -191,6 +195,7 @@ github.com/vmihailenco/msgpack/v5 v5.0.0-alpha.2/go.mod h1:LDfrk4wJpSFwkzNOJxrCW github.com/vmihailenco/tagparser v0.1.0/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v0.6.0 h1:+vkHm/XwJ7ekpISV2Ixew93gCrxTbuwTF5rSewnLLgw= go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -207,6 +212,10 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -220,6 +229,7 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222033325-078779b8f2d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -229,6 +239,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -252,8 +263,12 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM= golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2 h1:FD4wDsP+CQUqh2V12OBOt90pLHVToe58P++fUu3ggV4= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/graphql/dataloaders/data_loaders.go b/graphql/dataloaders/data_loaders.go new file mode 100644 index 0000000..6de14ec --- /dev/null +++ b/graphql/dataloaders/data_loaders.go @@ -0,0 +1,56 @@ +package dataloaders + +import ( + "context" + "time" + + "github.com/tribalwarshelp/api/langversion" + "github.com/tribalwarshelp/api/player" + "github.com/tribalwarshelp/api/tribe" + "github.com/tribalwarshelp/api/village" + "github.com/tribalwarshelp/shared/models" +) + +type DataLoaders struct { + LangVersionByTag LangVersionLoader +} + +type Config struct { + PlayerRepo player.Repository + TribeRepo tribe.Repository + VillageRepo village.Repository + LangVersionRepo langversion.Repository +} + +func New(cfg Config) *DataLoaders { + return &DataLoaders{ + LangVersionByTag: LangVersionLoader{ + wait: 2 * time.Millisecond, + maxBatch: 0, + fetch: func(tagsS []string) ([]*models.LangVersion, []error) { + tags := []models.LanguageTag{} + for _, tag := range tagsS { + tags = append(tags, models.LanguageTag(tag)) + } + langVersions, _, err := cfg.LangVersionRepo.Fetch(context.Background(), &models.LangVersionFilter{ + Tag: tags, + }) + if err != nil { + return nil, []error{err} + } + + langVersionByTag := make(map[models.LanguageTag]*models.LangVersion) + for _, langVersion := range langVersions { + langVersionByTag[langVersion.Tag] = langVersion + } + + inOrder := make([]*models.LangVersion, len(tagsS)) + for i, tag := range tags { + inOrder[i] = langVersionByTag[tag] + } + + return inOrder, nil + }, + }, + } +} diff --git a/graphql/dataloaders/langversionloader_gen.go b/graphql/dataloaders/langversionloader_gen.go new file mode 100644 index 0000000..171c3fd --- /dev/null +++ b/graphql/dataloaders/langversionloader_gen.go @@ -0,0 +1,224 @@ +// Code generated by github.com/vektah/dataloaden, DO NOT EDIT. + +package dataloaders + +import ( + "sync" + "time" + + "github.com/tribalwarshelp/shared/models" +) + +// LangVersionLoaderConfig captures the config to create a new LangVersionLoader +type LangVersionLoaderConfig struct { + // Fetch is a method that provides the data for the loader + Fetch func(keys []string) ([]*models.LangVersion, []error) + + // Wait is how long wait before sending a batch + Wait time.Duration + + // MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit + MaxBatch int +} + +// NewLangVersionLoader creates a new LangVersionLoader given a fetch, wait, and maxBatch +func NewLangVersionLoader(config LangVersionLoaderConfig) *LangVersionLoader { + return &LangVersionLoader{ + fetch: config.Fetch, + wait: config.Wait, + maxBatch: config.MaxBatch, + } +} + +// LangVersionLoader batches and caches requests +type LangVersionLoader struct { + // this method provides the data for the loader + fetch func(keys []string) ([]*models.LangVersion, []error) + + // how long to done before sending a batch + wait time.Duration + + // this will limit the maximum number of keys to send in one batch, 0 = no limit + maxBatch int + + // INTERNAL + + // lazily created cache + cache map[string]*models.LangVersion + + // the current batch. keys will continue to be collected until timeout is hit, + // then everything will be sent to the fetch method and out to the listeners + batch *langVersionLoaderBatch + + // mutex to prevent races + mu sync.Mutex +} + +type langVersionLoaderBatch struct { + keys []string + data []*models.LangVersion + error []error + closing bool + done chan struct{} +} + +// Load a LangVersion by key, batching and caching will be applied automatically +func (l *LangVersionLoader) Load(key string) (*models.LangVersion, error) { + return l.LoadThunk(key)() +} + +// LoadThunk returns a function that when called will block waiting for a LangVersion. +// This method should be used if you want one goroutine to make requests to many +// different data loaders without blocking until the thunk is called. +func (l *LangVersionLoader) LoadThunk(key string) func() (*models.LangVersion, error) { + l.mu.Lock() + if it, ok := l.cache[key]; ok { + l.mu.Unlock() + return func() (*models.LangVersion, error) { + return it, nil + } + } + if l.batch == nil { + l.batch = &langVersionLoaderBatch{done: make(chan struct{})} + } + batch := l.batch + pos := batch.keyIndex(l, key) + l.mu.Unlock() + + return func() (*models.LangVersion, error) { + <-batch.done + + var data *models.LangVersion + if pos < len(batch.data) { + data = batch.data[pos] + } + + var err error + // its convenient to be able to return a single error for everything + if len(batch.error) == 1 { + err = batch.error[0] + } else if batch.error != nil { + err = batch.error[pos] + } + + if err == nil { + l.mu.Lock() + l.unsafeSet(key, data) + l.mu.Unlock() + } + + return data, err + } +} + +// LoadAll fetches many keys at once. It will be broken into appropriate sized +// sub batches depending on how the loader is configured +func (l *LangVersionLoader) LoadAll(keys []string) ([]*models.LangVersion, []error) { + results := make([]func() (*models.LangVersion, error), len(keys)) + + for i, key := range keys { + results[i] = l.LoadThunk(key) + } + + langVersions := make([]*models.LangVersion, len(keys)) + errors := make([]error, len(keys)) + for i, thunk := range results { + langVersions[i], errors[i] = thunk() + } + return langVersions, errors +} + +// LoadAllThunk returns a function that when called will block waiting for a LangVersions. +// This method should be used if you want one goroutine to make requests to many +// different data loaders without blocking until the thunk is called. +func (l *LangVersionLoader) LoadAllThunk(keys []string) func() ([]*models.LangVersion, []error) { + results := make([]func() (*models.LangVersion, error), len(keys)) + for i, key := range keys { + results[i] = l.LoadThunk(key) + } + return func() ([]*models.LangVersion, []error) { + langVersions := make([]*models.LangVersion, len(keys)) + errors := make([]error, len(keys)) + for i, thunk := range results { + langVersions[i], errors[i] = thunk() + } + return langVersions, errors + } +} + +// Prime the cache with the provided key and value. If the key already exists, no change is made +// and false is returned. +// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).) +func (l *LangVersionLoader) Prime(key string, value *models.LangVersion) bool { + l.mu.Lock() + var found bool + if _, found = l.cache[key]; !found { + // make a copy when writing to the cache, its easy to pass a pointer in from a loop var + // and end up with the whole cache pointing to the same value. + cpy := *value + l.unsafeSet(key, &cpy) + } + l.mu.Unlock() + return !found +} + +// Clear the value at key from the cache, if it exists +func (l *LangVersionLoader) Clear(key string) { + l.mu.Lock() + delete(l.cache, key) + l.mu.Unlock() +} + +func (l *LangVersionLoader) unsafeSet(key string, value *models.LangVersion) { + if l.cache == nil { + l.cache = map[string]*models.LangVersion{} + } + l.cache[key] = value +} + +// keyIndex will return the location of the key in the batch, if its not found +// it will add the key to the batch +func (b *langVersionLoaderBatch) keyIndex(l *LangVersionLoader, key string) int { + for i, existingKey := range b.keys { + if key == existingKey { + return i + } + } + + pos := len(b.keys) + b.keys = append(b.keys, key) + if pos == 0 { + go b.startTimer(l) + } + + if l.maxBatch != 0 && pos >= l.maxBatch-1 { + if !b.closing { + b.closing = true + l.batch = nil + go b.end(l) + } + } + + return pos +} + +func (b *langVersionLoaderBatch) startTimer(l *LangVersionLoader) { + time.Sleep(l.wait) + l.mu.Lock() + + // we must have hit a batch limit and are already finalizing this batch + if b.closing { + l.mu.Unlock() + return + } + + l.batch = nil + l.mu.Unlock() + + b.end(l) +} + +func (b *langVersionLoaderBatch) end(l *LangVersionLoader) { + b.data, b.error = l.fetch(b.keys) + close(b.done) +} diff --git a/graphql/dataloaders/dataloaders.go b/graphql/dataloaders/server_data_loaders.go similarity index 85% rename from graphql/dataloaders/dataloaders.go rename to graphql/dataloaders/server_data_loaders.go index 09d327f..5a2e929 100644 --- a/graphql/dataloaders/dataloaders.go +++ b/graphql/dataloaders/server_data_loaders.go @@ -4,26 +4,17 @@ import ( "context" "time" - "github.com/tribalwarshelp/api/player" - "github.com/tribalwarshelp/api/tribe" - "github.com/tribalwarshelp/api/village" "github.com/tribalwarshelp/shared/models" ) -type DataLoaders struct { +type ServerDataLoaders struct { PlayerByID PlayerLoader TribeByID TribeLoader VillageByID VillageLoader } -type Config struct { - PlayerRepo player.Repository - TribeRepo tribe.Repository - VillageRepo village.Repository -} - -func New(server string, cfg Config) *DataLoaders { - return &DataLoaders{ +func NewServerDataLoaders(server string, cfg Config) *ServerDataLoaders { + return &ServerDataLoaders{ PlayerByID: PlayerLoader{ wait: 2 * time.Millisecond, maxBatch: 0, diff --git a/graphql/generated/generated.go b/graphql/generated/generated.go index 2eb5a74..dc14068 100644 --- a/graphql/generated/generated.go +++ b/graphql/generated/generated.go @@ -39,6 +39,7 @@ type ResolverRoot interface { Ennoblement() EnnoblementResolver Player() PlayerResolver Query() QueryResolver + Server() ServerResolver Village() VillageResolver } @@ -103,10 +104,10 @@ type ComplexityRoot struct { } Server struct { - ID func(childComplexity int) int - Key func(childComplexity int) int - LangVersionTag func(childComplexity int) int - Status func(childComplexity int) int + ID func(childComplexity int) int + Key func(childComplexity int) int + LangVersion func(childComplexity int) int + Status func(childComplexity int) int } ServersList struct { @@ -174,6 +175,9 @@ type QueryResolver interface { Villages(ctx context.Context, server string, filter *models.VillageFilter) (*VillagesList, error) Village(ctx context.Context, server string, id int) (*models.Village, error) } +type ServerResolver interface { + LangVersion(ctx context.Context, obj *models.Server) (*models.LangVersion, error) +} type VillageResolver interface { Player(ctx context.Context, obj *models.Village) (*models.Player, error) } @@ -528,12 +532,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Server.Key(childComplexity), true - case "Server.langVersionTag": - if e.complexity.Server.LangVersionTag == nil { + case "Server.langVersion": + if e.complexity.Server.LangVersion == nil { break } - return e.complexity.Server.LangVersionTag(childComplexity), true + return e.complexity.Server.LangVersion(childComplexity), true case "Server.status": if e.complexity.Server.Status == nil { @@ -964,7 +968,7 @@ type Server { id: Int! key: String! status: ServerStatus! - langVersionTag: LanguageTag! + langVersion: LangVersion @goField(forceResolver: true) } type ServersList { @@ -2923,7 +2927,7 @@ func (ec *executionContext) _Server_status(ctx context.Context, field graphql.Co return ec.marshalNServerStatus2githubᚗcomᚋtribalwarshelpᚋsharedᚋmodelsᚐServerStatus(ctx, field.Selections, res) } -func (ec *executionContext) _Server_langVersionTag(ctx context.Context, field graphql.CollectedField, obj *models.Server) (ret graphql.Marshaler) { +func (ec *executionContext) _Server_langVersion(ctx context.Context, field graphql.CollectedField, obj *models.Server) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2934,27 +2938,24 @@ func (ec *executionContext) _Server_langVersionTag(ctx context.Context, field gr Object: "Server", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.LangVersionTag, nil + return ec.resolvers.Server().LangVersion(rctx, obj) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(models.LanguageTag) + res := resTmp.(*models.LangVersion) fc.Result = res - return ec.marshalNLanguageTag2githubᚗcomᚋtribalwarshelpᚋsharedᚋmodelsᚐLanguageTag(ctx, field.Selections, res) + return ec.marshalOLangVersion2ᚖgithubᚗcomᚋtribalwarshelpᚋsharedᚋmodelsᚐLangVersion(ctx, field.Selections, res) } func (ec *executionContext) _ServersList_items(ctx context.Context, field graphql.CollectedField, obj *ServersList) (ret graphql.Marshaler) { @@ -6525,23 +6526,29 @@ func (ec *executionContext) _Server(ctx context.Context, sel ast.SelectionSet, o case "id": out.Values[i] = ec._Server_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "key": out.Values[i] = ec._Server_key(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "status": out.Values[i] = ec._Server_status(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ - } - case "langVersionTag": - out.Values[i] = ec._Server_langVersionTag(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } + case "langVersion": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Server_langVersion(ctx, field, obj) + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/graphql/resolvers/ennoblement.go b/graphql/resolvers/ennoblement.go index 4ed5d66..c192e7a 100644 --- a/graphql/resolvers/ennoblement.go +++ b/graphql/resolvers/ennoblement.go @@ -14,7 +14,7 @@ func (r *ennoblementResolver) NewOwner(ctx context.Context, obj *models.Ennoblem } if server, ok := getServer(graphql.GetFieldContext(ctx)); ok { - dataloaders := middleware.DataLoadersFromContext(ctx) + dataloaders := middleware.ServerDataLoadersFromContext(ctx) if dataloaders != nil { if dataloader, ok := dataloaders[server]; ok { player, _ := dataloader.PlayerByID.Load(obj.NewOwnerID) @@ -34,7 +34,7 @@ func (r *ennoblementResolver) OldOwner(ctx context.Context, obj *models.Ennoblem } if server, ok := getServer(graphql.GetFieldContext(ctx)); ok { - dataloaders := middleware.DataLoadersFromContext(ctx) + dataloaders := middleware.ServerDataLoadersFromContext(ctx) if dataloaders != nil { if dataloader, ok := dataloaders[server]; ok { player, _ := dataloader.PlayerByID.Load(obj.OldOwnerID) @@ -54,7 +54,7 @@ func (r *ennoblementResolver) Village(ctx context.Context, obj *models.Ennobleme } if server, ok := getServer(graphql.GetFieldContext(ctx)); ok { - dataloaders := middleware.DataLoadersFromContext(ctx) + dataloaders := middleware.ServerDataLoadersFromContext(ctx) if dataloaders != nil { if dataloader, ok := dataloaders[server]; ok { village, _ := dataloader.VillageByID.Load(obj.VillageID) diff --git a/graphql/resolvers/player.go b/graphql/resolvers/player.go index 2d67297..138b16b 100644 --- a/graphql/resolvers/player.go +++ b/graphql/resolvers/player.go @@ -15,7 +15,7 @@ func (r *playerResolver) Tribe(ctx context.Context, obj *models.Player) (*models } if server, ok := getServer(graphql.GetFieldContext(ctx)); ok { - dataloaders := middleware.DataLoadersFromContext(ctx) + dataloaders := middleware.ServerDataLoadersFromContext(ctx) if dataloaders != nil { if dataloader, ok := dataloaders[server]; ok { tribe, _ := dataloader.TribeByID.Load(obj.TribeID) diff --git a/graphql/resolvers/resolver.go b/graphql/resolvers/resolver.go index 7a0003c..97fe449 100644 --- a/graphql/resolvers/resolver.go +++ b/graphql/resolvers/resolver.go @@ -24,8 +24,10 @@ func (r *Resolver) Query() generated.QueryResolver { return &queryRe func (r *Resolver) Player() generated.PlayerResolver { return &playerResolver{r} } func (r *Resolver) Village() generated.VillageResolver { return &villageResolver{r} } func (r *Resolver) Ennoblement() generated.EnnoblementResolver { return &ennoblementResolver{r} } +func (r *Resolver) Server() generated.ServerResolver { return &serverResolver{r} } type queryResolver struct{ *Resolver } type playerResolver struct{ *Resolver } type villageResolver struct{ *Resolver } type ennoblementResolver struct{ *Resolver } +type serverResolver struct{ *Resolver } diff --git a/graphql/resolvers/server.go b/graphql/resolvers/server.go index 45e7cd5..59ec522 100644 --- a/graphql/resolvers/server.go +++ b/graphql/resolvers/server.go @@ -3,10 +3,21 @@ package resolvers import ( "context" + "github.com/tribalwarshelp/api/middleware" + "github.com/tribalwarshelp/api/graphql/generated" "github.com/tribalwarshelp/shared/models" ) +func (r *serverResolver) LangVersion(ctx context.Context, obj *models.Server) (*models.LangVersion, error) { + loaders := middleware.DataLoadersFromContext(ctx) + if loaders != nil { + lv, _ := loaders.LangVersionByTag.Load(obj.LangVersionTag.String()) + return lv, nil + } + return nil, nil +} + func (r *queryResolver) Servers(ctx context.Context, filter *models.ServerFilter) (*generated.ServersList, error) { var err error list := &generated.ServersList{} diff --git a/graphql/resolvers/village.go b/graphql/resolvers/village.go index 5e88661..6ec85bd 100644 --- a/graphql/resolvers/village.go +++ b/graphql/resolvers/village.go @@ -15,7 +15,7 @@ func (r *villageResolver) Player(ctx context.Context, obj *models.Village) (*mod } if server, ok := getServer(graphql.GetFieldContext(ctx)); ok { - dataloaders := middleware.DataLoadersFromContext(ctx) + dataloaders := middleware.ServerDataLoadersFromContext(ctx) if dataloaders != nil { if dataloader, ok := dataloaders[server]; ok { tribe, _ := dataloader.PlayerByID.Load(obj.PlayerID) diff --git a/graphql/schema/server.graphql b/graphql/schema/server.graphql index e982971..033e162 100644 --- a/graphql/schema/server.graphql +++ b/graphql/schema/server.graphql @@ -7,7 +7,7 @@ type Server { id: Int! key: String! status: ServerStatus! - langVersionTag: LanguageTag! + langVersion: LangVersion @goField(forceResolver: true) } type ServersList { diff --git a/main.go b/main.go index 12cbf47..d4e5194 100644 --- a/main.go +++ b/main.go @@ -88,9 +88,10 @@ func main() { router := gin.Default() v1 := router.Group("") v1.Use(middleware.DataLoadersToContext(serverRepo, dataloaders.Config{ - PlayerRepo: playerRepo, - TribeRepo: tribeRepo, - VillageRepo: villageRepo, + PlayerRepo: playerRepo, + TribeRepo: tribeRepo, + VillageRepo: villageRepo, + LangVersionRepo: langversionRepo, })) httpdelivery.Attach(httpdelivery.Config{ RouterGroup: v1, diff --git a/middleware/dataloaders_to_context.go b/middleware/dataloaders_to_context.go index 3f015dc..4938576 100644 --- a/middleware/dataloaders_to_context.go +++ b/middleware/dataloaders_to_context.go @@ -12,12 +12,13 @@ import ( "github.com/gin-gonic/gin" ) +var serverDataLoadersContextKey ContextKey = "serverDataLoaders" var dataloadersContextKey ContextKey = "dataloaders" func DataLoadersToContext(serverRepo server.Repository, cfg dataloaders.Config) gin.HandlerFunc { return func(c *gin.Context) { ctx := c.Request.Context() - loaders := make(map[string]*dataloaders.DataLoaders) + loaders := make(map[string]*dataloaders.ServerDataLoaders) servers, _, err := serverRepo.Fetch(c.Request.Context(), nil) if err != nil { c.JSON(http.StatusOK, &gqlerror.Error{ @@ -27,22 +28,37 @@ func DataLoadersToContext(serverRepo server.Repository, cfg dataloaders.Config) return } for _, server := range servers { - loaders[server.Key] = dataloaders.New(server.Key, cfg) + loaders[server.Key] = dataloaders.NewServerDataLoaders(server.Key, cfg) } - c.Request = c.Request.WithContext(StoreDataLoadersInContext(ctx, loaders)) + ctx = StoreServerDataLoadersInContext(ctx, loaders) + ctx = StoreDataLoadersInContext(ctx, dataloaders.New(cfg)) + c.Request = c.Request.WithContext(ctx) c.Next() } } -func StoreDataLoadersInContext(ctx context.Context, loaders map[string]*dataloaders.DataLoaders) context.Context { +func StoreServerDataLoadersInContext(ctx context.Context, loaders map[string]*dataloaders.ServerDataLoaders) context.Context { + return context.WithValue(ctx, serverDataLoadersContextKey, loaders) +} + +func ServerDataLoadersFromContext(ctx context.Context) map[string]*dataloaders.ServerDataLoaders { + dl := ctx.Value(serverDataLoadersContextKey) + if dl == nil { + return nil + } + + return dl.(map[string]*dataloaders.ServerDataLoaders) +} + +func StoreDataLoadersInContext(ctx context.Context, loaders *dataloaders.DataLoaders) context.Context { return context.WithValue(ctx, dataloadersContextKey, loaders) } -func DataLoadersFromContext(ctx context.Context) map[string]*dataloaders.DataLoaders { +func DataLoadersFromContext(ctx context.Context) *dataloaders.DataLoaders { dl := ctx.Value(dataloadersContextKey) if dl == nil { return nil } - return dl.(map[string]*dataloaders.DataLoaders) + return dl.(*dataloaders.DataLoaders) }