Loading CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/). ## [Unreleased] ### Added - Dynamic leaderboards feature. - Presence updates now report the user's handle. ### Changed - The build system now strips up to current dir in recorded source file paths at compile. Loading cmd/admin.go 0 → 100644 +151 −0 Original line number Diff line number Diff line // Copyright 2017 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "database/sql" "encoding/base64" "encoding/json" "flag" "fmt" "github.com/gorhill/cronexpr" "github.com/satori/go.uuid" "github.com/uber-go/zap" "net/url" "os" ) type adminService struct { DSNS string logger zap.Logger } func AdminParse(args []string, logger zap.Logger) { if len(args) == 0 { logger.Fatal("Admin requires a subcommand. Available commands are: 'create-leaderboard'.") } var exec func([]string, zap.Logger) switch args[0] { case "create-leaderboard": exec = createLeaderboard default: logger.Fatal("Unrecognized admin subcommand. Available commands are: 'create-leaderboard'.") } exec(args[1:], logger) os.Exit(0) } func createLeaderboard(args []string, logger zap.Logger) { var dsns string var id string var authoritative bool var sortOrder string var resetSchedule string var metadata string flags := flag.NewFlagSet("admin", flag.ExitOnError) flags.StringVar(&dsns, "db", "root@localhost:26257", "CockroachDB JDBC connection details.") flags.StringVar(&id, "id", "", "ID to assign to the leaderboard.") flags.BoolVar(&authoritative, "authoritative", false, "True if clients may not submit scores directly, false otherwise.") flags.StringVar(&sortOrder, "sort", "descending", "Leaderboard sort order, 'asc' or 'desc'.") flags.StringVar(&resetSchedule, "reset", "", "Optional reset schedule in CRON format.") flags.StringVar(&metadata, "metadata", "{}", "Optional additional metadata as a JSON string.") if err := flags.Parse(args); err != nil { logger.Fatal("Could not parse admin flags.") } if dsns == "" { logger.Fatal("Database connection details are required.") } query := `INSERT INTO leaderboard (id, authoritative, sort_order, reset_schedule, metadata) VALUES ($1, $2, $3, $4, $5)` params := []interface{}{} // ID. if id == "" { params = append(params, uuid.NewV4().Bytes()) } else { params = append(params, []byte(id)) } // Authoritative. params = append(params, authoritative) // Sort order. if sortOrder == "asc" { params = append(params, 0) } else if sortOrder == "desc" { params = append(params, 1) } else { logger.Fatal("Invalid sort value, must be 'asc' or 'desc'.") } // Count is hardcoded in the INSERT above. // Reset schedule. if resetSchedule != "" { _, err := cronexpr.Parse(resetSchedule) if err != nil { logger.Fatal("Reset schedule must be a valid CRON expression.") } params = append(params, resetSchedule) } else { params = append(params, nil) } // Metadata. metadataBytes := []byte(metadata) var maybeJSON map[string]interface{} if json.Unmarshal(metadataBytes, &maybeJSON) != nil { logger.Fatal("Metadata must be a valid JSON string.") } params = append(params, metadataBytes) rawurl := fmt.Sprintf("postgresql://%s?sslmode=disable", dsns) url, err := url.Parse(rawurl) if err != nil { logger.Fatal("Bad connection URL", zap.Error(err)) } logger.Info("Database connection", zap.String("dsns", dsns)) // Default to "nakama" as DB name. dbname := "nakama" if len(url.Path) > 1 { dbname = url.Path[1:] } url.Path = fmt.Sprintf("/%s", dbname) db, err := sql.Open(dialect, url.String()) if err != nil { logger.Fatal("Failed to open database", zap.Error(err)) } if err = db.Ping(); err != nil { logger.Fatal("Error pinging database", zap.Error(err)) } res, err := db.Exec(query, params...) if err != nil { logger.Fatal("Error creating leaderboard", zap.Error(err)) } if rowsAffected, _ := res.RowsAffected(); rowsAffected != 1 { logger.Fatal("Error creating leaderboard, unexpected insert result") } logger.Info("Leaderboard created", zap.String("base64(id)", base64.StdEncoding.EncodeToString(params[0].([]byte)))) } glide.lock +9 −4 Original line number Diff line number Diff line hash: 8320f72a78e69c58350e25d60a59f6b36fc5cf4da055ce9c9a0c6a63083912d1 updated: 2017-01-13T19:42:47.231844584Z hash: d332790eaf0dd90a5d91b4fddfd82897055e261ff360d616cf363c0e689ab4f6 updated: 2017-03-10T18:12:22.221261057Z imports: - name: github.com/armon/go-metrics version: 97c69685293dce4c0a2d0b19535179bbc976e4d2 Loading @@ -16,11 +16,11 @@ imports: - name: github.com/gogo/protobuf version: 909568be09de550ed094403c2bf8a261b5bb730a subpackages: - gogoproto - proto - protoc-gen-gogo/descriptor - name: github.com/golang/protobuf version: 8ee79997227bf9b34611aee7946ae64735e6fd93 - name: github.com/gorhill/cronexpr version: a557574d6c024ed6e36acc8b610f5f211c91568a - name: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/handlers Loading @@ -31,8 +31,12 @@ imports: version: 1f512fc3f05332ba7117626cdfb4e07474e58e60 - name: github.com/lib/pq version: 22cb3e4c487ce6242e2b03369219e5631eed1221 subpackages: - oid - name: github.com/rubenv/sql-migrate version: a3ed23a40ebd39f82bf2a36768ed7d595f2bdc1e subpackages: - sqlparse - name: github.com/satori/go.uuid version: b061729afc07e77a8aa4fad0a2fd840958f1942a - name: github.com/uber-go/atomic Loading @@ -43,6 +47,7 @@ imports: version: f6b343c37ca80bfa8ea539da67a0b621f84fab1d subpackages: - bcrypt - blowfish - name: golang.org/x/net version: 69d4b8aa71caaaa75c3dfc11211d1be495abec7c subpackages: Loading glide.yaml +12 −4 Original line number Diff line number Diff line Loading @@ -9,8 +9,12 @@ owners: - name: Mo Firouz email: mo@herioclabs.com import: - package: golang.org/x/net/context - package: golang.org/x/crypto/bcrypt - package: golang.org/x/net subpackages: - context - package: golang.org/x/crypto subpackages: - bcrypt - package: github.com/golang/protobuf - package: github.com/gogo/protobuf version: ~0.3.0 Loading @@ -22,7 +26,7 @@ import: version: ~1.1 - package: github.com/lib/pq - package: github.com/rubenv/sql-migrate - package: github.com/go-gorp/gorp/ - package: github.com/go-gorp/gorp version: ~2.0.0 - package: github.com/go-yaml/yaml version: v2 Loading @@ -32,4 +36,8 @@ import: - package: github.com/satori/go.uuid - package: github.com/dgrijalva/jwt-go version: ~3.0.0 - package: github.com/elazarl/go-bindata-assetfs/... - package: github.com/elazarl/go-bindata-assetfs subpackages: - '...' - package: github.com/gorhill/cronexpr version: ~1.0.0 main.go +2 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,8 @@ func main() { cmd.DoctorParse(os.Args[2:]) case "migrate": cmd.MigrateParse(os.Args[2:], clogger) case "admin": cmd.AdminParse(os.Args[2:], clogger) } } Loading Loading
CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,10 @@ All notable changes to this project are documented below. The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/). ## [Unreleased] ### Added - Dynamic leaderboards feature. - Presence updates now report the user's handle. ### Changed - The build system now strips up to current dir in recorded source file paths at compile. Loading
cmd/admin.go 0 → 100644 +151 −0 Original line number Diff line number Diff line // Copyright 2017 The Nakama Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "database/sql" "encoding/base64" "encoding/json" "flag" "fmt" "github.com/gorhill/cronexpr" "github.com/satori/go.uuid" "github.com/uber-go/zap" "net/url" "os" ) type adminService struct { DSNS string logger zap.Logger } func AdminParse(args []string, logger zap.Logger) { if len(args) == 0 { logger.Fatal("Admin requires a subcommand. Available commands are: 'create-leaderboard'.") } var exec func([]string, zap.Logger) switch args[0] { case "create-leaderboard": exec = createLeaderboard default: logger.Fatal("Unrecognized admin subcommand. Available commands are: 'create-leaderboard'.") } exec(args[1:], logger) os.Exit(0) } func createLeaderboard(args []string, logger zap.Logger) { var dsns string var id string var authoritative bool var sortOrder string var resetSchedule string var metadata string flags := flag.NewFlagSet("admin", flag.ExitOnError) flags.StringVar(&dsns, "db", "root@localhost:26257", "CockroachDB JDBC connection details.") flags.StringVar(&id, "id", "", "ID to assign to the leaderboard.") flags.BoolVar(&authoritative, "authoritative", false, "True if clients may not submit scores directly, false otherwise.") flags.StringVar(&sortOrder, "sort", "descending", "Leaderboard sort order, 'asc' or 'desc'.") flags.StringVar(&resetSchedule, "reset", "", "Optional reset schedule in CRON format.") flags.StringVar(&metadata, "metadata", "{}", "Optional additional metadata as a JSON string.") if err := flags.Parse(args); err != nil { logger.Fatal("Could not parse admin flags.") } if dsns == "" { logger.Fatal("Database connection details are required.") } query := `INSERT INTO leaderboard (id, authoritative, sort_order, reset_schedule, metadata) VALUES ($1, $2, $3, $4, $5)` params := []interface{}{} // ID. if id == "" { params = append(params, uuid.NewV4().Bytes()) } else { params = append(params, []byte(id)) } // Authoritative. params = append(params, authoritative) // Sort order. if sortOrder == "asc" { params = append(params, 0) } else if sortOrder == "desc" { params = append(params, 1) } else { logger.Fatal("Invalid sort value, must be 'asc' or 'desc'.") } // Count is hardcoded in the INSERT above. // Reset schedule. if resetSchedule != "" { _, err := cronexpr.Parse(resetSchedule) if err != nil { logger.Fatal("Reset schedule must be a valid CRON expression.") } params = append(params, resetSchedule) } else { params = append(params, nil) } // Metadata. metadataBytes := []byte(metadata) var maybeJSON map[string]interface{} if json.Unmarshal(metadataBytes, &maybeJSON) != nil { logger.Fatal("Metadata must be a valid JSON string.") } params = append(params, metadataBytes) rawurl := fmt.Sprintf("postgresql://%s?sslmode=disable", dsns) url, err := url.Parse(rawurl) if err != nil { logger.Fatal("Bad connection URL", zap.Error(err)) } logger.Info("Database connection", zap.String("dsns", dsns)) // Default to "nakama" as DB name. dbname := "nakama" if len(url.Path) > 1 { dbname = url.Path[1:] } url.Path = fmt.Sprintf("/%s", dbname) db, err := sql.Open(dialect, url.String()) if err != nil { logger.Fatal("Failed to open database", zap.Error(err)) } if err = db.Ping(); err != nil { logger.Fatal("Error pinging database", zap.Error(err)) } res, err := db.Exec(query, params...) if err != nil { logger.Fatal("Error creating leaderboard", zap.Error(err)) } if rowsAffected, _ := res.RowsAffected(); rowsAffected != 1 { logger.Fatal("Error creating leaderboard, unexpected insert result") } logger.Info("Leaderboard created", zap.String("base64(id)", base64.StdEncoding.EncodeToString(params[0].([]byte)))) }
glide.lock +9 −4 Original line number Diff line number Diff line hash: 8320f72a78e69c58350e25d60a59f6b36fc5cf4da055ce9c9a0c6a63083912d1 updated: 2017-01-13T19:42:47.231844584Z hash: d332790eaf0dd90a5d91b4fddfd82897055e261ff360d616cf363c0e689ab4f6 updated: 2017-03-10T18:12:22.221261057Z imports: - name: github.com/armon/go-metrics version: 97c69685293dce4c0a2d0b19535179bbc976e4d2 Loading @@ -16,11 +16,11 @@ imports: - name: github.com/gogo/protobuf version: 909568be09de550ed094403c2bf8a261b5bb730a subpackages: - gogoproto - proto - protoc-gen-gogo/descriptor - name: github.com/golang/protobuf version: 8ee79997227bf9b34611aee7946ae64735e6fd93 - name: github.com/gorhill/cronexpr version: a557574d6c024ed6e36acc8b610f5f211c91568a - name: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/handlers Loading @@ -31,8 +31,12 @@ imports: version: 1f512fc3f05332ba7117626cdfb4e07474e58e60 - name: github.com/lib/pq version: 22cb3e4c487ce6242e2b03369219e5631eed1221 subpackages: - oid - name: github.com/rubenv/sql-migrate version: a3ed23a40ebd39f82bf2a36768ed7d595f2bdc1e subpackages: - sqlparse - name: github.com/satori/go.uuid version: b061729afc07e77a8aa4fad0a2fd840958f1942a - name: github.com/uber-go/atomic Loading @@ -43,6 +47,7 @@ imports: version: f6b343c37ca80bfa8ea539da67a0b621f84fab1d subpackages: - bcrypt - blowfish - name: golang.org/x/net version: 69d4b8aa71caaaa75c3dfc11211d1be495abec7c subpackages: Loading
glide.yaml +12 −4 Original line number Diff line number Diff line Loading @@ -9,8 +9,12 @@ owners: - name: Mo Firouz email: mo@herioclabs.com import: - package: golang.org/x/net/context - package: golang.org/x/crypto/bcrypt - package: golang.org/x/net subpackages: - context - package: golang.org/x/crypto subpackages: - bcrypt - package: github.com/golang/protobuf - package: github.com/gogo/protobuf version: ~0.3.0 Loading @@ -22,7 +26,7 @@ import: version: ~1.1 - package: github.com/lib/pq - package: github.com/rubenv/sql-migrate - package: github.com/go-gorp/gorp/ - package: github.com/go-gorp/gorp version: ~2.0.0 - package: github.com/go-yaml/yaml version: v2 Loading @@ -32,4 +36,8 @@ import: - package: github.com/satori/go.uuid - package: github.com/dgrijalva/jwt-go version: ~3.0.0 - package: github.com/elazarl/go-bindata-assetfs/... - package: github.com/elazarl/go-bindata-assetfs subpackages: - '...' - package: github.com/gorhill/cronexpr version: ~1.0.0
main.go +2 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,8 @@ func main() { cmd.DoctorParse(os.Args[2:]) case "migrate": cmd.MigrateParse(os.Args[2:], clogger) case "admin": cmd.AdminParse(os.Args[2:], clogger) } } Loading