refactor: transform matches data to store in db only useful data

This commit is contained in:
Valentin Kaelin 2019-09-29 18:06:54 +02:00
parent 68d15872ce
commit 4f13d8400e
11 changed files with 270 additions and 150 deletions

View file

@ -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)
}

View file

@ -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')

View file

@ -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'])
}
}

View file

@ -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,7 +91,22 @@ class SummonerController {
}
const requests = matchesToGetFromRiot.map(Jax.Match.get)
const matchesFromApi = await Promise.all(requests)
let matchesFromApi = await Promise.all(requests)
/* 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 */
@ -72,9 +114,10 @@ class SummonerController {
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

View file

@ -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()

View file

@ -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

View file

@ -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'
}

View file

@ -7,6 +7,7 @@ class MatchSchema extends Schema {
up () {
this.create('matches', (collection) => {
collection.index('gameId', {gameId: 1})
collection.index('puuid', {puuid: 1})
})
}

View file

@ -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",

View file

@ -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",

View file

@ -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'
]
/*