This commit is contained in:
nite 2025-04-07 22:43:52 +10:00
parent 4508b19f4b
commit 7076e9d259
10 changed files with 67 additions and 133 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
config.json
.idea/

View File

@ -14,7 +14,7 @@ import (
func FetchAndStore[T any](
e endpoint.EntityEndpoint[T],
) {
total, err := e.GetLastOneId()
total, err := e.Count()
if err != nil {
log.Fatalf("failed to get %s length: %v", e.GetEndpointName(), err)
}

View File

@ -22,7 +22,7 @@ import (
)
func StartWebhookServer(client *igdb.Client) {
baseUrl, err := url.Parse(config.C().ExtralUrl)
baseUrl, err := url.Parse(config.C().ExternalUrl)
if err != nil {
log.Fatalf("failed to parse url: %v", err)
}
@ -112,8 +112,8 @@ func StartWebhookServer(client *igdb.Client) {
}
}()
enabledEndpoint := endpoint.AllEndpoints
enabledEndpoint = slices.DeleteFunc(enabledEndpoint, func(e endpoint.EndpointName) bool {
enabledEndpoint := endpoint.AllNames
enabledEndpoint = slices.DeleteFunc(enabledEndpoint, func(e endpoint.Name) bool {
return e == endpoint.EPWebhooks || e == endpoint.EPSearch || e == endpoint.EPPopularityPrimitives
})
@ -121,14 +121,14 @@ func StartWebhookServer(client *igdb.Client) {
if baseUrl.Hostname() == "localhost" || (ip != nil && ip.IsLoopback()) {
log.Printf("extral url is localhost. webhook will not be registered")
} else {
for _, endp := range enabledEndpoint {
Url := baseUrl.JoinPath(fmt.Sprintf("/webhook/%s", string(endp)))
log.Printf("registering webhook \"%s\" to \"%s\"", endp, Url.String())
err = client.Webhooks.Register(endp, config.C().WebhookSecret, Url.String())
for _, ep := range enabledEndpoint {
Url := baseUrl.JoinPath(fmt.Sprintf("/webhook/%s", string(ep)))
log.Printf("registering webhook \"%s\" to \"%s\"", ep, Url.String())
err = client.Webhooks.Register(ep, config.C().WebhookSecret, Url.String())
if err != nil {
log.Fatalf("failed to register webhook \"%s\": %v", endp, err)
log.Fatalf("failed to register webhook \"%s\": %v", ep, err)
}
log.Printf("webhook \"%s\" registered", endp)
log.Printf("webhook \"%s\" registered", ep)
}
log.Printf("all webhook registered")
}
@ -201,7 +201,7 @@ func webhook[T any](
if v, ok := any(item).(gameGetter); ok {
game, err := db.GetItemByIGDBID[pb.Game](endpoint.EPGames, v.GetGame().Id)
if err != nil && err != mongo.ErrNoDocuments {
if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
log.Printf("failed to get game: %v", err)
goto END
}
@ -211,7 +211,7 @@ func webhook[T any](
goto END
}
oldGame, err := db.GetGameByIGDBID(game.Item.Id)
if err != nil && err != mongo.ErrNoDocuments {
if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
log.Printf("failed to get game: %v", err)
goto END
}

View File

@ -20,7 +20,7 @@ type Config struct {
ClientSecret string `json:"client_secret"`
} `json:"twitch"`
WebhookSecret string `json:"webhook_secret"`
ExtralUrl string `json:"extral_url"`
ExternalUrl string `json:"external_url"`
}
var c *Config

139
db/db.go
View File

@ -22,19 +22,23 @@ var (
type MongoDB struct {
client *mongo.Client
Collections map[endpoint.EndpointName]*mongo.Collection
Collections map[endpoint.Name]*mongo.Collection
GameCollection *mongo.Collection
}
func GetInstance() *MongoDB {
once.Do(func() {
bsonOpts := &options.BSONOptions{
UseJSONStructTags: true,
}
clientOptions := options.Client().ApplyURI(fmt.Sprintf(
"mongodb://%s:%s@%s:%v",
config.C().Database.User,
config.C().Database.Password,
config.C().Database.Host,
config.C().Database.Port,
)).SetConnectTimeout(3 * time.Second)
)).SetConnectTimeout(3 * time.Second).SetBSONOptions(bsonOpts)
client, err := mongo.Connect(clientOptions)
if err != nil {
@ -42,10 +46,10 @@ func GetInstance() *MongoDB {
}
instance = &MongoDB{
client: client,
Collections: make(map[endpoint.EndpointName]*mongo.Collection),
Collections: make(map[endpoint.Name]*mongo.Collection),
}
for _, e := range endpoint.AllEndpoints {
for _, e := range endpoint.AllNames {
instance.Collections[e] = client.Database(config.C().Database.Database).Collection(string(e))
}
@ -60,7 +64,7 @@ func (m *MongoDB) createIndex() {
ctx, cancel := context.WithTimeout(context.Background(), 3*60*time.Second)
defer cancel()
textIndexMap := map[endpoint.EndpointName]string{
textIndexMap := map[endpoint.Name]string{
endpoint.EPGames: "item.name",
endpoint.EPAlternativeNames: "item.name",
}
@ -76,7 +80,7 @@ func (m *MongoDB) createIndex() {
}
}
indexMap := map[endpoint.EndpointName][]string{
indexMap := map[endpoint.Name][]string{
endpoint.EPAlternativeNames: {"item.game.id"},
endpoint.EPArtworks: {"item.game.id"},
endpoint.EPCollectionMemberships: {"item.game.id"},
@ -109,7 +113,7 @@ func (m *MongoDB) createIndex() {
}
}
for _, e := range endpoint.AllEndpoints {
for _, e := range endpoint.AllNames {
if e == endpoint.EPWebhooks || e == endpoint.EPSearch || e == endpoint.EPPopularityPrimitives {
continue
}
@ -122,9 +126,18 @@ func (m *MongoDB) createIndex() {
log.Printf("failed to create index item.id for %s: %v", string(e), err)
}
}
_, err := m.GameCollection.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{
{Key: "id", Value: 1},
},
})
if err != nil {
log.Printf("failed to create index id for game_details: %v", err)
}
}
func SaveItem[T any](e endpoint.EndpointName, item *model.Item[T]) error {
func SaveItem[T any](e endpoint.Name, item *model.Item[T]) error {
if item.MId.IsZero() {
item.MId = bson.NewObjectID()
}
@ -146,7 +159,7 @@ func SaveItem[T any](e endpoint.EndpointName, item *model.Item[T]) error {
return nil
}
func SaveItems[T any](e endpoint.EndpointName, items []*model.Item[T]) error {
func SaveItems[T any](e endpoint.Name, items []*model.Item[T]) error {
var models []mongo.WriteModel
for _, item := range items {
@ -155,8 +168,8 @@ func SaveItems[T any](e endpoint.EndpointName, items []*model.Item[T]) error {
}
filter := bson.M{"_id": item.MId}
update := bson.M{"$set": item}
model := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
models = append(models, model)
oneModel := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
models = append(models, oneModel)
}
coll := GetInstance().Collections[e]
@ -173,7 +186,7 @@ func SaveItems[T any](e endpoint.EndpointName, items []*model.Item[T]) error {
return nil
}
func CountItems(e endpoint.EndpointName) (int64, error) {
func CountItems(e endpoint.Name) (int64, error) {
coll := GetInstance().Collections[e]
if coll == nil {
return 0, fmt.Errorf("collection not found")
@ -187,7 +200,7 @@ func CountItems(e endpoint.EndpointName) (int64, error) {
return count, nil
}
func GetItemByIGDBID[T any](e endpoint.EndpointName, id uint64) (*model.Item[T], error) {
func GetItemByIGDBID[T any](e endpoint.Name, id uint64) (*model.Item[T], error) {
var item model.Item[T]
coll := GetInstance().Collections[e]
if coll == nil {
@ -202,7 +215,7 @@ func GetItemByIGDBID[T any](e endpoint.EndpointName, id uint64) (*model.Item[T],
return &item, nil
}
func GetItemsByIGDBIDs[T any](e endpoint.EndpointName, ids []uint64) (map[uint64]*model.Item[T], error) {
func GetItemsByIGDBIDs[T any](e endpoint.Name, ids []uint64) (map[uint64]*model.Item[T], error) {
if len(ids) == 0 {
return nil, nil
}
@ -241,22 +254,7 @@ func GetItemsByIGDBIDs[T any](e endpoint.EndpointName, ids []uint64) (map[uint64
return res, nil
}
func RemoveItemByID(e endpoint.EndpointName, id bson.ObjectID) error {
coll := GetInstance().Collections[e]
if coll == nil {
return fmt.Errorf("collection not found")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := coll.DeleteOne(ctx, bson.M{"_id": id})
if err != nil {
return fmt.Errorf("failed to remove game: %w", err)
}
return nil
}
func RemoveItemsByID(e endpoint.EndpointName, ids []bson.ObjectID) error {
func RemoveItemsByID(e endpoint.Name, ids []bson.ObjectID) error {
coll := GetInstance().Collections[e]
if coll == nil {
return fmt.Errorf("collection not found")
@ -271,86 +269,7 @@ func RemoveItemsByID(e endpoint.EndpointName, ids []bson.ObjectID) error {
return nil
}
func RemoveDuplicateItems(e endpoint.EndpointName) error {
coll := GetInstance().Collections[e]
if coll == nil {
return fmt.Errorf("collection not found")
}
pipeline := bson.A{
bson.D{
{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$item.id"},
{Key: "docs", Value: bson.D{
{Key: "$push", Value: "$_id"},
}},
}},
},
bson.D{
{Key: "$match", Value: bson.D{
{Key: "$expr", Value: bson.D{
{Key: "$gt", Value: bson.A{
bson.D{{Key: "$size", Value: "$docs"}},
1,
}},
}},
}},
},
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cursor, err := coll.Aggregate(ctx, pipeline)
if err != nil {
return fmt.Errorf("failed to aggregate: %w", err)
}
var results []struct {
ID uint64 `bson:"_id"`
Docs []bson.ObjectID `bson:"docs"`
}
err = cursor.All(ctx, &results)
if err != nil {
return fmt.Errorf("failed to get results: %w", err)
}
removedIds := make([]bson.ObjectID, 0, len(results))
for _, result := range results {
removedIds = append(removedIds, result.Docs[1:]...)
}
err = RemoveItemsByID(e, removedIds)
if err != nil {
return fmt.Errorf("failed to remove duplicate games: %w", err)
}
return nil
}
func GetItemsByIGDBGameID[T any](e endpoint.EndpointName, id uint64) ([]*model.Item[T], error) {
coll := GetInstance().Collections[e]
if coll == nil {
return nil, fmt.Errorf("collection not found")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cursor, err := coll.Find(ctx, bson.M{"item.game.id": id})
if err != nil {
return nil, fmt.Errorf("failed to get items %s: %w", string(e), err)
}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var items []*model.Item[T]
err = cursor.All(ctx, &items)
if err != nil {
return nil, fmt.Errorf("failed to decode items %s: %w", string(e), err)
}
return items, nil
}
func GetItemsPagnated[T any](e endpoint.EndpointName, offset int64, limit int64) ([]*model.Item[T], error) {
func GetItemsPaginated[T any](e endpoint.Name, offset int64, limit int64) ([]*model.Item[T], error) {
coll := GetInstance().Collections[e]
if coll == nil {

View File

@ -28,7 +28,7 @@ func IsGamesAggregated(games []*pb.Game) (map[uint64]bool, error) {
}
res := make(map[uint64]bool, len(games))
g := []*model.Game{}
var g []*model.Game
err = cursor.All(ctx, &g)
if err != nil {
return nil, fmt.Errorf("failed to get games: %w", err)

12
go.mod
View File

@ -3,7 +3,7 @@ module igdb-database
go 1.24.1
require (
github.com/bestnite/go-igdb v0.0.9
github.com/bestnite/go-igdb v0.0.11
go.mongodb.org/mongo-driver/v2 v2.1.0
google.golang.org/protobuf v1.36.6
)
@ -17,7 +17,7 @@ require (
github.com/bestnite/go-flaresolverr v0.0.0-20250404141941-4644c2e66727 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect
@ -25,10 +25,10 @@ require (
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
h12.io/socks v1.0.3 // indirect
)

14
go.sum
View File

@ -25,6 +25,10 @@ github.com/bestnite/go-flaresolverr v0.0.0-20250404141941-4644c2e66727 h1:F1fNb9
github.com/bestnite/go-flaresolverr v0.0.0-20250404141941-4644c2e66727/go.mod h1:LX2oPIfG4LnUtQ7FAWV727IXuODZVbzRwG/7t2KdMNo=
github.com/bestnite/go-igdb v0.0.9 h1:3n1OHSf6mA2ygoktbcVvAlQXZjkmtKuP1+Ql1pNAN5I=
github.com/bestnite/go-igdb v0.0.9/go.mod h1:HBPwYCgSVd7oaiLWJVV72UpqsmIYjUmaIvGmuFk3CwY=
github.com/bestnite/go-igdb v0.0.10 h1:2fAph2vvE4xM5gS9TpevFzU0r7i0WcLZ/rAkNbvLRrg=
github.com/bestnite/go-igdb v0.0.10/go.mod h1:HBPwYCgSVd7oaiLWJVV72UpqsmIYjUmaIvGmuFk3CwY=
github.com/bestnite/go-igdb v0.0.11 h1:DIG3NjTpRfpIDIbTsbOuMWI/Y7IZRUNu/WaOaPZdcKg=
github.com/bestnite/go-igdb v0.0.11/go.mod h1:HBPwYCgSVd7oaiLWJVV72UpqsmIYjUmaIvGmuFk3CwY=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@ -73,6 +77,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -233,6 +239,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -305,6 +313,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -343,6 +353,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -377,6 +389,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=

View File

@ -67,7 +67,7 @@ func aggregateGames() {
go func(i int64) {
defer func() { <-concurrence }()
defer wg.Done()
items, err := db.GetItemsPagnated[pb.Game](endpoint.EPGames, i, taskOneLoop)
items, err := db.GetItemsPaginated[pb.Game](endpoint.EPGames, i, taskOneLoop)
if err != nil {
log.Fatalf("failed to get games: %v", err)
}

View File

@ -10,7 +10,7 @@ type GameIds []uint64
type GameId uint64
type Game struct {
MId bson.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
MId bson.ObjectID `json:"_id,omitempty"`
Id uint64 `json:"id,omitempty"`
AgeRatings []*pb.AgeRating `json:"age_ratings,omitempty"`
AggregatedRating float64 `json:"aggregated_rating,omitempty"`
@ -67,5 +67,5 @@ type Game struct {
GameStatus *pb.GameStatus `json:"game_status,omitempty"`
GameType *pb.GameType `json:"game_type,omitempty"`
AllNames []string `bson:"all_names,omitempty"`
AllNames []string `json:"all_names,omitempty"`
}