import { Database } from '../../Plugin/Database' ; import { DeepPartial } from '../../utils/DeepPartial' ; import { Dimension , NanoID } from '../../Dimension' ; import { Match } from '../../Match' ; import { Tournament , Player } from '../../Tournament' ; import { verify , generateToken } from '../../Plugin/Database/utils' ; import { Plugin } from '../../Plugin' ; import bcrypt from 'bcryptjs' ; import { Datastore } from '@google-cloud/datastore' ; import { pickMatch } from '../../Station/routes/api/dimensions/match' ; import { deepMerge } from '../../utils/DeepMerge' ; import { deepCopy } from '../../utils/DeepCopy' ; import { stripFunctions } from '../../utils' ; require ( 'dotenv' ) . config ( ) ; const salt = bcrypt . genSaltSync ( ) ; export class GCloudDataStore extends Database { public name = 'GCloud Data Store' ; public type = Plugin . Type . DATABASE ; public datastore : Datastore ; constructor ( gcpConfigs , configs = { } ) { super ( configs ) ; this . datastore = new Datastore ( { keyFile : gcpConfigs . keyFile } ) ; } public async initialize ( ) { const existingUser = await this . getUser ( 'admin' ) ; if ( ! existingUser ) { await this . registerUser ( 'admin' , process . env . ADMIN_PASSWORD ) ; } return ; } public async storeMatch ( match , governID ) { const data = { ... pickMatch ( match ) , governID : governID } ; const strippedData = stripFunctions ( deepCopy ( data ) ) ; const key = this . getMatchDatastoreKey ( match . id ) ; return this . datastore . save ( { key : key , data : strippedData , } ) ; } public async getMatch ( id ) { const key = this . getMatchDatastoreKey ( id ) ; return ( await this . datastore . get ( key ) ) [ 0 ] ; } public async getPlayerMatches ( playerID , governID , offset = 0 , limit = 10 , order = - 1 ) { const query = this . datastore . createQuery ( GCloudDataStore . Kinds . MATCHES ) . filter ( 'agents.tournamentID.id' , '=' , playerID ) . filter ( 'governID' , '=' , governID ) . offset ( offset ) . limit ( limit ) . order ( 'creationDate' , { descending : order === - 1 } ) ; return ( await this . datastore . runQuery ( query ) ) [ 0 ] ; } public async registerUser ( username , password , userData ? ) { const hash = bcrypt . hashSync ( password , salt ) ; const playerID = Player . generatePlayerID ( ) ; const playerIDkey = this . datastore . key ( [ GCloudDataStore . Kinds . USERS , username , GCloudDataStore . Kinds . PLAYER_IDS , playerID , ] ) ; const userKey = this . getUserDatastoreKey ( username ) ; const user = { username , passwordHash : hash , statistics : { test : { nested : 'abc' , } , } , playerID : playerID , meta : { ... userData , } , } ; await this . datastore . insert ( { key : userKey , data : user , excludeFromIndexes : [ 'passwordHash' ] , } ) ; await this . datastore . insert ( { key : playerIDkey , data : { } , } ) ; } private getUserDatastoreKey ( username ) { return this . datastore . key ( [ GCloudDataStore . Kinds . USERS , username ] ) ; } private getMatchDatastoreKey ( matchID ) { return this . datastore . key ( [ GCloudDataStore . Kinds . MATCHES , matchID ] ) ; } private getTournamentConfigsDatastoreKey ( tournamentID ) { return this . datastore . key ( [ GCloudDataStore . Kinds . TOURNAMENT_CONFIGS , tournamentID , ] ) ; } param usernameOrID public async getUser ( usernameOrID , publicView = true ) { let user ; const q = this . datastore . createQuery ( GCloudDataStore . Kinds . USERS ) . filter ( 'playerID' , '=' , usernameOrID ) ; const res = await this . datastore . runQuery ( q ) ; user = res [ 0 ] [ 0 ] ; if ( ! user ) { const key = this . getUserDatastoreKey ( usernameOrID ) ; user = ( await this . datastore . get ( key ) ) [ 0 ] ; } if ( publicView && user ) { delete user . passwordHash ; } return user ; } public async loginUser ( username , password ) { const userKey = this . getUserDatastoreKey ( username ) ; const user = ( await this . datastore . get ( userKey ) ) [ 0 ] ; if ( user ) { if ( bcrypt . compareSync ( password , user . passwordHash ) ) { return generateToken ( user ) ; } else { throw new Error ( 'Invalid password' ) ; } } else { throw new Error ( 'Not a valid user' ) ; } } public async updateUser ( usernameOrID , update ) { let user = await this . getUser ( usernameOrID ) ; const userKey = this . getUserDatastoreKey ( user . username ) ; user = deepMerge ( user , update ) ; await this . datastore . update ( { key : userKey , data : user , } ) ; } public async deleteUser ( usernameOrID ) { const user = await this . getUser ( usernameOrID ) ; const userKey = this . getUserDatastoreKey ( user . username ) ; await this . datastore . delete ( { key : userKey , } ) ; } public async verifyToken ( jwt ) { return verify ( jwt ) ; } public isAdmin ( user ) { if ( user . username === 'admin' ) return true ; return false ; } public async getUsersInTournament ( tournamentKey , offset = 0 , limit = - 1 ) { const key = `statistics.${ tournamentKey }` ; if ( limit == - 1 ) { limit = 0 ; } else if ( limit == 0 ) { return [ ] ; } const q = this . datastore . createQuery ( GCloudDataStore . Kinds . USERS ) . filter ( `${ key }.matchesPlayed` , '>=' , 0 ) . offset ( offset ) ; return ( await this . datastore . runQuery ( q ) ) [ 0 ] ; } public async manipulate ( dimension ) { dimension . configs . backingDatabase = this . name ; return ; } public async storeTournamentConfigs ( tournamentID , tournamentConfigs , status ) { const key = this . getTournamentConfigsDatastoreKey ( tournamentID ) ; await this . datastore . upsert ( { key : key , data : { configs : stripFunctions ( deepCopy ( tournamentConfigs ) ) , id : tournamentID , status : status , modificationDate : new Date ( ) , } , } ) ; } public async getTournamentConfigsModificationDate ( tournamentID ) { const key = this . getTournamentConfigsDatastoreKey ( tournamentID ) ; const data = ( await this . datastore . get ( key ) ) [ 0 ] ; if ( data ) { return new Date ( data . modificationDate ) ; } return null ; } public async getTournamentConfigs ( tournamentID ) { const key = this . getTournamentConfigsDatastoreKey ( tournamentID ) ; const data = ( await this . datastore . get ( key ) ) [ 0 ] ; if ( data ) { return { configs : data . configs , status : data . status } ; } return null ; } } export namespace GCloudDataStore { /**
     * Configurations for Google Cloud Datastore
     */ export interface Configs { /**
         * Keyfile to use for authentication
         */ keyFile : string ; } /**
     * The kind of various groupings in the datastore
     */ export enum Kinds { /**
         * For all user data
         */ USERS = 'users' , /**
         * Player ID data for uniqueness
         */ PLAYER_IDS = 'playerIds' , /**
         * For all tournament configuration data
         */ TOURNAMENT_CONFIGS = 'tournamentConfigs' , /**
         * For all match data
         */ MATCHES = 'matches' } }