import * as functions from 'firebase-functions' ; import { readFileSync , stat } from 'fs' ; import { resolve } from 'path' ; import { friendInvitationConstants , Game , Invitation , LeaderBoardUsers , OpponentType , PlayerMode , pushNotificationRouteConstants , Question , QuestionStatus , SystemStatConstants , TriggerConstants , UserStatConstants , UserStatus , User , UserStatusConstants } from '../../projects/shared-library/src/lib/shared/model' ; import { AccountService } from '../services/account.service' ; import { AppSettings } from '../services/app-settings.service' ; import { LeaderBoardService } from '../services/leaderboard.service' ; import { StatsService } from '../services/stats.service' ; import { AchievementMechanics } from '../utils/achievement-mechanics' ; import { ESUtils } from '../utils/ESUtils' ; import { GamePlayedWithStats } from '../utils/game-played-with-stats' ; import { GameLeaderBoardStats } from '../utils/game-leader-board-stats' ; import { MailClient } from '../utils/mail-client' ; import { PushNotification } from '../utils/push-notifications' ; import { UserContributionStat } from '../utils/user-contribution-stat' ; import admin from './firebase.client' ; import { UserService } from '../services/user.service' ; import { QuestionService } from '../services/question.service' ; import { Utils } from '../utils/utils' ; import { UserStatusService } from '../services/user-status.service' ; const mailConfig = JSON . parse ( readFileSync ( resolve ( __dirname , '../../../config/mail.config.json' ) , 'utf8' ) ) ; export class FirebaseFunctions { static async addMessage ( firebaseFunctions ) { firebaseFunctions . https . onRequest ( async ( req , res ) => { const original = req . query . text ; try { const snapshot = await admin . database ( ) . ref ( '/messages' ) . push ( { original : original } ) ; res . redirect ( 303 , snapshot . ref ) ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } ) ; } static async doQuestionWriteOperation ( change , context ) { try { const data = change . after . data ( ) ; if ( data ) { const question = data ; data . createdOn = ( data . createdOn && data . createdOn [ '_seconds' ] ) ? new Date ( data . createdOn [ '_seconds' ] * 1000 ) : new Date ( ) ; await ESUtils . createOrUpdateIndex ( data . categoryIds [ '0' ] , question , context . params . questionId ) ; await UserContributionStat . getUser ( question . created_uid , UserStatConstants . initialContribution , false ) ; await StatsService . updateSystemStats ( 'total_questions' ) ; } else { await ESUtils . removeIndex ( context . params . questionId ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doReactionWriteOperation ( change , context ) { try { if ( context . params . reactions === 'reactions' ) { const afterData = change . after . exists ? change . after . data ( ) : null ; const beforeData = change . before . exists ? change . before . data ( ) : null ; const question = await QuestionService . getQuestionById ( context . params . questionId ) ; if ( beforeData && afterData ) { if ( beforeData . status != afterData . status ) { question . stats . reactionsCount [ afterData . status ] = question . stats . reactionsCount && question . stats . reactionsCount [ afterData . status ] ? Utils . changeFieldValue ( 1 ) : 1 ; question . stats . reactionsCount [ beforeData . status ] = question . stats . reactionsCount && question . stats . reactionsCount [ beforeData . status ] ? Utils . changeFieldValue ( - 1 ) : 0 ; } else { return true ; } } else if ( ! beforeData && afterData ) { question . stats . reactionsCount [ afterData . status ] = question . stats . reactionsCount && question . stats . reactionsCount [ afterData . status ] ? Utils . changeFieldValue ( 1 ) : 1 ; } else if ( beforeData && ! afterData ) { question . stats . reactionsCount [ beforeData . status ] = question . stats . reactionsCount && question . stats . reactionsCount [ beforeData . status ] ? Utils . changeFieldValue ( - 1 ) : 0 ; } else { return true ; } const newquestion = { ... question } ; await QuestionService . updateQuestion ( 'questions' , newquestion ) ; return true ; } else { return true ; } } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doInvitationWriteOperation ( change , context ) { try { const beforeEventData = change . before . data ( ) ; const afterEventData = change . after . data ( ) ; const invitation = afterEventData ; if ( afterEventData !== beforeEventData && mailConfig . enableMail && invitation . status === friendInvitationConstants . PENDING ) { const url = `${ mailConfig . hosturl }${ invitation . id }` ; const htmlContent = `You have a new invitation request.
                 Click <a href="${ url }">Accept Invitation</a> to accept the invitation.` ; const mail = new MailClient ( invitation . email , TriggerConstants . invitationMailSubject , TriggerConstants . invitationTxt , htmlContent ) ; await mail . sendMail ( ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doGameCreateOperation ( snap , context ) { try { const data = snap . data ( ) ; if ( data ) { await StatsService . updateSystemStats ( 'active_games' ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doGameUpdateOperation ( change , context ) { try { const beforeEventData = change . before . data ( ) ; const afterEventData = change . after . data ( ) ; if ( afterEventData !== beforeEventData ) { const game = Game . getViewModel ( afterEventData ) ; if ( game . gameOver ) { await StatsService . updateSystemStats ( SystemStatConstants . ACTIVE_GAMES ) ; await StatsService . updateSystemStats ( SystemStatConstants . GAME_PLAYED ) ; await GameLeaderBoardStats . getGameUsers ( game ) ; if ( Number ( game . gameOptions . playerMode ) === PlayerMode . Opponent && Number ( game . gameOptions . opponentType ) === OpponentType . Friend ) { await GamePlayedWithStats . calculateUserGamePlayedState ( game ) ; } } await StatsService . calculateQuestionAndAccountStat ( beforeEventData , afterEventData ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doUserCreateOperation ( snap , context ) { try { const data = snap . data ( ) ; if ( data ) { await StatsService . updateSystemStats ( 'total_users' ) ; await UserService . setUserDetails ( data ) ; const appSetting = await AppSettings . Instance . getAppSettings ( ) ; if ( appSetting . lives . enable ) { const accountObj = { } ; accountObj . id = data . userId ; accountObj . lives = appSetting . lives . max_lives ; accountObj . lastGamePlayed = Utils . getUTCTimeStamp ( ) ; accountObj . lastGamePlayedNotification = false ; await AccountService . setAccount ( accountObj ) ; } } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doAccountUpdateOperation ( change , context ) { try { const beforeEventData = change . before . data ( ) ; const afterEventData = change . after . data ( ) ; if ( afterEventData !== beforeEventData ) { const account = afterEventData ; await LeaderBoardService . setLeaderBoardStatForSingleUser ( account ) ; await AchievementMechanics . updateAchievement ( account ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doUnpublishedQuestionsUpdateOperation ( change , context ) { try { const beforeEventData = change . before . data ( ) ; const afterEventData = change . after . data ( ) ; const appSetting = await AppSettings . Instance . getAppSettings ( ) ; if ( beforeEventData . status !== afterEventData . status ) { const oldStatus = QuestionStatus [ beforeEventData . status ] . toLowerCase ( ) . replace ( '_' , ' ' ) ; const newStatus = QuestionStatus [ afterEventData . status ] . toLowerCase ( ) . replace ( '_' , ' ' ) ; const message = PushNotification . compileTemplateString ( appSetting . notification_template . question_notifications_status_change . message , { oldStatus : oldStatus , newStatus : newStatus } ) ; console . log ( 'message' , message ) ; PushNotification . sendGamePlayPushNotifications ( message , afterEventData . created_uid , pushNotificationRouteConstants . QUESTION_NOTIFICATIONS ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doQuestionCreateOperation ( snap , context ) { try { const data = snap . data ( ) ; if ( data ) { const question = data ; const appSetting = await AppSettings . Instance . getAppSettings ( ) ; const message = appSetting . notification_template . question_notifications_approved . message ; console . log ( 'Notification sent on question approved' ) ; PushNotification . sendGamePlayPushNotifications ( message , question . created_uid , pushNotificationRouteConstants . QUESTION_NOTIFICATIONS ) ; await AccountService . earnBytesOnQuestionContribute ( question . created_uid ) ; } return true ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } static async doUserStatusUpdateOperation ( change , context ) { try { const userDataStatus = change . after . val ( ) ; const token = context . params . tokenId ; console . log ( 'userDataStatus' , userDataStatus ) ; const onlineStatus = ( userDataStatus . status === 'online' ) ? true : false ; let userStatus = await UserStatusService . getUserStatusById ( userDataStatus . userId ) ; if ( ! userStatus ) { userStatus = new UserStatus ( ) ; userStatus . userId = userDataStatus . userId ; } const user = await UserService . getUserById ( userDataStatus . userId ) ; if ( userDataStatus . device !== TriggerConstants . WEB ) { userDataStatus . status = user . status ; if ( userDataStatus . device === TriggerConstants . ANDROID ) { const deviceTokenIndex = user . androidPushTokens . findIndex ( ( androidPushToken ) => ( androidPushToken === token || ( androidPushToken && androidPushToken . token && androidPushToken . token === token ) ) ) ; if ( deviceTokenIndex !== - 1 ) { user . androidPushTokens [ deviceTokenIndex ] . online = onlineStatus ; } } else { const deviceTokenIndex = user . iosPushTokens . findIndex ( ( iosPushToken ) => ( iosPushToken === token || ( iosPushToken && iosPushToken . token && iosPushToken . token === token ) ) ) ; if ( deviceTokenIndex !== - 1 ) { user . iosPushTokens [ deviceTokenIndex ] . online = onlineStatus ; } } await UserService . updateUser ( { ... user } ) ; } const onlineOnAndroid = user . androidPushTokens && user . androidPushTokens . length > 0 ? user . androidPushTokens . some ( ( androidPushToken ) => androidPushToken . token && androidPushToken . online ) : false ; const onlineOnIos = user . iosPushTokens && user . iosPushTokens . length > 0 ? user . iosPushTokens . some ( ( iosPushToken ) => iosPushToken . token && iosPushToken . online ) : false ; userStatus . online = ( onlineOnAndroid || onlineOnIos || userDataStatus . status === UserStatusConstants . ONLINE ) ? true : false ; userStatus . lastUpdated = new Date ( ) . getTime ( ) ; console . log ( 'userStatus' , userStatus ) ; return UserStatusService . updateUserStatus ( { ... userStatus } ) ; } catch ( error ) { console . error ( 'Error :' , error ) ; throw error ; } } } exports . onQuestionWrite = functions . firestore . document ( '/questions/{questionId}' ) . onWrite ( async ( change , context ) => await FirebaseFunctions . doQuestionWriteOperation ( change , context ) ) ; exports . onReactionWrite = functions . firestore . document ( '/questions/{questionId}/{reactions}/{userId}' ) . onWrite ( async ( snap , context ) => await FirebaseFunctions . doReactionWriteOperation ( snap , context ) ) ; exports . onInvitationWrite = functions . firestore . document ( '/invitations/{invitationId}' ) . onWrite ( async ( change , context ) => await FirebaseFunctions . doInvitationWriteOperation ( change , context ) ) ; exports . onGameCreate = functions . firestore . document ( '/games/{gameId}' ) . onCreate ( async ( snap , context ) => await FirebaseFunctions . doGameCreateOperation ( snap , context ) ) ; exports . onGameUpdate = functions . firestore . document ( '/games/{gameId}' ) . onUpdate ( async ( change , context ) => await FirebaseFunctions . doGameUpdateOperation ( change , context ) ) ; exports . onUserCreate = functions . firestore . document ( '/users/{userId}' ) . onCreate ( async ( snap , context ) => await FirebaseFunctions . doUserCreateOperation ( snap , context ) ) ; exports . onAccountUpdate = functions . firestore . document ( '/accounts/{accountId}' ) . onUpdate ( async ( change , context ) => await FirebaseFunctions . doAccountUpdateOperation ( change , context ) ) ; exports . onUnpublishedQuestionsUpdate = functions . firestore . document ( '/unpublished_questions/{questionId}' ) . onUpdate ( async ( change , context ) => await FirebaseFunctions . doUnpublishedQuestionsUpdateOperation ( change , context ) ) ; exports . onQuestionCreate = functions . firestore . document ( '/questions/{questionId}' ) . onCreate ( async ( snap , context ) => await FirebaseFunctions . doQuestionCreateOperation ( snap , context ) ) ; exports . onUserStatusWrite = functions . database . ref ( '/users/{tokenId}' ) . onWrite ( async ( change , context ) => await FirebaseFunctions . doUserStatusUpdateOperation ( change , context ) ) ;