<commit_before>'use strict'

/* TODO:
	Mapping to one 1st-party platform session ID as required
*/

const uuid = require('node-uuid')
const jsonschema = require('jsonschema')

// if we can't resolve an IP, dump it at the north pole to match with other people we can't classify
// TODO: rethink this, maybe push these into a separate sub-pool to match amongst themselves
const northPoleCoords = [ 0, 90 ]

const defaultPool = {
	querySize: 10,
	must: [],
	defaults: {},
}

const poolSchema = {
	properties: {
		maxSpaces: { type: 'int' },
	},
	required: [
		'maxSpaces',
	],
}

module.exports = function*(loader)
{
	class MatchmakingService extends loader.Service
	{
		get serviceErrors() { return [
			'poolNotFound',
			'sessionInaccessible',
		]}
		
		*init()
		{
			this.db = yield this.createMongoConnection('matchmaking')
			this.poolConfig = loader.require(this.config.matchmaking.data)
			
			const poolValidator = new jsonschema.Validator()
			for (const poolName in this.poolConfig.pools)
			{
				const pool = this.poolConfig.pools[poolName]
				for (var prop in defaultPool)
				{
					if (pool[prop] === undefined)
					{
						pool[prop] = defaultPool[prop]
					}
				}
				
				const poolResult = poolValidator.validate(pool, poolSchema)
				if (!poolResult.valid)
				{
					throw new Error(poolResult.errors)
				}
				
				pool.collection = this.db.collection(poolName)
				yield pool.collection.ensureIndex({ pos: '2dsphere' })
				yield pool.collection.ensureIndex({ lastUpdatedAt: 1 }, { expireAfterSeconds: this.config.matchmaking.sessionTTL * 60 })
			
				pool.partyCollection = this.db.collection(`${poolName}_parties`)
			}			
		}
		
		getRoutes()
		{
			const pool = { type: 'string' }
			const partyID = { type: 'string' }
			const sessionID = { type: 'string' }
			
			const searchSchema = {
				properties: {
					pool,
					sessionValues: { type: 'object' },
					members: {
						type: 'array',
						items: { type: [ 'number', 'string' ] },
					},
					partyID,
					forceCreate: { type: 'boolean' },
				},
				required: [
					'pool',
					'sessionValues',
					'members',
					'partyID',
				],
			}
			
			const pingSchema = {
				properties: {
					pool,
					partyID,
					sessionID,
				},
				required: [
					'pool',
					'partyID',
					'sessionID',
				],
			}
			
			return [
				[ 'search', this.search, [ this.authenticated, this.schema(searchSchema) ] ],
				[ 'ping', this.ping, [ this.authenticated, this.schema(pingSchema) ] ],
			]
		}
		
		*ping(req)
		{
			const poolName = req.params.pool
			const pool = this.poolConfig.pools[poolName]
			if (!pool)
			{
				return this.errors.poolNotFound(poolName)
			}
			
			const partyID = req.params.partyID
			const sessionID = req.params.sessionID
			
			const pingWrite = yield pool.collection.updateOne({
				_id: sessionID,
				['parties.' + partyID]: { $exists: true },
			},
			{
				$currentDate: { lastUpdatedAt: true },
			})
			
			if (pingWrite.result.n)
			{
				return { ok: true }
			}
			else
			{
				return this.errors.sessionInaccessible()
			}
		}
		
		*search(req)
		{
			const poolName = req.params.pool
			const pool = this.poolConfig.pools[poolName]
			if (!pool)
			{
				return this.errors.poolNotFound(poolName)
			}
			
			const sessionValues = {}
			for (const paramName in pool.defaults)
			{
				sessionValues[paramName] = req.params.sessionValues[paramName] || pool.defaults[paramName]
			}
			
			const members = req.params.members
			members.push(req.user.id)
			const partySize = members.length
			const partyID = req.params.partyID
			
			const freeSpaces = { $gte: partySize }
			const mongoQuery = {
				freeSpaces,
				['parties.' + partyID]: { $exists: false }, 
			}
			
			for (const rule of pool.must)
			{
				switch (rule.op)
				{
					case 'eq':
					{
						mongoQuery['sessionValues.' + rule.field] = sessionValues[rule.field]
					}
					break
				}
			}
			
			const geo = req.socket.geo
			// geoip gives lat/long, mongo wants long/lat
			const coordinates = geo ? [ geo.ll[1], geo.ll[0] ] : northPoleCoords
			const sessionPoint = { type: 'Point', coordinates }
			
			let joinedSession
			if (!req.params.forceCreate)
			{
				const geoResults = yield pool.collection.geoNear(sessionPoint, {
					spherical: true,
					num: pool.querySize,
					query: mongoQuery,
				})<commit_msg>Exclude entirely empty sessions from matchmaking searches<commit_after>'use strict'

/* TODO:
	Mapping to one 1st-party platform session ID as required
*/

const uuid = require('node-uuid')
const jsonschema = require('jsonschema')

// if we can't resolve an IP, dump it at the north pole to match with other people we can't classify
// TODO: rethink this, maybe push these into a separate sub-pool to match amongst themselves
const northPoleCoords = [ 0, 90 ]

const defaultPool = {
	querySize: 10,
	must: [],
	defaults: {},
}

const poolSchema = {
	properties: {
		maxSpaces: { type: 'int' },
	},
	required: [
		'maxSpaces',
	],
}

module.exports = function*(loader)
{
	class MatchmakingService extends loader.Service
	{
		get serviceErrors() { return [
			'poolNotFound',
			'sessionInaccessible',
		]}
		
		*init()
		{
			this.db = yield this.createMongoConnection('matchmaking')
			this.poolConfig = loader.require(this.config.matchmaking.data)
			
			const poolValidator = new jsonschema.Validator()
			for (const poolName in this.poolConfig.pools)
			{
				const pool = this.poolConfig.pools[poolName]
				for (var prop in defaultPool)
				{
					if (pool[prop] === undefined)
					{
						pool[prop] = defaultPool[prop]
					}
				}
				
				const poolResult = poolValidator.validate(pool, poolSchema)
				if (!poolResult.valid)
				{
					throw new Error(poolResult.errors)
				}
				
				pool.collection = this.db.collection(poolName)
				yield pool.collection.ensureIndex({ pos: '2dsphere' })
				yield pool.collection.ensureIndex({ lastUpdatedAt: 1 }, { expireAfterSeconds: this.config.matchmaking.sessionTTL * 60 })
			
				pool.partyCollection = this.db.collection(`${poolName}_parties`)
			}			
		}
		
		getRoutes()
		{
			const pool = { type: 'string' }
			const partyID = { type: 'string' }
			const sessionID = { type: 'string' }
			
			const searchSchema = {
				properties: {
					pool,
					sessionValues: { type: 'object' },
					members: {
						type: 'array',
						items: { type: [ 'number', 'string' ] },
					},
					partyID,
					forceCreate: { type: 'boolean' },
				},
				required: [
					'pool',
					'sessionValues',
					'members',
					'partyID',
				],
			}
			
			const pingSchema = {
				properties: {
					pool,
					partyID,
					sessionID,
				},
				required: [
					'pool',
					'partyID',
					'sessionID',
				],
			}
			
			return [
				[ 'search', this.search, [ this.authenticated, this.schema(searchSchema) ] ],
				[ 'ping', this.ping, [ this.authenticated, this.schema(pingSchema) ] ],
			]
		}
		
		*ping(req)
		{
			const poolName = req.params.pool
			const pool = this.poolConfig.pools[poolName]
			if (!pool)
			{
				return this.errors.poolNotFound(poolName)
			}
			
			const partyID = req.params.partyID
			const sessionID = req.params.sessionID
			
			const pingWrite = yield pool.collection.updateOne({
				_id: sessionID,
				['parties.' + partyID]: { $exists: true },
			},
			{
				$currentDate: { lastUpdatedAt: true },
			})
			
			if (pingWrite.result.n)
			{
				return { ok: true }
			}
			else
			{
				return this.errors.sessionInaccessible()
			}
		}
		
		*search(req)
		{
			const poolName = req.params.pool
			const pool = this.poolConfig.pools[poolName]
			if (!pool)
			{
				return this.errors.poolNotFound(poolName)
			}
			
			const sessionValues = {}
			for (const paramName in pool.defaults)
			{
				sessionValues[paramName] = req.params.sessionValues[paramName] || pool.defaults[paramName]
			}
			
			const members = req.params.members
			members.push(req.user.id)
			const partySize = members.length
			const partyID = req.params.partyID
			
			const freeSpaces = { $gte: partySize, $lt: pool.maxSpaces }
			const mongoQuery = {
				freeSpaces,
				['parties.' + partyID]: { $exists: false }, 
			}
			
			for (const rule of pool.must)
			{
				switch (rule.op)
				{
					case 'eq':
					{
						mongoQuery['sessionValues.' + rule.field] = sessionValues[rule.field]
					}
					break
				}
			}
			
			const geo = req.socket.geo
			// geoip gives lat/long, mongo wants long/lat
			const coordinates = geo ? [ geo.ll[1], geo.ll[0] ] : northPoleCoords
			const sessionPoint = { type: 'Point', coordinates }
			
			let joinedSession
			if (!req.params.forceCreate)
			{
				const geoResults = yield pool.collection.geoNear(sessionPoint, {
					spherical: true,
					num: pool.querySize,
					query: mongoQuery,
				})
{'fraction_non_alphanumeric': 0.12782041003321498, 'fraction_numerical': 0.002748826022219677, 'mean_word_length': 2.6413678065054214, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1967885', 'n_tokens_mistral': 3755, 'n_tokens_neox': 2829, 'n_words': 638}