From 4f13d8400e0e24940c46f84a8b3f16ca955fb0d0 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 29 Sep 2019 18:06:54 +0200 Subject: [PATCH] refactor: transform matches data to store in db only useful data --- client/src/helpers/summoner.js | 140 ++---------------- client/src/store/modules/summoner.js | 2 +- client/src/views/Summoner.vue | 9 +- .../Controllers/Http/SummonerController.js | 67 +++++++-- server/app/Helpers/Match.js | 25 ++++ server/app/Transformers/MatchTransformer.js | 138 +++++++++++++++++ server/config/bumblebee.js | 28 ++++ .../migrations/1567863677607_match_schema.js | 1 + server/package-lock.json | 5 + server/package.json | 1 + server/start/app.js | 4 +- 11 files changed, 270 insertions(+), 150 deletions(-) create mode 100644 server/app/Helpers/Match.js create mode 100644 server/app/Transformers/MatchTransformer.js create mode 100644 server/config/bumblebee.js diff --git a/client/src/helpers/summoner.js b/client/src/helpers/summoner.js index cb98367..097d845 100644 --- a/client/src/helpers/summoner.js +++ b/client/src/helpers/summoner.js @@ -1,4 +1,4 @@ -import { timeDifference, secToTime, getRankImg } from '@/helpers/functions.js' +import { timeDifference, getRankImg } from '@/helpers/functions.js' import { maps, gameModes } from '@/data/data.js' import summonersJSON from '@/data/summoner.json' @@ -7,7 +7,7 @@ import summonersJSON from '@/data/summoner.json' * @param {Object} RiotData : all data from the Riot API * @param {Object} championsInfos : champions data from the Riot API */ -export function createSummonerData(RiotData, championsInfos, runesInfos) { +export function createSummonerData(RiotData) { console.log('--- ALL INFOS ---') console.log(RiotData) @@ -25,129 +25,25 @@ export function createSummonerData(RiotData, championsInfos, runesInfos) { soloQ.lp = soloQStats.leaguePoints } - const matchesInfos = [] - // Loop on all matches - for (let i = 0; i < matches.length; i++) { - const currentMatch = matches[i] - const participantId = currentMatch.participantIdentities.find((p) => p.player.currentAccountId === userStats.accountId).participantId - const player = currentMatch.participants[participantId - 1] - const teamId = player.teamId - - let win = currentMatch.teams.find((t) => t.teamId === teamId).win - let status = win === 'Win' ? 'Victory' : 'Defeat' - - // Match less than 5min - if (currentMatch.gameDuration < 300) { - win = 'Remake' - status = 'Remake' + for (const match of matches) { + for (let i = 0; i < match.items.length; i++) { + match.items[i] = getItemLink(match.items[i]) } - const map = maps[currentMatch.mapId] - let mode = gameModes[currentMatch.queueId] - if (!mode) - mode = 'Undefined gamemode' - const champion = (({ id, name }) => ({ id, name }))(Object.entries(championsInfos).find(([, champion]) => Number(champion.key) === player.championId)[1]) - const role = getRoleName(player.timeline) + match.firstSum = getSummonerLink(match.firstSum) + match.secondSum = getSummonerLink(match.secondSum) - const timeAgo = timeDifference(currentMatch.gameCreation) - const time = secToTime(currentMatch.gameDuration) - const kills = player.stats.kills - const deaths = player.stats.deaths - const assists = player.stats.assists - let kda - if (kills + assists !== 0 && deaths === 0) { - kda = '∞' - } else { - kda = +(deaths === 0 ? 0 : ((kills + assists) / deaths)).toFixed(2) - } - const level = player.stats.champLevel - const damage = +(player.stats.totalDamageDealtToChampions / 1000).toFixed(1) + 'k' + match.date = timeDifference(match.date) - const primaryRuneCategory = runesInfos.find(r => r.id === player.stats.perkPrimaryStyle) - let primaryRune - for (const subCat of primaryRuneCategory.slots) { - primaryRune = subCat.runes.find(r => r.id === player.stats.perk0) - if (primaryRune) { - break - } - } - primaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${primaryRune.icon}` - let secondaryRune = runesInfos.find(r => r.id === player.stats.perkSubStyle) - secondaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${secondaryRune.icon}` - - const totalKills = currentMatch.participants.reduce((prev, current) => { - if (current.teamId !== teamId) { - return prev - } - return prev + current.stats.kills - }, 0) - const kp = +((kills + assists) * 100 / totalKills).toFixed(1) + '%' - - const items = [] - for (let i = 0; i < 6; i++) { - const currentItem = 'item' + i - items.push(getItemLink(player.stats[currentItem])) - } - - const gold = +(player.stats.goldEarned / 1000).toFixed(1) + 'k' - const minions = player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled - - const firstSum = player.spell1Id - const secondSum = player.spell2Id - - const allyTeam = [] - const enemyTeam = [] - for (let summoner of currentMatch.participantIdentities) { - const allData = currentMatch.participants[summoner.participantId - 1] - const playerInfos = { - name: summoner.player.summonerName, - role: getRoleName(allData.timeline), - champion: (({ id, name }) => ({ id, name }))(Object.entries(championsInfos).find(([, champion]) => Number(champion.key) === allData.championId)[1]) - } - - if (allData.teamId === teamId) { - allyTeam.push(playerInfos) - } else { - enemyTeam.push(playerInfos) - } - } - allyTeam.sort(sortTeamByRole) - enemyTeam.sort(sortTeamByRole) - - matchesInfos.push({ - result: win, - status, - map, - gamemode: mode, - champion, - role, - primaryRune, - secondaryRune, - date: timeAgo, - time, - kills, - deaths, - assists, - kda, - level, - damage, - kp, - items, - gold, - minions, - firstSum: getSummonerLink(firstSum), - secondSum: getSummonerLink(secondSum), - allyTeam, - enemyTeam - }) + match.map = maps[match.map] + match.gamemode = gameModes[match.gamemode] + if (!match.gamemode) match.gamemode = 'Undefined gamemode' } // end loop matches - console.log('matches infos just below') - console.log(matchesInfos) return { accountId: userStats.accountId, allMatches: RiotData.allMatches, - matches: matchesInfos, + matches: RiotData.matchesDetails, profileIconId: userStats.profileIconId, name: userStats.name, level: userStats.summonerLevel, @@ -162,19 +58,7 @@ function getItemLink(id) { return `url('https://ddragon.leagueoflegends.com/cdn/${process.env.VUE_APP_PATCH}/img/item/${id}.png')` } -function getRoleName(timeline) { - if (timeline.lane === 'BOTTOM' && timeline.role.includes('SUPPORT')) { - return 'SUPPORT' - } - return timeline.lane -} - function getSummonerLink(id) { const spellName = Object.entries(summonersJSON.data).find(([, spell]) => Number(spell.key) === id)[0] return `https://ddragon.leagueoflegends.com/cdn/${process.env.VUE_APP_PATCH}/img/spell/${spellName}.png` } - -function sortTeamByRole(a, b) { - const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] - return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) -} diff --git a/client/src/store/modules/summoner.js b/client/src/store/modules/summoner.js index f378061..25fc661 100644 --- a/client/src/store/modules/summoner.js +++ b/client/src/store/modules/summoner.js @@ -28,7 +28,7 @@ export const actions = { try { const resp = await axios(({ url: 'api', data: { summoner, region }, method: 'POST' })) if (resp.data) { - const infos = createSummonerData(resp.data, rootState.ddragon.champions, rootState.ddragon.runes) + const infos = createSummonerData(resp.data) commit('SUMMONER_FOUND', infos) } else { commit('SUMMONER_NOT_FOUND') diff --git a/client/src/views/Summoner.vue b/client/src/views/Summoner.vue index db0a6ce..4673a19 100644 --- a/client/src/views/Summoner.vue +++ b/client/src/views/Summoner.vue @@ -132,7 +132,6 @@ export default { ...mapState({ summonerInfos: state => state.summoner.infos }), - ...mapGetters('ddragon', ['areChampionsLoaded']), ...mapGetters('summoner', ['summonerFound', 'summonerNotFound', 'summonerLoading']) }, @@ -148,18 +147,12 @@ export default { }, methods: { - async apiCall() { - if (!this.areChampionsLoaded) - await this.getChampions() - - await this.getRunes() - + apiCall() { this.summonerRequest({ summoner: this.summoner, region: this.region }) }, redirect(summoner, region) { this.$router.push(`/summoner/${region}/${summoner}`) }, - ...mapActions('ddragon', ['getChampions', 'getRunes']), ...mapActions('summoner', ['summonerRequest']) } } diff --git a/server/app/Controllers/Http/SummonerController.js b/server/app/Controllers/Http/SummonerController.js index 93dc61a..facc907 100644 --- a/server/app/Controllers/Http/SummonerController.js +++ b/server/app/Controllers/Http/SummonerController.js @@ -2,26 +2,51 @@ const Match = use('App/Models/Match') const Jax = use('Jax') +const MatchTransformer = use('App/Transformers/MatchTransformer') class SummonerController { /** * POST Endpoint : get summoner data */ - async api({ request, response }) { + async api({ request, response, transform }) { const summoner = request.input('summoner') const region = request.input('region') - const data = await this.getSummonerData(summoner, region) + const data = await this.getSummonerData(summoner, region, transform) response.json(data) } + /** + * Get the matchlist of all matches made less than 4 months ago by the Summoner + * @param today + * @param accountId + * @param beginIndex + * @param allMatches + */ + async getMatchList(today, accountId, beginIndex, allMatches) { + const { matches } = await Jax.Matchlist.accountID(accountId, beginIndex) + + allMatches = [...allMatches, ...matches] + + const lastMatch = matches[matches.length - 1].timestamp + const diff = today - lastMatch + console.log(diff) + // More matches to get from Riot API if they are younger than 4 months + if (matches.length === 100 && diff < 10368000000) { + return this.getMatchList(today, accountId, (beginIndex + 100), allMatches) + } else { + console.log('return all matches bro') + return allMatches + } + } + /** * Get all the data about the searched Summoner * @param summoner * @param region */ - async getSummonerData(summoner, region) { + async getSummonerData(summoner, region, transform) { console.time('all') console.log(summoner, region) @@ -47,13 +72,15 @@ class SummonerController { finalJSON.soloQ = soloQ.length ? soloQ[0] : null; console.time('getMatches') - const { matches } = await Jax.Matchlist.accountID(account.accountId) + const today = Date.now() + const matches = await this.getMatchList(today, account.accountId, 0, []) const gameIds = matches.slice(0, 10).map(({ gameId }) => gameId) let matchesDetails = [] const matchesToGetFromRiot = [] for (let i = 0; i < gameIds.length; ++i) { - const matchSaved = await Match.where({ gameId: gameIds[i] }).first() + // const matchSaved = await Match.where({ gameId: gameIds[i] }).first() + const matchSaved = await Match.where({ gameId: gameIds[i], puuid: account.puuid }).first() if (matchSaved) { console.log('match in mongodb') matchesDetails.push(matchSaved) @@ -64,17 +91,33 @@ class SummonerController { } const requests = matchesToGetFromRiot.map(Jax.Match.get) - const matchesFromApi = await Promise.all(requests) - matchesDetails = [...matchesDetails, ...matchesFromApi] + let matchesFromApi = await Promise.all(requests) - /* Save all matches in db */ - for (const match of matchesFromApi) { - await Match.create(match) - console.log('match saved') + /* If we have to same some matches in the db */ + if (matchesFromApi.length !== 0) { + const champions = await Jax.DDragon.Champion.list() + const runes = await Jax.DDragon.Rune.list() + const ctx = { + account, + champions: champions.data, + runes + } + matchesFromApi = await transform.collection(matchesFromApi) + .transformWith(MatchTransformer) + .withContext(ctx) + .toJSON() + + matchesDetails = [...matchesDetails, ...matchesFromApi] + + /* Save all matches in db */ + for (const match of matchesFromApi) { + await Match.create(match) + console.log('match saved') + } } /* Sort 10 matches */ - matchesDetails.sort((a, b) => (a.gameCreation < b.gameCreation) ? 1 : -1) + matchesDetails.sort((a, b) => (a.gameCreation < b.gameCreation) ? -1 : 1) finalJSON.matchesDetails = matchesDetails finalJSON.allMatches = matches diff --git a/server/app/Helpers/Match.js b/server/app/Helpers/Match.js new file mode 100644 index 0000000..c3b486c --- /dev/null +++ b/server/app/Helpers/Match.js @@ -0,0 +1,25 @@ +class MatchHelper { + getRoleName(timeline) { + if (timeline.lane === 'BOTTOM' && timeline.role.includes('SUPPORT')) { + return 'SUPPORT' + } + return timeline.lane + } + + /** + * Return time in a formatted way + * @param sec : time in seconds to convert + */ + secToTime(sec) { + const min = Math.floor(sec / 60) + const newSec = sec - min * 60 + return min + 'm' + (newSec < 10 ? '0' + newSec : newSec) + 's' + } + + sortTeamByRole(a, b) { + const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] + return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) + } +} + +module.exports = new MatchHelper() \ No newline at end of file diff --git a/server/app/Transformers/MatchTransformer.js b/server/app/Transformers/MatchTransformer.js new file mode 100644 index 0000000..799a87d --- /dev/null +++ b/server/app/Transformers/MatchTransformer.js @@ -0,0 +1,138 @@ +'use strict' + +const BumblebeeTransformer = use('Bumblebee/Transformer') +const MatchHelper = use('App/Helpers/Match') +const Logger = use('Logger') + +/** + * MatchTransformer class + * + * @class MatchTransformer + * @constructor + */ +class MatchTransformer extends BumblebeeTransformer { + /** + * This method is used to transform the data. + */ + transform(match, { account, champions, runes }) { + + // console.log(champions) + // Logger.transport('file').info(champions) + + const participantId = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId).participantId + const player = match.participants[participantId - 1] + const teamId = player.teamId + + let win = match.teams.find((t) => t.teamId === teamId).win + let status = win === 'Win' ? 'Victory' : 'Defeat' + + // Match less than 5min + if (match.gameDuration < 300) { + win = 'Remake' + status = 'Remake' + } + + const map = match.mapId + const mode = match.queueId + + const champion = (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === player.championId)[1]) + const role = MatchHelper.getRoleName(player.timeline) + + const gameCreation = match.gameCreation + const time = MatchHelper.secToTime(match.gameDuration) + + const kills = player.stats.kills + const deaths = player.stats.deaths + const assists = player.stats.assists + let kda + if (kills + assists !== 0 && deaths === 0) { + kda = '∞' + } else { + kda = +(deaths === 0 ? 0 : ((kills + assists) / deaths)).toFixed(2) + } + const level = player.stats.champLevel + const damage = +(player.stats.totalDamageDealtToChampions / 1000).toFixed(1) + 'k' + + const totalKills = match.participants.reduce((prev, current) => { + if (current.teamId !== teamId) { + return prev + } + return prev + current.stats.kills + }, 0) + const kp = +((kills + assists) * 100 / totalKills).toFixed(1) + '%' + + const primaryRuneCategory = runes.find(r => r.id === player.stats.perkPrimaryStyle) + let primaryRune + for (const subCat of primaryRuneCategory.slots) { + primaryRune = subCat.runes.find(r => r.id === player.stats.perk0) + if (primaryRune) { + break + } + } + primaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${primaryRune.icon}` + let secondaryRune = runes.find(r => r.id === player.stats.perkSubStyle) + secondaryRune = `https://ddragon.leagueoflegends.com/cdn/img/${secondaryRune.icon}` + + const items = [] + for (let i = 0; i < 6; i++) { + const currentItem = 'item' + i + items.push(player.stats[currentItem]) + } + + const gold = +(player.stats.goldEarned / 1000).toFixed(1) + 'k' + const minions = player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled + + const firstSum = player.spell1Id + const secondSum = player.spell2Id + + const allyTeam = [] + const enemyTeam = [] + for (let summoner of match.participantIdentities) { + const allData = match.participants[summoner.participantId - 1] + const playerInfos = { + name: summoner.player.summonerName, + role: MatchHelper.getRoleName(allData.timeline), + champion: (({ id, name }) => ({ id, name }))(Object.entries(champions).find(([, champion]) => Number(champion.key) === allData.championId)[1]) + } + + if (allData.teamId === teamId) { + allyTeam.push(playerInfos) + } else { + enemyTeam.push(playerInfos) + } + } + allyTeam.sort(MatchHelper.sortTeamByRole) + enemyTeam.sort(MatchHelper.sortTeamByRole) + + return { + puuid: account.puuid, + gameId: match.gameId, + result: win, + status, + map, + gamemode: mode, + champion, + role, + primaryRune, + secondaryRune, + date: gameCreation, + time, + kills, + deaths, + assists, + kda, + level, + damage, + kp, + items, + gold, + minions, + firstSum, + secondSum, + allyTeam, + enemyTeam + } + } +} + +module.exports = MatchTransformer diff --git a/server/config/bumblebee.js b/server/config/bumblebee.js new file mode 100644 index 0000000..c57c4ed --- /dev/null +++ b/server/config/bumblebee.js @@ -0,0 +1,28 @@ +'use strict' + +module.exports = { + /* + * When enabled, Bulblebee will automatically parse the ?include= + * parameter and include all requested resources + */ + parseRequest: false, + + /* + * Nested includes will be resolved up to this limit any further nested + * resources are going to be ignored + */ + includeRecursionLimit: 10, + + /* + * The serializer will be used to transform the data into its final + * representation. + * Currently supported: 'plain', 'data' + */ + serializer: 'plain', + + /* + * When a transformer is reffered to by its name only, Bumblebee will try to + * resolve the transformer using this namespace as prefix. + */ + namespace: 'App/Transformers' +} diff --git a/server/database/migrations/1567863677607_match_schema.js b/server/database/migrations/1567863677607_match_schema.js index 199dfe9..e339d5f 100644 --- a/server/database/migrations/1567863677607_match_schema.js +++ b/server/database/migrations/1567863677607_match_schema.js @@ -7,6 +7,7 @@ class MatchSchema extends Schema { up () { this.create('matches', (collection) => { collection.index('gameId', {gameId: 1}) + collection.index('puuid', {puuid: 1}) }) } diff --git a/server/package-lock.json b/server/package-lock.json index 1944a15..40d6284 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -216,6 +216,11 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.0.0.tgz", "integrity": "sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg==" }, + "adonis-bumblebee": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/adonis-bumblebee/-/adonis-bumblebee-2.0.0.tgz", + "integrity": "sha512-HUvzuoU3Xb4je23Pjec1SLTA8r8GWUoE6BVCl9y32IfrqKD3o1XXWwWcHs5SK1zFgb4OLjB6NDWQmvg8MKs1tg==" + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", diff --git a/server/package.json b/server/package.json index 6aaa9a8..89f67fb 100644 --- a/server/package.json +++ b/server/package.json @@ -24,6 +24,7 @@ "@adonisjs/framework": "^5.0.9", "@adonisjs/ignitor": "^2.0.8", "@adonisjs/lucid": "^6.1.3", + "adonis-bumblebee": "^2.0.0", "got": "^9.6.0", "lucid-mongo": "^3.1.6", "mongodb-core": "^3.2.7", diff --git a/server/start/app.js b/server/start/app.js index 8fa64c9..b9e9263 100644 --- a/server/start/app.js +++ b/server/start/app.js @@ -17,6 +17,7 @@ const providers = [ '@adonisjs/bodyparser/providers/BodyParserProvider', '@adonisjs/cors/providers/CorsProvider', 'lucid-mongo/providers/LucidMongoProvider', + 'adonis-bumblebee/providers/BumblebeeProvider', join(__dirname, '../providers/Jax/JaxProvider') ] @@ -31,7 +32,8 @@ const providers = [ | */ const aceProviders = [ - 'lucid-mongo/providers/MigrationsProvider' + 'lucid-mongo/providers/MigrationsProvider', + 'adonis-bumblebee/providers/CommandsProvider' ] /*