import mongoose from 'mongoose' ; import { Plugin } from '../../Plugin' ; import { Database } from '../../Plugin/Database' ; import { Dimension , DatabaseType , NanoID } from '../../Dimension' ; import MatchSchemaCreator from './models/match' ; import { Match } from '../../Match' ; import { DeepPartial } from '../../utils/DeepPartial' ; import { pickMatch } from '../../Station/routes/api/dimensions/match' ; import bcrypt from 'bcryptjs' ; import UserSchemaCreator from './models/user' ; import { generateToken , verify } from '../../Plugin/Database/utils' ; import { Tournament } from '../../Tournament' ; import { pick } from '../../utils' ; import { nanoid } from '../..' ; import TournamentConfigSchema from './models/tournamentConfig' ; import { TournamentStatus } from '../../Tournament/TournamentStatus' ; require ( 'dotenv' ) . config ( ) ; const salt = bcrypt . genSaltSync ( ) ; export class MongoDB extends Database { public name = 'MongoDB' ; public type = Plugin . Type . DATABASE ; public db : mongoose . Connection ; public mongoose : mongoose . Mongoose ; public models : MongoDB . Models = { user : null , match : null , tournamentConfigs : null , } ; /** The MongoDB connection string used to connect to the database and read/write to it */ public connectionString : string ; constructor ( connectionString , configs = { } ) { super ( configs ) ; this . mongoose = new mongoose . Mongoose ( { useUnifiedTopology : true } ) ; this . mongoose . set ( 'useCreateIndex' , true ) ; this . mongoose . set ( 'useFindAndModify' , false ) ; this . connectionString = connectionString ; const matchSchema = MatchSchemaCreator ( ) ; this . models . match = this . mongoose . model ( 'Match' , matchSchema ) ; const userSchema = UserSchemaCreator ( ) ; this . models . user = this . mongoose . model ( 'User' , userSchema ) ; this . models . tournamentConfigs = this . mongoose . model ( 'TournamentConfigs' , TournamentConfigSchema ) ; } /**
     * Connects to the mongo database and returns the Connection object
     */ public async connect ( ) { this . mongoose . connect ( this . connectionString , { useNewUrlParser : true } ) ; this . db = this . mongoose . connection ; this . db . on ( 'error' , console . error . bind ( console , 'connection error:' ) ) ; return this . db ; } public async initialize ( ) { await this . connect ( ) ; 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 } ; return this . models . match . create ( data ) ; } public async getMatch ( id ) { return this . models . match . findOne ( { id : id } ) ; } public async getPlayerMatches ( usernameOrID , governID , offset = 0 , limit = 10 , order = - 1 ) { const user = await this . getUser ( usernameOrID ) ; if ( user ) { return this . models . match . aggregate ( [ { $match : { governID : { $eq : governID , } , 'agents.tournamentID.id' : { $eq : user . playerID , } , } , } , { $sort : { creationDate : order , } , } , { $skip : offset , } , { $limit : limit , } , ] ) ; } else { return [ ] ; } } public async registerUser ( username , password , userData ? ) { const hash = bcrypt . hashSync ( password , salt ) ; return this . models . user . create ( { username : username , passwordHash : hash , statistics : { } , meta : { ... userData , } , } ) ; } param usernameOrID public async getUser ( usernameOrID , publicView = true ) { return this . models . user . findOne ( { $or : [ { username : usernameOrID } , { playerID : usernameOrID } ] , } ) . then ( ( user ) => { let obj ; if ( user ) { obj = user . toObject ( ) ; if ( ! publicView ) return obj ; const d = < Database . User > ( pick ( obj , 'creationDate' , 'meta' , 'statistics' , 'playerID' , 'username' ) ) ; obj = { ... d , passwordHash : '' } ; return obj ; } return null ; } ) ; } public async loginUser ( username , password ) { return this . models . user . findOne ( { username : username } ) . then ( ( user ) => { 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 ) { return this . models . user . findOneAndUpdate ( { $or : [ { username : usernameOrID } , { playerID : usernameOrID } ] } , update ) . then ( ( user ) => { if ( user ) { return user . toObject ( ) ; } else { throw new Error ( 'Not a valid user' ) ; } } ) ; } public async deleteUser ( usernameOrID ) { return this . models . user . findOneAndDelete ( { $or : [ { username : usernameOrID } , { playerID : usernameOrID } ] , } ) . then ( ( user ) => { if ( ! user ) { throw new Error ( 'Not a valid user' ) ; } } ) ; } 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 [ ] ; } return this . models . user . find ( { [ key ] : { $exists : true } } ) . skip ( offset ) . limit ( limit ) . then ( ( users ) => { const mapped = users . map ( ( user ) => user . toObject ( ) ) ; return mapped ; } ) ; } public async manipulate ( dimension ) { dimension . configs . backingDatabase = DatabaseType . MONGO ; return ; } public async storeTournamentConfigs ( tournamentID , tournamentConfigs , status ) { return this . models . tournamentConfigs . updateOne ( { id : tournamentID } , { configs : tournamentConfigs , id : tournamentID , status : status , modificationDate : new Date ( ) , } , { upsert : true } ) ; } public async getTournamentConfigsModificationDate ( tournamentID ) { return this . models . tournamentConfigs . findOne ( { id : tournamentID } ) . select ( { modificationDate : 1 } ) . then ( ( date ) => { if ( date ) { return new Date ( date . toObject ( ) . modificationDate ) ; } else { return null ; } } ) ; } public async getTournamentConfigs ( tournamentID ) { return this . models . tournamentConfigs . findOne ( { id : tournamentID } ) . then ( ( data ) => { if ( data ) { return data . toObject ( ) ; } else { return null ; } } ) ; } } export namespace MongoDB { /**
     * See  Match class for what these fields represent. They are copied here letter for letter. If set true, the
     * field will be included into the database
      export interface MatchSchemaOptions { state : boolean ; results : boolean ; creationDate : boolean ; finishDate : boolean ; agents : boolean ; } /**
     * User Schema Options. If set to true, that field will be included into the database.
     */ export interface UserSchemaOptions { /** Creation date of the user */ creationDate : boolean ; } export interface Models { user : mongoose . Model < mongoose . Document > ; match : mongoose . Model < mongoose . Document > ; tournamentConfigs : mongoose . Model < mongoose . Document > ; } }