From 59b9afb7d3c77ac2df9f4e2291690f4d25eba732 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Fri, 14 Aug 2020 14:53:46 -0400 Subject: [PATCH] Add board syncing from Workshop --- .gitignore | 5 +- leadercraft-shopper/go.mod | 8 + leadercraft-shopper/go.sum | 4 + leadercraft-shopper/main.go | 142 +++++++++++++ leadercraft-shopper/sql_service.go | 270 ++++++++++++++++++++++++ leadercraft-shopper/sql_structs.go | 318 +++++++++++++++++++++++++++++ 6 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 leadercraft-shopper/go.mod create mode 100644 leadercraft-shopper/go.sum create mode 100644 leadercraft-shopper/main.go create mode 100644 leadercraft-shopper/sql_service.go create mode 100644 leadercraft-shopper/sql_structs.go diff --git a/.gitignore b/.gitignore index b6d4eac..11db995 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ # Dependency directories (remove the comment below to include it) vendor/ -**/leadercraft-ranker +# binaries +leadercraft-ranker/leadercraft-ranker +leadercraft-shopper/leadercraft-shopper +leadercraft-shopper/criterias/*.json diff --git a/leadercraft-shopper/go.mod b/leadercraft-shopper/go.mod new file mode 100644 index 0000000..89d967e --- /dev/null +++ b/leadercraft-shopper/go.mod @@ -0,0 +1,8 @@ +module git.exmods.org/NGnius/lion-turtle/leadercraft-shopper + +go 1.13 + +require ( + github.com/lib/pq v1.3.0 + github.com/mattn/go-sqlite3 v2.0.3+incompatible +) diff --git a/leadercraft-shopper/go.sum b/leadercraft-shopper/go.sum new file mode 100644 index 0000000..3807af5 --- /dev/null +++ b/leadercraft-shopper/go.sum @@ -0,0 +1,4 @@ +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= diff --git a/leadercraft-shopper/main.go b/leadercraft-shopper/main.go new file mode 100644 index 0000000..5da681b --- /dev/null +++ b/leadercraft-shopper/main.go @@ -0,0 +1,142 @@ +// NGnius 2020-01-30 + +package main // leadercraft-server + +import ( + "flag" + "fmt" + "net/http" + "os" + "os/signal" + "encoding/json" + "strconv" +) + +const ( + // Version the current version + Version = "0.1" + // Name the program name + Name = "leadercraft-shopper" +) + +var ( + isClosing bool + printVersionAndExit bool +) + +type Criteria struct { + Location [][]float64 + GameID int64 + Coefficient int64 + ScoreMode string +} + +func defaultCriteria() Criteria { + return Criteria { + Location: [][]float64 {[]float64{-10000,-10000, -10000}, []float64{10000,10000,10000}}, + GameID: 0, + Coefficient: 1_000_000_000, + ScoreMode: "time", + } +} + +func init() { + initArgs() +} + +func main() { + parseArgs() + sqlInitErr := sqlInit() + if sqlInitErr != nil { + fmt.Printf("Failed to initialise SQL connection: %s\n", sqlInitErr) + os.Exit(1) + } + // handle interrupt (terminate) signal + signalChan := make(chan os.Signal) + signal.Notify(signalChan, os.Interrupt) + go func() { + s := <-signalChan + fmt.Println("Received terminate signal " + s.String()) + isClosing = true + sqlClose() + }() + // get popular games + for page := 1; page < 10; page++ { + url := fmt.Sprintf("https://api.steampowered.com/IPublishedFileService/QueryFiles/v1/?key=%s&page=%d&numperpage=%d&appid=%d&requiredtags=%s&return_short_description=true", os.Getenv("STEAM_TOKEN"), page, 100, 1078000, "[\"featured\",\"game\"]") + resp, err := http.Get(url) + if err != nil { + fmt.Println(err) + return + } + if resp.StatusCode != 200 { + fmt.Println(resp.Status) + return + } + gamesJSON := make(map[string]interface{}) + decoder := json.NewDecoder(resp.Body) + decErr := decoder.Decode(&gamesJSON) + if decErr != nil { + fmt.Println(decErr) + return + } + resp.Body.Close() + games := gamesJSON["response"].(map[string]interface{})["publishedfiledetails"].([]interface{}) + //fmt.Println(len(games)) + //fmt.Println(gamesJSON["response"].(map[string]interface{})["total"].(float64)) + folderErr := os.MkdirAll("criterias/", os.ModeDir | os.ModePerm) + if folderErr != nil { + fmt.Println(folderErr) + } + for _, genGame := range games { + game := genGame.(map[string]interface{}) + id, _ := strconv.Atoi(game["publishedfileid"].(string)) + id64 := int64(id) + title := game["title"].(string) + desc := game["short_description"].(string) + //fmt.Printf("Game ID:%d title:'%s' desc:'%s'\n", id, title, desc) + gameBoard := LoadBoard(id64) + if gameBoard == nil { + // create new board + gameBoard = &Board{ + ID: id64, + Name: title, + Description: desc, + } + gameBoard.Commit() + // create new criteria + crit := defaultCriteria() + crit.GameID = id64 + file, createErr := os.Create(fmt.Sprintf("criterias/criteria-%d.json", id64)) + if createErr != nil { + fmt.Println(createErr) + } else { + encoder := json.NewEncoder(file) + encoder.Encode(&crit) + file.Close() + } + fmt.Printf("Created new Board ID:%d Name:'%s'\n", id, title) + } else if id64 > 2 { + fmt.Printf("Found existing Board ID:%d Name:'%s'\n", id, gameBoard.Name) + if gameBoard.Name != title || gameBoard.Description != desc { + gameBoard.Name = title + gameBoard.Description = desc + } + gameBoard.Commit() + } + } + } +} + +func initArgs() { + flag.BoolVar(&printVersionAndExit, "version", false, "Print version and exit") + flag.StringVar(&sqlConnection, "conn", sqlConnectionDefault, "Database connection string") + flag.StringVar(&sqlServer, "sql", sqlServerDefault, "SQL Database type") +} + +func parseArgs() { + flag.Parse() + if printVersionAndExit { + fmt.Println(Name + " v" + Version) + os.Exit(0) + } +} diff --git a/leadercraft-shopper/sql_service.go b/leadercraft-shopper/sql_service.go new file mode 100644 index 0000000..fdc9fa3 --- /dev/null +++ b/leadercraft-shopper/sql_service.go @@ -0,0 +1,270 @@ +// NGnius 2020-02-11 + +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/lib/pq" // postgres + _ "github.com/mattn/go-sqlite3" // sqlite +) + +const ( + sqlServerDefault = "sqlite3" + sqlConnectionDefault = "test.sqlite" +) + +var ( + // command line arguments + sqlServer string + sqlConnection string + buildTables bool + populateTables bool + autocreate bool + // internal variables + db *sql.DB + queryType int + queryStrings [][]string +) + +func sqlInit() error { + var dbOpenErr error + if sqlServer == "postgres" { + queryType = 1 + } else { // sqlite3 + queryType = 0 + } + queryStrings = [][]string{ + []string{ // sqlite + "SELECT * FROM Boards WHERE name=? LIMIT 1;", + "SELECT * FROM Players WHERE name=? LIMTI 1;", + "INSERT INTO Entries(score, player, board, time) VALUES (?, ?, ?, ?);", + "INSERT INTO Keys(token, player, time) VALUES (?, ?, ?);", + "SELECT * FROM Keys WHERE token=? AND player=? AND time=?", + "SELECT * FROM Keys WHERE token=? LIMIT 1;", + "SELECT * FROM Keys WHERE player=? LIMIT 1;", + "SELECT * FROM Boards WHERE id=?", + "INSERT OR REPLACE INTO Boards(id, name, description) VALUES (?, ?, ?);", + "SELECT * FROM Entries WHERE board=?", + "SELECT * FROM Entries WHERE board=? and rank >= ? and rank <= ? ORDER BY rank ASC;", + "SELECT * FROM Players WHERE id=? LIMIT 1;", + "INSERT OR REPLACE INTO Players(id, name) VALUES (?, ?);", + "SELECT * FROM Entries WHERE player=?", + "SELECT * FROM Entries WHERE player=? ORDER BY time DESC LIMIT ?;", + "SELECT * FROM Entries WHERE id=? LIMIT 1;", + "INSERT OR REPLACE INTO Entries(id, rank, score, player, board, time, metadata) VALUES (?, ?, ?, ?, ?, ?, ?);", + "SELECT * FROM Keys WHERE id=? LIMIT 1;", + "INSERT OR REPLACE INTO Keys(id, token, player, time, metadata) VALUES (?, ?, ?, ?, ?);", + "CREATE TABLE IF NOT EXISTS Players (id INTEGER PRIMARY KEY, name TEXT NOT NULL);", + "CREATE TABLE IF NOT EXISTS Boards (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL);", + "CREATE TABLE IF NOT EXISTS Entries (id INTEGER PRIMARY KEY, rank INTEGER NOT NULL DEFAULT -1, score INTEGER NOT NULL, player INTEGER NOT NULL, board INTEGER NOT NULL, time INTEGER NOT NULL, metadata BLOB NOT NULL DEFAULT x'FF', FOREIGN KEY(player) REFERENCES Players(id), FOREIGN KEY(board) REFERENCES Boards(id));", + "CREATE TABLE IF NOT EXISTS Keys (id INTEGER PRIMARY KEY, token TEXT NOT NULL, player INTEGER NOT NULL, time INTEGER NOT NULL, metadata BLOB NOT NULL DEFAULT x'FF', FOREIGN KEY(player) REFERENCES Players(id));", + }, + []string{ // postgres + "SELECT * FROM Boards WHERE name=$1 LIMIT 1;", + "SELECT * FROM Players WHERE name=$1 LIMTI 1;", + "INSERT INTO Entries(score, player, board, time) VALUES ($1, $2, $3, $4);", + "INSERT INTO Keys(token, player, time) VALUES ($1, $2, $3);", + "SELECT * FROM Keys WHERE token=$1 AND player=$2 AND time=$3", + "SELECT * FROM Keys WHERE token=$1 LIMIT 1;", + "SELECT * FROM Keys WHERE player=$1 LIMIT 1;", + "SELECT * FROM Boards WHERE id=$1", + "INSERT INTO Boards(id, name, description) VALUES ($1, $2, $3) ON CONFLICT(id) DO UPDATE SET name=$2, description=$3;", + "SELECT * FROM Entries WHERE board=$1", + "SELECT * FROM Entries WHERE board=$1 and rank >= $2 and rank <= $3 ORDER BY rank ASC;", + "SELECT * FROM Players WHERE id=$1 LIMIT 1;", + "INSERT INTO Players(id, name) VALUES ($1, $2) ON CONFLICT(id) DO UPDATE SET name=$2;", + "SELECT * FROM Entries WHERE player=$1;", + "SELECT * FROM Entries WHERE player=$1 ORDER BY time DESC LIMIT $2;", + "SELECT * FROM Entries WHERE id=$1 LIMIT 1;", + "INSERT INTO Entries(id, rank, score, player, board, time, metadata) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT(id) DO UPDATE SET rank=$2, score=$3, player=$4, board=$5, time=$6, metadata=$7;", + "SELECT * FROM Keys WHERE id=$1 LIMIT 1;", + "INSERT INTO Keys(id, token, player, time, metadata) VALUES ($1, $2, $3, $4, $5) ON CONFLICT(id) DO UPDATE SET token=$2, player=$3, time=$4, metadata=$5;", + "CREATE TABLE IF NOT EXISTS Players (id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL);", + "CREATE TABLE IF NOT EXISTS Boards (id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL);", + "CREATE TABLE IF NOT EXISTS Entries (id BIGSERIAL PRIMARY KEY, rank INTEGER NOT NULL DEFAULT -1, score BIGINT NOT NULL, player BIGSERIAL NOT NULL, board BIGSERIAL NOT NULL, time INTEGER NOT NULL, metadata BYTEA NOT NULL DEFAULT E'\\\\xFF', FOREIGN KEY(player) REFERENCES Players(id), FOREIGN KEY(board) REFERENCES Boards(id));", + "CREATE TABLE IF NOT EXISTS Keys (id BIGSERIAL PRIMARY KEY, token TEXT NOT NULL, player BIGSERIAL NOT NULL, time BIGSERIAL NOT NULL, metadata BYTEA NOT NULL DEFAULT E'\\\\xFF', FOREIGN KEY(player) REFERENCES Players(id));", + }, + } + fmt.Printf("SQL Server %s is query type %d\n", sqlServer, queryType) + //fmt.Println(len(queryStrings[queryType])) + db, dbOpenErr = sql.Open(sqlServer, sqlConnection) + if dbOpenErr != nil { + return dbOpenErr + } + if buildTables { + fmt.Println("Building tables in database...") + sqlBuildTables() + } + if populateTables { + fmt.Println("Populating tables in database...") + sqlPopulateTables() + } + return nil +} + +func sqlClose() error { + if db != nil { + err := db.Close() + if err != nil { + return err + } + db = nil + } + return nil +} + +func boardByName(name string) (*Board, error) { + b := &Board{} + return b, db.QueryRow(queryStrings[queryType][0], name).Scan(b.Intake()...) +} + +func playerByName(name string) (*Player, error) { + p := &Player{} + return p, db.QueryRow(queryStrings[queryType][1], name).Scan(p.Intake()...) +} + +func newEntrySql(score, player, board int64) error { + tx, _ := db.Begin() + stmt, _ := tx.Prepare(queryStrings[queryType][2]) + _, err := stmt.Exec(score, player, board, time.Now().Unix()) + if err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func newKeySql(player int64, name string) (*Key, error) { + tx, _ := db.Begin() + if autocreate { + sqlPlayer := &Player{ID: player, Name: name} + if (sqlPlayer.Load() != nil) { + playerErr := sqlPlayer.Commit() + if playerErr != nil { + return nil, playerErr + } + } + } + newKey := &Key{Token: "new", Player: player, Time: time.Now().Unix()} + stmt, _ := tx.Prepare(queryStrings[queryType][3]) + _, err := stmt.Exec(newKey.Token, newKey.Player, newKey.Time) + if err != nil { + tx.Rollback() + return nil, err + } + tx.Commit() + db.QueryRow(queryStrings[queryType][4], newKey.Token, newKey.Player, newKey.Time).Scan(newKey.Intake()...) + tokenErr := newKey.GenerateToken() + if tokenErr != nil { + return nil, tokenErr + } + newKey.Metadata = []byte{0xFE} // enable key with not extra permissions + return newKey, newKey.Commit() +} + +func keyByToken(token string) (*Key, error) { + k := &Key{} + return k, db.QueryRow(queryStrings[queryType][5], token).Scan(k.Intake()...) +} + +func keyByPlayer(player int64) (*Key, error) { + k := &Key{} + return k, db.QueryRow(queryStrings[queryType][6], player).Scan(k.Intake()...) +} + +// internal operations +func sqlBuildTables() { + transaction, txErr := db.Begin() + if txErr != nil { + return + } + // test table + //transaction.Exec("CREATE TABLE IF NOT EXISTS Test (Sometext VARCHAR, Somenumber);") + //transaction.Exec("INSERT INTO Test (Sometext, Somenumber) VALUES (?,?);", "Hello sqlite", 123) + // build real tables + transaction.Exec(queryStrings[queryType][19]) + transaction.Exec(queryStrings[queryType][20]) + transaction.Exec(queryStrings[queryType][21]) + transaction.Exec(queryStrings[queryType][22]) + transaction.Commit() +} + +func sqlPopulateTables() { + boards := []*Board{ + &Board{ID: 1, Name: "main-test", Description: "Overall best (test data)"}, + &Board{ID: 2, Name: "coolest-test", Description: "Coolest score (test data)"}, + &Board{ID: 3, Name: "fastest-test", Description: "Fastest time (test data)"}, + } + for _, b := range boards { + err := b.Commit() + if err != nil { + fmt.Printf("Error creating board %d: %s\n", b.ID, err) + } + } + players := []*Player{ + &Player{ID: 1, Name: "NGnius (test)"}, + &Player{ID: 2, Name: "Also NGnius (test)"}, + &Player{ID: 3, Name: ".xX||eDgY TeeNaGeR||Xx. (test)"}, + &Player{ID: 4, Name: "New username who dis? (test)"}, + &Player{ID: 5, Name: "Extremely Ridiculously Long Name to break things (test)"}, + &Player{ID: 6, Name: "P|P3 |o|z (test)"}, + &Player{ID: 7, Name: "Waldo (test)"}, + &Player{ID: 8, Name: "Zettagram.com (test)"}, + &Player{ID: 9, Name: "The Doctor (test)"}, + &Player{ID: 10, Name: "Marvin the Paranoid Android (test)"}, + &Player{ID: 11, Name: "IDK HOW (test)"}, + &Player{ID: 12, Name: "If you can read this your API may be wrong (test)"}, + &Player{ID: 13, Name: "Unlucky 7 (test)"}, + } + for _, p := range players { + err := p.Commit() + if err != nil { + fmt.Printf("Error creating player %d: %s\n", p.ID, err) + } + } + now := time.Now().Unix() + metadata := []byte{0x00, 0x00, 0x00, 0x00} + entries := []*Entry{ + &Entry{ID: 1, Rank: 1, Score: 1000, Player: players[0].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 2, Rank: 2, Score: 900, Player: players[1].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 3, Rank: 3, Score: 400, Player: players[2].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 4, Rank: 4, Score: 350, Player: players[3].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 5, Rank: 5, Score: 350, Player: players[4].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 6, Rank: 6, Score: 250, Player: players[5].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 7, Rank: 7, Score: 200, Player: players[6].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 8, Rank: 8, Score: 175, Player: players[7].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 9, Rank: 9, Score: 150, Player: players[8].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 10, Rank: 10, Score: 140, Player: players[9].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 11, Rank: 11, Score: 10, Player: players[10].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 12, Rank: 12, Score: 60, Player: players[11].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, + &Entry{ID: 13, Rank: 13, Score: 13, Player: players[12].ID, Board: boards[0].ID, Time: now}, + } + for _, e := range entries { + err := e.Commit() + if err != nil { + fmt.Printf("Error creating entry %d: %s\n", e.ID, err) + } + } + metadata = []byte{0x7E} + keys := []*Key{ + &Key{ID: 1, Token: "", Player: players[0].ID, Time: now, Metadata: metadata}, + &Key{ID: 2, Token: "", Player: players[1].ID, Time: now, Metadata: metadata}, + } + for _, k := range keys { + genErr := k.GenerateToken() + if genErr != nil { + fmt.Printf("Error generating token for key %d, %s\n", k.ID, genErr) + } + //fmt.Printf("Key %d token: %s\n", k.ID, k.Token) + err := k.Commit() + if err != nil { + fmt.Printf("Error creating key %d, %s\n", k.ID, err) + } + // bitwise op test + //fmt.Printf("Key &d is superuser:%t, enabled:%t\n", k.ID, k.IsSuperuser(), k.IsEnabled()) + } +} diff --git a/leadercraft-shopper/sql_structs.go b/leadercraft-shopper/sql_structs.go new file mode 100644 index 0000000..564767f --- /dev/null +++ b/leadercraft-shopper/sql_structs.go @@ -0,0 +1,318 @@ +// NGnius 2020-02-12 + +package main + +import ( + "crypto/rand" + "crypto/sha512" + "encoding/binary" + "math/big" + "strconv" + // test + //"fmt" +) + +var ( + randomizeTokens bool +) + +type Rower interface { + Intake() []interface{} + Output() []interface{} +} + +type Board struct { + ID int64 + Name string + Description string +} + +func LoadBoard(id int64) *Board { + b := &Board{ID: id} + loadErr := b.Load() + if loadErr != nil { + return nil + } + return b +} + +func (b *Board) Load() error { + return db.QueryRow(queryStrings[queryType][7], b.ID).Scan(b.Intake()...) +} + +func (b *Board) Commit() error { + tx, _ := db.Begin() + statement, _ := tx.Prepare(queryStrings[queryType][8]) + _, err := statement.Exec(b.Output()...) + if err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (b *Board) Entries() ([]*Entry, error) { + var entries []*Entry + rows, err := db.Query(queryStrings[queryType][9], b.ID) + if err != nil { + return entries, err + } + count := 0 + for rows.Next() { + entries = append(entries, &Entry{}) + scanErr := rows.Scan(entries[count].Intake()...) + if scanErr != nil { + return entries, scanErr + } + count++ + } + return entries, nil +} + +func (b *Board) SomeEntries(start, end int64) ([]*Entry, error) { + var entries []*Entry + rows, err := db.Query(queryStrings[queryType][10], b.ID, start, end) + if err != nil { + return entries, err + } + count := 0 + for rows.Next() { + entries = append(entries, &Entry{}) + scanErr := rows.Scan(entries[count].Intake()...) + if scanErr != nil { + return entries, scanErr + } + count++ + } + return entries, nil +} + +func (b *Board) Url() string { + return "/board?name=" + b.Name +} + +// implementation of Rower +func (b *Board) Intake() []interface{} { + return []interface{}{&b.ID, &b.Name, &b.Description} +} + +func (b *Board) Output() []interface{} { + return []interface{}{b.ID, b.Name, b.Description} +} + +type Player struct { + ID int64 + Name string +} + +func LoadPlayer(id int64) *Player { + p := &Player{ID: id} + loadErr := p.Load() + if loadErr != nil { + return nil + } + return p +} + +func (p *Player) Load() error { + return db.QueryRow(queryStrings[queryType][11], p.ID).Scan(p.Intake()...) +} + +func (p *Player) Commit() error { + tx, _ := db.Begin() + statement, _ := tx.Prepare(queryStrings[queryType][12]) + _, err := statement.Exec(p.Output()...) + if err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (p *Player) Entries() ([]*Entry, error) { + var entries []*Entry + rows, err := db.Query(queryStrings[queryType][13], p.ID) + if err != nil { + return entries, err + } + count := 0 + for rows.Next() { + entries = append(entries, &Entry{}) + scanErr := rows.Scan(entries[count].Intake()...) + if scanErr != nil { + return entries, scanErr + } + count++ + } + return entries, nil +} + +func (p *Player) SomeEntries(limit int64) ([]*Entry, error) { + var entries []*Entry + rows, err := db.Query(queryStrings[queryType][14], p.ID, limit) + if err != nil { + return entries, err + } + count := 0 + for rows.Next() { + entries = append(entries, &Entry{}) + scanErr := rows.Scan(entries[count].Intake()...) + if scanErr != nil { + return entries, scanErr + } + count++ + } + return entries, nil +} + +func (p *Player) Url() string { + return "/player?id=" + strconv.Itoa(int(p.ID)) +} + +// implementation of Rower +func (p *Player) Intake() []interface{} { + return []interface{}{&p.ID, &p.Name} +} + +func (p *Player) Output() []interface{} { + return []interface{}{p.ID, p.Name} +} + +type Entry struct { + ID int64 + Rank int64 + Score int64 + Player int64 + Board int64 + Time int64 // Created time (seconds since Unix epoch) + Metadata []byte +} + +func LoadEntry(id int64) *Entry { + e := &Entry{ID: id} + loadErr := e.Load() + if loadErr != nil { + return nil + } + return e +} + +func (e *Entry) Load() error { + return db.QueryRow(queryStrings[queryType][15], e.ID).Scan(e.Intake()...) +} + +func (e *Entry) Commit() error { + tx, _ := db.Begin() + statement, _ := tx.Prepare(queryStrings[queryType][16]) + _, err := statement.Exec(e.Output()...) + if err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +// implementation of Rower +func (e *Entry) Intake() []interface{} { + return []interface{}{&e.ID, &e.Rank, &e.Score, &e.Player, &e.Board, &e.Time, &e.Metadata} +} + +func (e *Entry) Output() []interface{} { + return []interface{}{e.ID, e.Rank, e.Score, e.Player, e.Board, e.Time, e.Metadata} +} + +type Key struct { + ID int64 + Token string + Player int64 + Time int64 // Created time (seconds since Unix epoch) + Metadata []byte +} + +func LoadKey(id int64) *Key { + k := &Key{ID: id} + loadErr := k.Load() + if loadErr != nil { + return nil + } + return k +} + +func (k *Key) Load() error { + return db.QueryRow(queryStrings[queryType][17], k.ID).Scan(k.Intake()...) +} + +func (k *Key) Commit() error { + tx, _ := db.Begin() + statement, _ := tx.Prepare(queryStrings[queryType][18]) + _, err := statement.Exec(k.Output()...) + if err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (k *Key) IsEnabled() bool { + return (k.Metadata[0] & 0b00000001) == 0 +} + +func (k *Key) Disable() { + k.Metadata[0] = k.Metadata[0] | 0b00000001 +} + +func (k *Key) IsSuperuser() bool { + return (k.Metadata[0] & 0b00000100) == 0 +} + +func (k *Key) Promote() { + k.Metadata[0] = k.Metadata[0] | 0b00000100 +} + +func (k *Key) IsMultiuser() bool { + return (k.Metadata[0] & 0b00010000) == 0 +} + +func (k *Key) Develop() { + k.Metadata[0] = k.Metadata[0] | 0b00010000 +} + +func (k *Key) GenerateToken() error { + buf_int64 := make([]byte, 10) // 8 bytes = 64 bits + input := []byte{} + if randomizeTokens { + max := big.NewInt(2 ^ 16 - 1) + for i := 0; i < 512; i++ { + // generate randomness + num, _ := rand.Int(rand.Reader, max) + input = append(input, num.Bytes()...) + } + } + binary.PutVarint(buf_int64, k.ID) + input = append(input, buf_int64[0:8]...) + binary.PutVarint(buf_int64, k.Player) + input = append(input, buf_int64[0:8]...) + binary.PutVarint(buf_int64, k.Time) + input = append(input, buf_int64[0:8]...) + bToken := sha512.Sum512(input) + //k.Token = string(bToken) + k.Token = "" + for _, b := range bToken { + tmp_b := b & 0b01111111 // Valid 7-bit ASCII values only + for !((tmp_b > 47 && tmp_b < 58) || (tmp_b > 64 && tmp_b < 91) || (tmp_b > 96 && tmp_b < 123)) { + tmp_b = sha512.Sum512([]byte{tmp_b})[1] + tmp_b = tmp_b & 0b01111111 + } + k.Token += string(tmp_b) + } + return nil +} + +// Implementation of Rower +func (k *Key) Intake() []interface{} { + return []interface{}{&k.ID, &k.Token, &k.Player, &k.Time, &k.Metadata} +} + +func (k *Key) Output() []interface{} { + return []interface{}{k.ID, k.Token, k.Player, k.Time, k.Metadata} +}