'use strict' ; import Config from '@Base/config' ; import { Domains } from '@Entities/Domains' ; import { AccountEntity } from '@Entities/AccountEntity' ; import { PlaceEntity } from '@Entities/PlaceEntity' ; import { placeFields } from '@Entities/PlaceFields' ; import { DomainEntity } from './DomainEntity' ; import { AuthToken } from '@Entities/AuthToken' ; import { Tokens , TokenScope } from '@Entities/Tokens' ; import { CriteriaFilter } from '@Entities/EntityFilters/CriteriaFilter' ; import { GenericFilter } from '@Entities/EntityFilters/GenericFilter' ; import { ValidateResponse } from '@Route-Tools/EntityFieldDefn' ; import { getEntityField , setEntityField , getEntityUpdateForField } from '@Route-Tools/GetterSetter' ; import { createObject , getObject , getObjects , updateObjectFields , deleteOne , deleteMany , noCaseCollation } from '@Tools/Db' ; import { GenUUID , IsNullOrEmpty , IsNotNullOrEmpty , genRandomString } from '@Tools/Misc' ; import { VKeyedCollection } from '@Tools/vTypes' ; import { Logger } from '@Tools/Logging' ; import { PlaceFilterInfo } from './EntityFilters/PlaceFilterInfo' ; import { Accounts } from './Accounts' ; export let placeCollection = 'places' ; export function initPlaces ( ) { const placer = new PlaceFilterInfo ( ) ; setInterval ( async ( ) => { let numPlaces = 0 ; let numUnhookedPlaces = 0 ; let numInactivePlaces = 0 ; const inactivePlaceTime = Places . dateWhenNotActive ( ) ; const lastGoodUpdateTime = new Date ( Date . now ( ) - ( Config [ 'metaverse-server' ] [ 'place-current-timeout-minutes' ] * 60 * 1000 ) ) ; for await ( const aPlace of Places . enumerateAsync ( placer ) ) { numPlaces ++ ; const updates = { } ; if ( IsNullOrEmpty ( aPlace . currentLastUpdateTime ) || aPlace . currentLastUpdateTime < lastGoodUpdateTime ) { const aDomain = await Domains . getDomainWithId ( aPlace . domainId ) ; if ( aDomain ) { const domainAttendance = aDomain . numUsers ?? 0 ; if ( IsNullOrEmpty ( aPlace . currentAttendance ) || aPlace . currentAttendance !== domainAttendance ) { aPlace . currentAttendance = domainAttendance ; updates . currentAttendance = aPlace . currentAttendance ; } ; aPlace . lastActivity = aDomain . timeOfLastHeartbeat ?? inactivePlaceTime ; updates . lastActivity = aPlace . lastActivity ; } else { numUnhookedPlaces ++ ; aPlace . currentAttendance = 0 ; aPlace . lastActivity = inactivePlaceTime ; updates . currentAttendance = aPlace . currentAttendance ; updates . lastActivity = aPlace . lastActivity ; } ; } else { if ( IsNullOrEmpty ( aPlace . lastActivity ) || aPlace . lastActivity !== aPlace . currentLastUpdateTime ) { aPlace . lastActivity = aPlace . currentLastUpdateTime ; updates . lastActivity = aPlace . lastActivity ; } ; } ; if ( aPlace . lastActivity <= inactivePlaceTime ) { numInactivePlaces ++ ; } ; await Places . updateEntityFields ( aPlace , updates ) ; } ; } , 1000 * Config [ 'metaverse-server' ] [ 'place-check-last-activity-seconds' ] ) ; } ; export const Places = { async getPlaceWithId ( pPlaceId ) { return IsNullOrEmpty ( pPlaceId ) ? null : getObject ( placeCollection , new GenericFilter ( { 'id' : pPlaceId } ) ) ; } , async getPlaceWithName ( pPlacename ) { return IsNullOrEmpty ( pPlacename ) ? null : getObject ( placeCollection , new GenericFilter ( { 'name' : pPlacename } ) , noCaseCollation ) ; } , async addPlace ( pPlaceEntity ) { Logger . info ( `Places: creating place ${ pPlaceEntity . name }, id=${ pPlaceEntity . id }` ) ; return IsNullOrEmpty ( pPlaceEntity ) ? null : createObject ( placeCollection , pPlaceEntity ) ; } , async createPlace ( pAccountId ) { const newPlace = new PlaceEntity ( ) ; newPlace . id = GenUUID ( ) ; newPlace . name = 'UNKNOWN-' + genRandomString ( 5 ) ; newPlace . path = '/0,0,0/0,0,0,1' ; newPlace . whenCreated = new Date ( ) ; newPlace . currentAttendance = 0 ; const APItoken = await Tokens . createToken ( pAccountId , [ TokenScope . PLACE ] , - 1 ) ; await Tokens . addToken ( APItoken ) ; newPlace . currentAPIKeyTokenId = APItoken . id ; return newPlace ; } , async uniqifyPlaceName ( pPlaceName ) { let newPlacename = pPlaceName ; const existingPlace = await Places . getPlaceWithName ( newPlacename ) ; if ( existingPlace ) { newPlacename = newPlacename + '-' + genRandomString ( 5 ) ; Logger . info ( `uniqifyPlaceName: non-unique place name ${ pPlaceName }. Creating ${ newPlacename }` ) ; } ; return newPlacename ; } , async getField ( pAuthToken , pPlace , pField , pRequestingAccount ? ) { return getEntityField ( placeFields , pAuthToken , pPlace , pField , pRequestingAccount ) ; } , async setField ( pAuthToken , pPlace , pField , pVal , pRequestingAccount ? , pUpdates ? ) { return setEntityField ( placeFields , pAuthToken , pPlace , pField , pVal , pRequestingAccount , pUpdates ) ; } , async validateFieldValue ( pFieldName , pValue ) { const defn = placeFields [ pFieldName ] ; if ( defn ) { return await defn . validate ( defn , defn . request_field_name , pValue ) ; } ; return { 'valid' : false , 'reason' : 'Unknown field name' } ; } , getUpdateForField ( pPlace , pField , pExisting ? ) { return getEntityUpdateForField ( placeFields , pPlace , pField , pExisting ) ; } , async removePlace ( pPlaceEntity ) { Logger . info ( `Places: removing place ${ pPlaceEntity . name }, id=${ pPlaceEntity . id }` ) ; return deleteOne ( placeCollection , new GenericFilter ( { 'id' : pPlaceEntity . id } ) ) ; } , async removeMany ( pCriteria ) { return deleteMany ( placeCollection , pCriteria ) ; } , async * enumerateAsync ( pPager , pInfoer ? , pScoper ? ) { for await ( const place of getObjects ( placeCollection , pPager , pInfoer , pScoper ) ) { yield place ; } ; } , async updateEntityFields ( pEntity , pFields ) { return updateObjectFields ( placeCollection , new GenericFilter ( { 'id' : pEntity . id } ) , pFields ) ; } , dateWhenNotActive ( ) { return new Date ( Date . now ( ) - ( Config [ 'metaverse-server' ] [ 'place-inactive-timeout-minutes' ] * 60 * 1000 ) ) ; } , async getCurrentInfoAPIKey ( pPlace ) { let key ; const keyToken = await Tokens . getTokenWithTokenId ( pPlace . currentAPIKeyTokenId ) ; if ( IsNotNullOrEmpty ( keyToken ) ) { key = keyToken . token ; } ; return key ; } , async getAddressString ( pPlace ) { let addr = pPlace . path ?? '/0,0,0/0,0,0,1' ; const pieces = addr . split ( '/' ) ; if ( pieces [ 0 ] . length === 0 ) { const aDomain = await Domains . getDomainWithId ( pPlace . domainId ) ; if ( IsNotNullOrEmpty ( aDomain ) ) { if ( IsNotNullOrEmpty ( aDomain . networkAddr ) ) { let domainAddr = aDomain . networkAddr ; if ( IsNotNullOrEmpty ( aDomain . networkPort ) ) { domainAddr = aDomain . networkAddr + ":" + aDomain . networkPort ; } ; addr = domainAddr + addr ; } ; } ; } ; return addr ; } , async getManagers ( pPlace ) { if ( IsNullOrEmpty ( pPlace . managers ) ) { pPlace . managers = [ ] ; const aDomain = await Domains . getDomainWithId ( pPlace . domainId ) ; if ( aDomain ) { const aAccount = await Accounts . getAccountWithId ( aDomain . sponsorAccountId ) ; if ( aAccount ) { pPlace . managers = [ aAccount . username ] ; } ; } ; await Places . updateEntityFields ( pPlace , { 'managers' : pPlace . managers } ) ; } return pPlace . managers ; } } ;