Commit 14cbcc78 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Dynamic leaderboards feature. Merge #43

parent 163a540d
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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.

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))))
}
+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
@@ -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
@@ -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
@@ -43,6 +47,7 @@ imports:
  version: f6b343c37ca80bfa8ea539da67a0b621f84fab1d
  subpackages:
  - bcrypt
  - blowfish
- name: golang.org/x/net
  version: 69d4b8aa71caaaa75c3dfc11211d1be495abec7c
  subpackages:
+12 −4
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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