refactor: use db transactions when inserting matches to prevent errors

This commit is contained in:
Valentin Kaelin 2022-01-30 18:01:05 +01:00
parent ec6319ec3b
commit 4811f95aa0
3 changed files with 170 additions and 141 deletions

View file

@ -29,6 +29,8 @@ Vue.filter('kilo', (value) => {
})
Vue.filter('secToTime', (sec, dotNotation = false) => {
if (isNaN(sec)) return 0
const min = Math.floor(sec / 60)
let newSec = Math.floor(sec - min * 60)
newSec = newSec < 10 ? '0' + newSec : newSec
@ -41,6 +43,8 @@ Vue.filter('percent', (value) => {
})
Vue.filter('round', (value, decimals = 2) => {
if (isNaN(value)) return 0
return parseFloat(value.toFixed(decimals))
})

View file

@ -1,4 +1,4 @@
import Database from '@ioc:Adonis/Lucid/Database'
import Database, { TransactionClientContract } from '@ioc:Adonis/Lucid/Database'
import { MatchDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
import Match from 'App/Models/Match'
import { getSeasonNumber, queuesWithRole } from 'App/helpers'
@ -6,157 +6,182 @@ import CDragonService from 'App/Services/CDragonService'
import { ChampionRoles, TeamPosition } from './ParsedType'
class MatchParser {
public async parseOneMatch(match: MatchDto) {
// Parse + store in database
const gameDuration =
match.info.gameDuration > 100_000
? Math.round(match.info.gameDuration / 1000)
: match.info.gameDuration
let parsedMatch: Match | null = null
let trx: TransactionClientContract | undefined
// - 1x Match
const parsedMatch = await Match.create({
id: match.metadata.matchId,
gameId: match.info.gameId,
map: match.info.mapId,
gamemode: match.info.queueId,
date: match.info.gameCreation,
region: match.info.platformId.toLowerCase(),
result: match.info.teams[0].win ? match.info.teams[0].teamId : match.info.teams[1].teamId,
season: getSeasonNumber(match.info.gameCreation),
gameDuration,
})
try {
// Start transaction
trx = await Database.transaction()
const isRemake = gameDuration < 300
const gameDuration =
match.info.gameDuration > 100_000
? Math.round(match.info.gameDuration / 1000)
: match.info.gameDuration
// - 2x MatchTeam : Red and Blue
for (const team of match.info.teams) {
let result = team.win ? 'Win' : 'Fail'
if (isRemake) {
result = 'Remake'
}
await parsedMatch.related('teams').create({
matchId: match.metadata.matchId,
color: team.teamId,
result: result,
barons: team.objectives.baron.kills,
dragons: team.objectives.dragon.kills,
inhibitors: team.objectives.inhibitor.kills,
riftHeralds: team.objectives.riftHerald.kills,
towers: team.objectives.tower.kills,
bans: team.bans.length ? team.bans.map((ban) => ban.championId) : undefined,
banOrders: team.bans.length ? team.bans.map((ban) => ban.pickTurn) : undefined,
})
}
const isRemake = gameDuration < 300
// - 10x MatchPlayer
const matchPlayers: any[] = []
for (const player of match.info.participants) {
const kda =
player.kills + player.assists !== 0 && player.deaths === 0
? player.kills + player.assists
: +(player.deaths === 0 ? 0 : (player.kills + player.assists) / player.deaths).toFixed(2)
// - 1x Match
parsedMatch = await Match.create(
{
id: match.metadata.matchId,
gameId: match.info.gameId,
map: match.info.mapId,
gamemode: match.info.queueId,
date: match.info.gameCreation,
region: match.info.platformId.toLowerCase(),
result: match.info.teams[0].win ? match.info.teams[0].teamId : match.info.teams[1].teamId,
season: getSeasonNumber(match.info.gameCreation),
gameDuration,
},
{ client: trx }
)
const team =
match.info.teams[0].teamId === player.teamId ? match.info.teams[0] : match.info.teams[1]
const teamKills = team.objectives.champion.kills
const kp =
teamKills === 0 ? 0 : +(((player.kills + player.assists) * 100) / teamKills).toFixed(1)
const primaryStyle = player.perks.styles.find((s) => s.description === 'primaryStyle')
const secondaryStyle = player.perks.styles.find((s) => s.description === 'subStyle')
const perksSelected: number[] = []
for (const styles of player.perks.styles) {
for (const perk of styles.selections) {
perksSelected.push(perk.perk)
// - 2x MatchTeam : Red and Blue
for (const team of match.info.teams) {
let result = team.win ? 'Win' : 'Fail'
if (isRemake) {
result = 'Remake'
}
await parsedMatch.related('teams').create({
matchId: match.metadata.matchId,
color: team.teamId,
result: result,
barons: team.objectives.baron.kills,
dragons: team.objectives.dragon.kills,
inhibitors: team.objectives.inhibitor.kills,
riftHeralds: team.objectives.riftHerald.kills,
towers: team.objectives.tower.kills,
bans: team.bans.length ? team.bans.map((ban) => ban.championId) : undefined,
banOrders: team.bans.length ? team.bans.map((ban) => ban.pickTurn) : undefined,
})
}
// Fix championId bug in older matches
if (player.championId > 1000) {
const championId = Object.keys(CDragonService.champions).find(
(key) =>
CDragonService.champions[key].name === player.championName ||
CDragonService.champions[key].alias === player.championName
)
if (!championId) {
console.log(
`CHAMPION NOT FOUND AT ALL: ${player.championId} FROM: ${match.metadata.matchId}`
// - 10x MatchPlayer
const matchPlayers: any[] = []
for (const player of match.info.participants) {
const kda =
player.kills + player.assists !== 0 && player.deaths === 0
? player.kills + player.assists
: +(player.deaths === 0 ? 0 : (player.kills + player.assists) / player.deaths).toFixed(
2
)
const team =
match.info.teams[0].teamId === player.teamId ? match.info.teams[0] : match.info.teams[1]
const teamKills = team.objectives.champion.kills
let kp =
teamKills === 0 ? 0 : +(((player.kills + player.assists) * 100) / teamKills).toFixed(1)
const primaryStyle = player.perks.styles.find((s) => s.description === 'primaryStyle')
const secondaryStyle = player.perks.styles.find((s) => s.description === 'subStyle')
const perksSelected: number[] = []
for (const styles of player.perks.styles) {
for (const perk of styles.selections) {
perksSelected.push(perk.perk)
}
}
// Fix championId bug in older matches
if (player.championId > 1000) {
const championId = Object.keys(CDragonService.champions).find(
(key) =>
CDragonService.champions[key].name === player.championName ||
CDragonService.champions[key].alias === player.championName
)
if (!championId) {
console.log(
`CHAMPION NOT FOUND AT ALL: ${player.championId} FROM: ${match.metadata.matchId}`
)
}
player.championId = championId ? Number(championId) : 1
}
player.championId = championId ? Number(championId) : 1
const originalChampionData = CDragonService.champions[player.championId]
const champRoles = originalChampionData.roles
matchPlayers.push({
match_id: match.metadata.matchId,
participant_id: player.participantId,
summoner_id: player.summonerId,
summoner_puuid: player.puuid,
summoner_name: player.summonerName,
win: team.win ? 1 : 0,
loss: team.win ? 0 : 1,
remake: isRemake ? 1 : 0,
team: player.teamId,
team_position:
player.teamPosition.length && queuesWithRole.includes(match.info.queueId)
? TeamPosition[player.teamPosition]
: TeamPosition.NONE,
kills: player.kills,
deaths: player.deaths,
assists: player.assists,
kda: kda,
kp: kp,
champ_level: player.champLevel,
champion_id: player.championId,
champion_role: ChampionRoles[champRoles[0]],
double_kills: player.doubleKills,
triple_kills: player.tripleKills,
quadra_kills: player.quadraKills,
penta_kills: player.pentaKills,
baron_kills: player.baronKills,
dragon_kills: player.dragonKills,
turret_kills: player.turretKills,
vision_score: player.visionScore,
gold: player.goldEarned,
summoner1_id: player.summoner1Id,
summoner2_id: player.summoner2Id,
item0: player.item0,
item1: player.item1,
item2: player.item2,
item3: player.item3,
item4: player.item4,
item5: player.item5,
item6: player.item6,
damage_dealt_objectives: player.damageDealtToObjectives,
damage_dealt_champions: player.totalDamageDealtToChampions,
damage_taken: player.totalDamageTaken,
heal: player.totalHeal,
minions: player.totalMinionsKilled + player.neutralMinionsKilled,
critical_strike: player.largestCriticalStrike,
killing_spree: player.killingSprees,
time_spent_living: player.longestTimeSpentLiving,
perks_primary_style: primaryStyle!.style,
perks_secondary_style: secondaryStyle!.style,
perks_selected: perksSelected.concat(Object.values(player.perks.statPerks)),
})
}
await Database.table('match_players').multiInsert(matchPlayers)
const originalChampionData = CDragonService.champions[player.championId]
const champRoles = originalChampionData.roles
matchPlayers.push({
match_id: match.metadata.matchId,
participant_id: player.participantId,
summoner_id: player.summonerId,
summoner_puuid: player.puuid,
summoner_name: player.summonerName,
win: team.win ? 1 : 0,
loss: team.win ? 0 : 1,
remake: isRemake ? 1 : 0,
team: player.teamId,
team_position:
player.teamPosition.length && queuesWithRole.includes(match.info.queueId)
? TeamPosition[player.teamPosition]
: TeamPosition.NONE,
kills: player.kills,
deaths: player.deaths,
assists: player.assists,
kda: kda,
kp: kp,
champ_level: player.champLevel,
champion_id: player.championId,
champion_role: ChampionRoles[champRoles[0]],
double_kills: player.doubleKills,
triple_kills: player.tripleKills,
quadra_kills: player.quadraKills,
penta_kills: player.pentaKills,
baron_kills: player.baronKills,
dragon_kills: player.dragonKills,
turret_kills: player.turretKills,
vision_score: player.visionScore,
gold: player.goldEarned,
summoner1_id: player.summoner1Id,
summoner2_id: player.summoner2Id,
item0: player.item0,
item1: player.item1,
item2: player.item2,
item3: player.item3,
item4: player.item4,
item5: player.item5,
item6: player.item6,
damage_dealt_objectives: player.damageDealtToObjectives,
damage_dealt_champions: player.totalDamageDealtToChampions,
damage_taken: player.totalDamageTaken,
heal: player.totalHeal,
minions: player.totalMinionsKilled + player.neutralMinionsKilled,
critical_strike: player.largestCriticalStrike,
killing_spree: player.killingSprees,
time_spent_living: player.longestTimeSpentLiving,
perks_primary_style: primaryStyle!.style,
perks_secondary_style: secondaryStyle!.style,
perks_selected: perksSelected.concat(Object.values(player.perks.statPerks)),
// Load Match relations
await parsedMatch.load((loader) => {
loader.load('teams').load('players')
})
}
await Database.table('match_players').multiInsert(matchPlayers)
// Load Match relations
await parsedMatch.load((loader) => {
loader.load('teams').load('players')
})
if (!trx.isCompleted) {
await trx.commit()
}
} catch (error) {
console.log('Error in MatchParser transaction.')
if (trx && trx.isTransaction) {
await trx.rollback()
return null
}
}
return parsedMatch
}
public async parse(matches: MatchDto[]) {
// Loop on all matches and call .parseOneMatch on it
const parsedMatches: Match[] = []
for (const match of matches) {
parsedMatches.push(await this.parseOneMatch(match))
const parsed = await this.parseOneMatch(match)
if (parsed) {
parsedMatches.push(parsed)
}
}
return parsedMatches
}

View file

@ -77,16 +77,16 @@ class MatchRepository {
public async globalStats(filters: SelectFilters) {
const query = `
SELECT
SUM(match_players.kills) as kills,
SUM(match_players.deaths) as deaths,
SUM(match_players.assists) as assists,
SUM(match_players.minions) as minions,
SUM(matches.game_duration) as time,
SUM(match_players.vision_score) as vision,
COALESCE(SUM(match_players.kills), 0) as kills,
COALESCE(SUM(match_players.deaths), 0) as deaths,
COALESCE(SUM(match_players.assists), 0) as assists,
COALESCE(SUM(match_players.minions), 0) as minions,
COALESCE(SUM(matches.game_duration), 0) as time,
COALESCE(SUM(match_players.vision_score), 0) as vision,
COUNT(match_players.id) as count,
AVG(match_players.kp) as kp,
SUM(match_players.win) as wins,
SUM(match_players.loss) as losses
COALESCE(AVG(match_players.kp), 0) as kp,
COALESCE(SUM(match_players.win), 0) as wins,
COALESCE(SUM(match_players.loss), 0) as losses
FROM
match_players
${this.JOIN_MATCHES}