diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index 6ceccbd..12b8694 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -5,6 +5,7 @@ import Jax from 'App/Services/Jax' import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator' +import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator' export default class SummonersController { /** @@ -22,8 +23,8 @@ export default class SummonersController { */ public async basic ({ request, response }: HttpContextContract) { console.time('all') - const { summoner, region} = await request.validate(SummonerBasicValidator) - const finalJSON:any = {} + const { summoner, region } = await request.validate(SummonerBasicValidator) + const finalJSON: any = {} try { const account = await SummonerService.getAccount(summoner, region) @@ -36,7 +37,7 @@ export default class SummonersController { // Summoner in DB let summonerDB = await Summoner.findOne({ puuid: account.puuid }) - if(!summonerDB) { + if (!summonerDB) { summonerDB = await Summoner.create({ puuid: account.puuid }) } @@ -69,4 +70,16 @@ export default class SummonersController { console.timeEnd('all') return response.json(finalJSON) } + + /** + * POST: get champions view summoner data + * @param ctx + */ + public async champions ({ request, response }) { + console.time('championsRequest') + const { puuid, queue, season } = await request.validate(SummonerChampionValidator) + const championStats = await MatchRepository.championCompleteStats(puuid, queue, season) + console.timeEnd('championsRequest') + return response.json(championStats) + } } diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts index fdb2913..33d539d 100644 --- a/server-new/app/Repositories/MatchRepository.ts +++ b/server-new/app/Repositories/MatchRepository.ts @@ -2,39 +2,114 @@ import mongodb from '@ioc:Mongodb/Database' import { Collection } from 'mongodb' class MatchRepository { - private season?: number private collection: Collection constructor () { this.getCollection() } - /** - * Get MongoDB matches collection - */ - private async getCollection () { - this.collection = await mongodb.connection().collection('matches') - } - /** * Basic matchParams used in a lot of requests * @param puuid of the summoner */ - private matchParams (puuid: string) { + private matchParams (puuid: string, season?: number) { return { summoner_puuid: puuid, result: { $not: { $eq: 'Remake' } }, gamemode: { $nin: [800, 810, 820, 830, 840, 850] }, - season: this.season ? this.season : { $exists: true }, + season: season ? season : { $exists: true }, } } + /** + * Build the aggregate mongo query + * @param puuid + * @param matchParams + * @param intermediateSteps + * @param groupId + * @param groupParams + * @param finalSteps + */ + private async aggregate ( + puuid: string, + matchParams: object, + intermediateSteps: any[], + groupId: any, + groupParams: object, + finalSteps: any[], + season?: number, + ) { + return this.collection.aggregate([ + { + $match: { + ...this.matchParams(puuid, season), + ...matchParams, + }, + }, + ...intermediateSteps, + { + $group: { + _id: groupId, + count: { $sum: 1 }, + wins: { + $sum: { + $cond: [{ $eq: ['$result', 'Win'] }, 1, 0], + }, + }, + losses: { + $sum: { + $cond: [{ $eq: ['$result', 'Fail'] }, 1, 0], + }, + }, + ...groupParams, + }, + }, + ...finalSteps, + ]).toArray() + } + + /** + * Get MongoDB matches collection + */ + public async getCollection () { + if (!this.collection) { + this.collection = await mongodb.connection().collection('matches') + } + } + + /** + * Get Summoner's complete statistics for the all played champs + * @param puuid of the summoner + * @param queue of the matches to fetch, if not set: get all matches + * @param season of the matches to fetch, if not set: get all seasons + */ + public async championCompleteStats (puuid: string, queue?: number, season?: number) { + const matchParams = queue ? { gamemode: { $eq: Number(queue) } } : {} + const groupParams = { + time: { $sum: '$time' }, + gameLength: { $avg: '$time' }, + date: { $max: '$date' }, + champion: { $first: '$champion' }, + kills: { $sum: '$stats.kills' }, + deaths: { $sum: '$stats.deaths' }, + assists: { $sum: '$stats.assists' }, + minions: { $avg: '$stats.minions' }, + gold: { $avg: '$stats.gold' }, + dmgChamp: { $avg: '$stats.dmgChamp' }, + dmgTaken: { $avg: '$stats.dmgTaken' }, + kp: { $avg: '$stats.kp' }, + } + const finalSteps = [ + { $sort: { 'count': -1, 'champion.name': 1 } }, + ] + return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season) + } + /** * Get Summoner's played seasons * @param puuid of the summoner */ public async seasons (puuid: string) { - this.season = undefined return this.collection.aggregate([ { $match: { diff --git a/server-new/app/Validators/SummonerChampionValidator.ts b/server-new/app/Validators/SummonerChampionValidator.ts new file mode 100644 index 0000000..31c1ed9 --- /dev/null +++ b/server-new/app/Validators/SummonerChampionValidator.ts @@ -0,0 +1,54 @@ +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import { schema } from '@ioc:Adonis/Core/Validator' + +export default class SummonerChampionValidator { + constructor (private ctx: HttpContextContract) { + } + + /** + * Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data. + * + * For example: + * 1. The username must be of data type string. But then also, it should + * not contain special characters or numbers. + * ``` + * schema.string({}, [ rules.alpha() ]) + * ``` + * + * 2. The email must be of data type string, formatted as a valid + * email. But also, not used by any other user. + * ``` + * schema.string({}, [ + * rules.email(), + * rules.unique({ table: 'users', column: 'email' }), + * ]) + * ``` + */ + public schema = schema.create({ + puuid: schema.string(), + queue: schema.number.optional(), + season: schema.number.optional(), + }) + + /** + * The `schema` first gets compiled to a reusable function and then that compiled + * function validates the data at runtime. + * + * Since, compiling the schema is an expensive operation, you must always cache it by + * defining a unique cache key. The simplest way is to use the current request route + * key, which is a combination of the route pattern and HTTP method. + */ + public cacheKey = this.ctx.routeKey + + /** + * Custom messages for validation failures. You can make use of dot notation `(.)` + * for targeting nested fields and array expressions `(*)` for targeting all + * children of an array. For example: + * + * { + * 'profile.username.required': 'Username is required', + * 'scores.*.number': 'Define scores as valid numbers' + * } + */ + public messages = {} +} diff --git a/server-new/providers/AppProvider.ts b/server-new/providers/AppProvider.ts index 624be77..8408dbe 100644 --- a/server-new/providers/AppProvider.ts +++ b/server-new/providers/AppProvider.ts @@ -8,8 +8,11 @@ export default class AppProvider { // Register your own bindings } - public boot () { + public async boot () { // IoC container is ready + + // Load Match Collections + await import('App/Repositories/MatchRepository') } public shutdown () {