import { Request , Response } from "express" ; import { Db , Collection , MongoError , UpdateFilter } from "mongodb" ; import { ConfigManager } from "../ConfigManager" ; import { MongoErrorCodes } from "../models/MongoErrorCodes" ; import { IParty } from "../models/IParty" ; import { IPartyData } from "../models/IPartyData" ; import { IGrailCollection } from "../models/IGrailCollection" ; import { GrailController } from "./GrailController" ; export class PartyController { private get partyCollection ( ) : Collection < IParty > { return this . db . collection < IParty > ( ConfigManager . db . partyCollection ) ; } private get grailCollection ( ) : Collection < IGrailCollection > { return this . db . collection < IGrailCollection > ( ConfigManager . db . holyGrailCollection ) ; } public constructor ( private db ) { } public add = async ( req , res ) => { let newParty = req . body ; const originalAddress = newParty . address ; newParty . address = PartyController . trimAndToLower ( newParty . address ) ; newParty . token = PartyController . getToken ( ) ; newParty . created = newParty . modified = new Date ( ) ; newParty . userlist = [ ] ; newParty . pendingUserlist = [ ] ; try { const result = await this . partyCollection . insertOne ( newParty ) ; const party = await this . partyCollection . findOne ( { _id : result . insertedId , } ) ; const partyData = await this . getPartyData ( party ) ; PartyController . mapAndReturnPartyData ( originalAddress , res , party , partyData ) ; } catch ( err ) { const mongoError = err as MongoError ; if ( mongoError . code === MongoErrorCodes . DuplicateKey ) { res . status ( 400 ) . send ( { type : "duplicateKey" , address : originalAddress } ) ; return ; } PartyController . sendUnknownError ( res , err ) ; } } ; public get = async ( req , res ) => { const address = GrailController . trimAndToLower ( req . params . address ) ; await this . getByAddress ( address , res , async ( party ) => PartyController . mapAndReturnPartyData ( address , res , party , await this . getPartyData ( party ) ) ) ; } ; private getPartyData = async ( party ) : Promise < any > => { const grails = await this . grailCollection . find ( { address : { $in : party . userlist , } , } ) . project ( { address : true , partyData : true , } ) . toArray ( ) ; return this . mapGrailsToPartyData ( grails as IGrailCollection [ ] ) ; } ; private mapGrailsToPartyData = ( grails ) => { let partyData = { users : [ ] , } ; grails . forEach ( ( grail ) => { partyData . users . push ( { username : grail . address , data : grail . partyData , } ) ; } ) ; return partyData ; } ; public validatePassword = async ( req , res ) => { const address = GrailController . trimAndToLower ( req . params . address ) ; const password = req . body . password ; if ( ! password && address ) { res . json ( false ) ; return ; } const party = await this . partyCollection . findOne ( { address : PartyController . trimAndToLower ( address ) , } ) ; if ( ! party ) { res . status ( 404 ) . send ( { type : "notFound" , address } ) ; } else { res . json ( party . password === password ) ; } } ; public updatePartyUser = async ( req , res ) => { try { const method = req . body . method ; const address = GrailController . trimAndToLower ( req . body . address ) ; const password = req . body . password ; const token = req . body . token ; const user = GrailController . trimAndToLower ( req . body . user ) ; switch ( method ) { case "accept" : await this . update ( res , address , password , token , ( dataToSet ) => dataToSet , { $pull : { pendingUserlist : user } , $addToSet : { userlist : user } , } ) ; break ; case "deny" : await this . update ( res , address , password , token , ( dataToSet ) => dataToSet , { $pull : { pendingUserlist : user } , } ) ; break ; case "remove" : await this . update ( res , address , password , token , ( dataToSet ) => dataToSet , { $pull : { userlist : user } , } ) ; break ; case "join" : await this . addUserToParty ( req , res ) ; break ; default : res . status ( 404 ) . send ( { type : "methodUnknown" , method } ) ; } } catch ( err ) { PartyController . sendUnknownError ( res , err ) ; } } ; private addUserToParty = async ( req , res ) => { const grailAddress = GrailController . trimAndToLower ( req . body . user ) ; const partyAddress = GrailController . trimAndToLower ( req . body . address ) ; const result = await this . grailCollection . findOne ( { address : GrailController . trimAndToLower ( grailAddress ) , } ) ; if ( ! result ) { res . status ( 404 ) . send ( { type : "userNotFound" , grailAddress } ) ; return ; } const party = await this . partyCollection . findOne ( { address : GrailController . trimAndToLower ( partyAddress ) , } ) ; if ( ! party ) { res . status ( 404 ) . send ( { type : "partyNotFound" , partyAddress } ) ; return ; } if ( party . userlist . indexOf ( grailAddress ) !== - 1 || party . pendingUserlist . indexOf ( grailAddress ) !== - 1 ) { res . status ( 409 ) . send ( { type : "conflict" , grailAddress } ) ; return ; } await this . update ( res , party . address , party . password , party . token , ( dataToSet ) => dataToSet , { $addToSet : { pendingUserlist : grailAddress } , } ) ; } ; private handleDbError = async ( address , password , token , res ) => { await this . getByAddress ( address , res , ( existingParty ) => { if ( existingParty . password !== password ) { res . status ( 401 ) . send ( { type : "password" , address } ) ; } else if ( existingParty . token !== token ) { res . status ( 403 ) . send ( { type : "token" , correctToken : existingParty . token , specifiedToken : token , address , } ) ; } else { PartyController . sendUnknownError ( res , "Database error" ) ; } } ) ; } ; private update = async ( res , address , password , token , modifyDataToSaveFunc , optionalUpdates ? ) => { try { const updateQuery = optionalUpdates ? optionalUpdates : { } ; updateQuery . $set = modifyDataToSaveFunc ( { token : PartyController . getToken ( ) , modified : new Date ( ) , } ) ; const result = await this . partyCollection . findOneAndUpdate ( { address : PartyController . trimAndToLower ( address ) , password : password , token : token , } , updateQuery , { returnDocument : "after" } ) ; if ( result && result . ok && result . value ) { PartyController . mapAndReturnPartyData ( address , res , result . value , await this . getPartyData ( result . value ) ) ; return ; } await this . handleDbError ( address , password , token , res ) ; } catch ( err ) { PartyController . sendUnknownError ( res , err ) ; } } ; private async getByAddress ( address , res , onSuccess ) { try { const party = await this . partyCollection . findOne ( { address : PartyController . trimAndToLower ( address ) , } ) ; if ( ! party ) { res . status ( 404 ) . send ( { type : "notFound" , address } ) ; return ; } onSuccess ( party ) ; } catch ( err ) { PartyController . sendUnknownError ( res , err ) ; } } private static mapAndReturnPartyData ( originalAddress , res , party , data ? ) { res . json ( { address : originalAddress , userlist : party . userlist , pendingUserlist : party . pendingUserlist , token : party . token , data : data , } as IPartyData ) ; } private static sendUnknownError ( res , error ? ) { res . status ( 500 ) . send ( { type : "unknown" , error } ) ; } private static getToken ( ) { return new Date ( ) . toISOString ( ) ; } private static trimAndToLower ( value ) { return value ? value . toLowerCase ( ) . trim ( ) : null ; } }