From e35492840ea863e4a6948a35177ce16a8ebcaf54 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 11:16:21 +0200 Subject: [PATCH] feat: more work on summoner/basic endpoint --- .../Controllers/Http/SummonersController.ts | 15 +- .../app/Repositories/MatchRepository.ts | 43 ++++++ .../Jax/src/Endpoints/MatchListEndpoint.ts | 21 ++- server-new/app/Services/MatchService.ts | 136 ++++++++++++++++++ server-new/app/Services/SummonerService.ts | 7 +- server-new/app/helpers.ts | 50 +++---- server-new/contracts/league.ts | 11 +- server-new/start/routes.ts | 7 + 8 files changed, 252 insertions(+), 38 deletions(-) create mode 100644 server-new/app/Repositories/MatchRepository.ts create mode 100644 server-new/app/Services/MatchService.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index b07aa89..5a28151 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -1,16 +1,20 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import { SummonerModel } from '@ioc:Adonis/League' import mongodb from '@ioc:Mongodb/Database' +import MatchRepository from 'App/Repositories/MatchRepository' import Jax from 'App/Services/Jax' +import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' export default class SummonersController { - // private async getSeasons (puuid) { - // let seasons = await MatchRepository.seasons(puuid) - // return seasons.length ? seasons.map(s => s._id) : [10] - // } + private async getSeasons (puuid: string) { + let seasons = await MatchRepository.seasons(puuid) + return seasons.length ? seasons.map(s => s._id) : [10] + } /** * POST: get basic summoner data + * @param ctx */ public async basic ({ request, response }: HttpContextContract) { console.time('all') @@ -41,9 +45,10 @@ export default class SummonersController { // ) const summonersCollection = await mongodb.connection().collection('summoners') - const summonerDB = await summonersCollection.findOne({ puuid: account.puuid }) + let summonerDB:SummonerModel|null = await summonersCollection.findOne({ puuid: account.puuid }) if(!summonerDB) { await summonersCollection.insertOne({ puuid: account.puuid }) + summonerDB = {puuid: account.puuid } } // Summoner names diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts new file mode 100644 index 0000000..fc6c4cc --- /dev/null +++ b/server-new/app/Repositories/MatchRepository.ts @@ -0,0 +1,43 @@ +import mongodb from '@ioc:Mongodb/Database' + +class MatchRepository { + private season?: number + + constructor () { + // TODO: keep matches collection in the repo instance + } + + /** + * Basic matchParams used in a lot of requests + * @param puuid of the summoner + */ + private _matchParams (puuid: string) { + return { + summoner_puuid: puuid, + result: { $not: { $eq: 'Remake' } }, + gamemode: { $nin: [800, 810, 820, 830, 840, 850] }, + season: this.season ? this.season : { $exists: true }, + } + } + /** + * Get Summoner's played seasons + * @param puuid of the summoner + */ + public async seasons (puuid: string) { + this.season = undefined + const matchesCollections = await mongodb.connection().collection('matches') + + return matchesCollections.aggregate([ + { + $match: { + ...this._matchParams(puuid), + }, + }, + { + $group: { _id: '$season' }, + }, + ]).toArray() + } +} + +export default new MatchRepository() diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts index 95f2f84..9cc978c 100644 --- a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts @@ -2,6 +2,25 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' import { JaxConfig } from '../../JaxConfig' import JaxRequest from '../JaxRequest' +export interface MatchlistDto { + startIndex: number, + totalGames: number, + endIndex: number, + matches: MatchReferenceDto[] +} + +export interface MatchReferenceDto { + gameId: number, + role: string, + season: number, + platformId: string, + champion: number, + queue: number, + lane: string, + timestamp: number, + seasonMatch?: number +} + export default class MatchlistEndpoint { private config: JaxConfig private limiter: RiotRateLimiter @@ -11,7 +30,7 @@ export default class MatchlistEndpoint { this.limiter = limiter } - public accountID (accountID: number, region: string, beginIndex = 0) { + public accountID (accountID: string, region: string, beginIndex = 0): Promise { return new JaxRequest( region, this.config, diff --git a/server-new/app/Services/MatchService.ts b/server-new/app/Services/MatchService.ts new file mode 100644 index 0000000..5cc580e --- /dev/null +++ b/server-new/app/Services/MatchService.ts @@ -0,0 +1,136 @@ +import Jax from './Jax' +// import Logger from '@ioc:Adonis/Core/Logger' +import { getSeasonNumber } from 'App/helpers' +import { MatchReferenceDto } from './Jax/src/Endpoints/MatchListEndpoint' +import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint' +import { SummonerModel } from '@ioc:Adonis/League' + +class MatchService { + /** + * Add 100 matches at a time to MatchList until the stopFetching condition is true + * @param account of the summoner + * @param stopFetching condition to stop fetching the MatchList + */ + private async _fetchMatchListUntil (account: SummonerDTO, stopFetching: any) { + let matchList: MatchReferenceDto[] = [] + let alreadyIn = false + let index = 0 + do { + let { matches: newMatchList } = await Jax.Matchlist.accountID(account.accountId, account.region as string, index) + // Error while fetching Riot API + if (!newMatchList) { + matchList = matchList.map(m => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + return matchList + } + matchList = [...matchList, ...newMatchList] + alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList) + // If the match is made in another region : we stop fetching + if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) { + alreadyIn = true + } + index += 100 + } while (!alreadyIn) + + // Remove matches from MatchList made in another region and tutorial games + const tutorialModes = [2000, 2010, 2020] + matchList = matchList + .filter(m => { + const sameRegion = m.platformId.toLowerCase() === account.region + const notATutorialGame = !tutorialModes.includes(m.queue) + + return sameRegion && notATutorialGame + }) + .map(m => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + + return matchList + } + /** + * Update the full MatchList of the summoner (min. 4 months) + * @param account of the summoner + * @param summonerDB summoner in the database + */ + public async updateMatchList (account: SummonerDTO, summonerDB: SummonerModel) { + console.time('matchList') + + // Summoner has already been searched : we already have a MatchList and we need to update it + if (summonerDB.matchList) { + // Get MatchList + const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => { + return summonerDB.matchList!.some(m => m.gameId === newMatchList[newMatchList.length - 1].gameId) + }) + // Update Summoner's MatchList + for (const match of matchList.reverse()) { + if (!summonerDB.matchList.some(m => m.gameId === match.gameId)) { + summonerDB.matchList.unshift(match) + } + } + } else { // First search of the Summoner + const today = Date.now() + // Get MatchList + const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => { + return (newMatchList.length !== 100 || today - newMatchList[newMatchList.length - 1].timestamp > 10368000000) + }) + // Create Summoner's MatchList in Database + summonerDB.matchList = matchList + } + console.timeEnd('matchList') + } + + /** + * Fetch list of matches for a specific Summoner + * @param account of the summoner + * @param gameIds of the matches to fetch + * @param summonerDB summoner in the database + */ + // public async getMatches (account, gameIds, summonerDB) { + // console.time('getMatches') + + // let matchesDetails = [] + // const matchesToGetFromRiot = [] + // for (let i = 0; i < gameIds.length; ++i) { + // const matchSaved = await summonerDB.matches().where({ gameId: gameIds[i] }).first() + // if (matchSaved) { + // matchesDetails.push(matchSaved) + // } else { + // matchesToGetFromRiot.push(gameIds[i]) + // } + // } + + // const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, account.region)) + // let matchesFromApi = await Promise.all(requests) + + // /* If we have to store some matches in the db */ + // if (matchesFromApi.length !== 0) { + // // Try to see why matches are sometimes undefined + // matchesFromApi.filter(m => { + // if (m === undefined) { + // Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m) + // } + // }) + + // // Transform raw matches data + // await BasicMatchTransformer.transform(matchesFromApi, { account }) + + // /* Save all matches from Riot Api in db */ + // for (const match of matchesFromApi) { + // await summonerDB.matches().create(match) + // match.newMatch = true + // } + // matchesDetails = [...matchesDetails, ...matchesFromApi] + // } + + // /* Sort matches */ + // matchesDetails.sort((a, b) => (a.date < b.date) ? 1 : -1) + // console.timeEnd('getMatches') + + // return matchesDetails + // } +} + +export default new MatchService() diff --git a/server-new/app/Services/SummonerService.ts b/server-new/app/Services/SummonerService.ts index b432f60..0798e2b 100644 --- a/server-new/app/Services/SummonerService.ts +++ b/server-new/app/Services/SummonerService.ts @@ -1,8 +1,7 @@ -'use strict' - import Jax from './Jax' import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint' +import { SummonerModel } from '@ioc:Adonis/League' class SummonerService { private uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER'] @@ -46,10 +45,10 @@ class SummonerService { * @param account of the summoner * @param summonerDB summoner in the database */ - public getAllSummonerNames (account: SummonerDTO, summonerDB:any) { + public getAllSummonerNames (account: SummonerDTO, summonerDB:SummonerModel) { const names = summonerDB.names ? summonerDB.names : [] - if (!names.find((n: any) => n.name === account.name)) { + if (!names.find(n => n.name === account.name)) { names.push({ name: account.name, date: new Date(), diff --git a/server-new/app/helpers.ts b/server-new/app/helpers.ts index 00cf0bf..8246741 100644 --- a/server-new/app/helpers.ts +++ b/server-new/app/helpers.ts @@ -1,7 +1,7 @@ /** * League of Legends queues with defined role for each summoner */ -const queuesWithRole = [ +export const queuesWithRole = [ 0, // Custom 400, // Draft 420, // Solo/Duo @@ -13,7 +13,7 @@ const queuesWithRole = [ /** * League of Legends seasons timestamps */ -const seasons = { +export const seasons = { 0: 9, 1578628800000: 10, } @@ -21,30 +21,26 @@ const seasons = { /** * League of Legends all support item ids */ -const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864] +export const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864] -module.exports = { - queuesWithRole, - seasons, - supportItems, - /** - * Get season number for a match - */ - getSeasonNumber (timestamp: number) { - const arrSeasons = Object.keys(seasons).map(k => Number(k)) - arrSeasons.push(timestamp) - arrSeasons.sort() - const indexSeason = arrSeasons.indexOf(timestamp) - 1 - return seasons[arrSeasons[indexSeason]] - }, - /** - * - * Sort array of Roles according to a specific order - * @param a first role - * @param b second role - */ - sortTeamByRole (a:any, b:any) { - const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] - return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) - }, +/** + * Get season number for a match + * @param timestamp + */ +export function getSeasonNumber (timestamp: number) { + const arrSeasons = Object.keys(seasons).map(k => Number(k)) + arrSeasons.push(timestamp) + arrSeasons.sort() + const indexSeason = arrSeasons.indexOf(timestamp) - 1 + return seasons[arrSeasons[indexSeason]] +} + +/** + * Sort array of Roles according to a specific order + * @param a first role + * @param b second role + */ +export function sortTeamByRole (a:any, b:any) { + const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] + return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) } diff --git a/server-new/contracts/league.ts b/server-new/contracts/league.ts index 9b77485..8c4b36c 100644 --- a/server-new/contracts/league.ts +++ b/server-new/contracts/league.ts @@ -1,5 +1,14 @@ declare module '@ioc:Adonis/League' { - interface Account { + import { MatchReferenceDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint' + interface SummonerModel { + puuid: string, + matchList?: MatchReferenceDto[], + names?: SummonerNames[] + } + + interface SummonerNames { + name: string, + date: Date } } diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts index 3364f8c..f1ed35f 100644 --- a/server-new/start/routes.ts +++ b/server-new/start/routes.ts @@ -36,6 +36,13 @@ Route.get('jax', async () => { return { player: summoner } }) +Route.get('test', async () => { + const summonersCollection = await mongodb.connection().collection('summoners') + const summonerDB = await summonersCollection.findOne({ puuid: 1234 }) + + return { player: summonerDB } +}) + Route.post('/summoner/basic', 'SummonersController.basic') Route.post('/summoner/overview', 'SummonersController.overview') Route.post('/summoner/champions', 'SummonersController.champions')