mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 21:07:27 +00:00
Merge branch 'V2'
This commit is contained in:
commit
11fb369051
114 changed files with 31057 additions and 9074 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
.DS_STORE
|
||||||
1
LeagueStatsSQL.diagram
Normal file
1
LeagueStatsSQL.diagram
Normal file
File diff suppressed because one or more lines are too long
13
README.md
13
README.md
|
|
@ -3,7 +3,6 @@
|
||||||
[](https://app.netlify.com/sites/leaguestats-gg/deploys)
|
[](https://app.netlify.com/sites/leaguestats-gg/deploys)
|
||||||
<a href="https://discord.gg/RjBzjfk"><img src="https://img.shields.io/badge/Discord-join%20chat-738bd7.svg" alt="LeagueStats.gg official Discord"></a>
|
<a href="https://discord.gg/RjBzjfk"><img src="https://img.shields.io/badge/Discord-join%20chat-738bd7.svg" alt="LeagueStats.gg official Discord"></a>
|
||||||
|
|
||||||
|
|
||||||
The goal of [leaguestats.gg](https://leaguestats.gg) is to provide global complete data for all League of Legends summoners.
|
The goal of [leaguestats.gg](https://leaguestats.gg) is to provide global complete data for all League of Legends summoners.
|
||||||
Here is an [example](https://leaguestats.gg/summoner/euw/SammyWinchester) of stats for some summoner.
|
Here is an [example](https://leaguestats.gg/summoner/euw/SammyWinchester) of stats for some summoner.
|
||||||
|
|
||||||
|
|
@ -18,11 +17,15 @@ Here is an [example](https://leaguestats.gg/summoner/euw/SammyWinchester) of sta
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Development environment requirements :
|
Development environment requirements :
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download/) >= 12.0.0
|
- [Node.js](https://nodejs.org/en/download/) >= 12.0.0
|
||||||
- [MongoDB](https://www.mongodb.com/download-center/community) >= 4.4
|
- [PostgreSQL](https://www.postgresql.org/download/)
|
||||||
- [Redis](https://redis.io/download)
|
- [Redis](https://redis.io/download)
|
||||||
|
|
||||||
|
You can use the `docker-compose.yml` file to quickly setup Postgre and Redis in development.
|
||||||
|
|
||||||
Setting up your development environment on your local machine :
|
Setting up your development environment on your local machine :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
> git clone https://github.com/vkaelin/LeagueStats.git
|
> git clone https://github.com/vkaelin/LeagueStats.git
|
||||||
> cd leaguestats/client
|
> cd leaguestats/client
|
||||||
|
|
@ -33,11 +36,13 @@ Setting up your development environment on your local machine :
|
||||||
> cd leaguestats/server
|
> cd leaguestats/server
|
||||||
> npm install
|
> npm install
|
||||||
> cp .env.example .env # edit the values
|
> cp .env.example .env # edit the values
|
||||||
> node ace mongodb:migration:run # your MongoDB installation needs to by a Replica Set and not a Standalone
|
> node ace migration:run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Useful commands
|
## Useful commands
|
||||||
|
|
||||||
Running the app :
|
Running the app :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
> cd client
|
> cd client
|
||||||
> npm run dev
|
> npm run dev
|
||||||
|
|
@ -49,6 +54,7 @@ Running the app :
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploying the app :
|
Deploying the app :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
> cd client
|
> cd client
|
||||||
> npm run build
|
> npm run build
|
||||||
|
|
@ -77,7 +83,6 @@ Adapt — remix, transform, and build upon the material
|
||||||
|
|
||||||
### Under the following terms:
|
### Under the following terms:
|
||||||
|
|
||||||
|
|
||||||
NonCommercial — You may not use the material for commercial purposes.
|
NonCommercial — You may not use the material for commercial purposes.
|
||||||
|
|
||||||
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
||||||
|
|
|
||||||
21512
client/package-lock.json
generated
21512
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
|
@ -5,6 +5,7 @@
|
||||||
:data="allyTeam"
|
:data="allyTeam"
|
||||||
:all-players="[...allyTeam.players, ...enemyTeam.players]"
|
:all-players="[...allyTeam.players, ...enemyTeam.players]"
|
||||||
:ally-team="true"
|
:ally-team="true"
|
||||||
|
:ranks-loaded="data.ranksLoaded"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex items-start justify-between px-3 py-2">
|
<div class="flex items-start justify-between px-3 py-2">
|
||||||
|
|
@ -23,6 +24,7 @@
|
||||||
:data="enemyTeam"
|
:data="enemyTeam"
|
||||||
:all-players="[...allyTeam.players, ...enemyTeam.players]"
|
:all-players="[...allyTeam.players, ...enemyTeam.players]"
|
||||||
:ally-team="false"
|
:ally-team="false"
|
||||||
|
:ranks-loaded="data.ranksLoaded"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="data.status === 'loading' && detailsOpen">
|
<div v-else-if="data.status === 'loading' && detailsOpen">
|
||||||
|
|
|
||||||
|
|
@ -91,29 +91,29 @@
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
player.firstSum ? player.firstSum.icon : null
|
player.summonerSpell1 ? player.summonerSpell1.icon : null
|
||||||
})`,
|
})`,
|
||||||
}"
|
}"
|
||||||
:class="{ 'cursor-pointer': player.firstSum }"
|
:class="{ 'cursor-pointer': player.summonerSpell1 }"
|
||||||
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="player.firstSum" #default>
|
<template v-if="player.summonerSpell1" #default>
|
||||||
<div
|
<div
|
||||||
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url('${player.firstSum.icon}')`,
|
backgroundImage: `url('${player.summonerSpell1.icon}')`,
|
||||||
}"
|
}"
|
||||||
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
<div class="ml-2 leading-tight">
|
<div class="ml-2 leading-tight">
|
||||||
<div class="text-base leading-none">
|
<div class="text-base leading-none">
|
||||||
{{ player.firstSum.name }}
|
{{ player.summonerSpell1.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 font-light text-blue-200">
|
<div class="mt-1 font-light text-blue-200">
|
||||||
{{ player.firstSum.description }}
|
{{ player.summonerSpell1.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -124,29 +124,29 @@
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${
|
backgroundImage: `url(${
|
||||||
player.secondSum ? player.secondSum.icon : null
|
player.summonerSpell2 ? player.summonerSpell2.icon : null
|
||||||
})`,
|
})`,
|
||||||
}"
|
}"
|
||||||
:class="{ 'cursor-pointer': player.secondSum }"
|
:class="{ 'cursor-pointer': player.summonerSpell2 }"
|
||||||
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-4 h-4 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="player.secondSum" #default>
|
<template v-if="player.summonerSpell2" #default>
|
||||||
<div
|
<div
|
||||||
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
class="flex max-w-sm p-2 text-xs text-left text-white select-none"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url('${player.secondSum.icon}')`,
|
backgroundImage: `url('${player.summonerSpell2.icon}')`,
|
||||||
}"
|
}"
|
||||||
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
class="flex-shrink-0 w-12 h-12 ml-1 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
<div class="ml-2 leading-tight">
|
<div class="ml-2 leading-tight">
|
||||||
<div class="text-base leading-none">
|
<div class="text-base leading-none">
|
||||||
{{ player.secondSum.name }}
|
{{ player.summonerSpell2.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 font-light text-blue-200">
|
<div class="mt-1 font-light text-blue-200">
|
||||||
{{ player.secondSum.description }}
|
{{ player.summonerSpell2.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,7 +195,7 @@
|
||||||
class="flex flex-col items-start justify-center ml-1 leading-none"
|
class="flex flex-col items-start justify-center ml-1 leading-none"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="player.firstSum"
|
v-if="player.summonerSpell1"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'summoner',
|
name: 'summoner',
|
||||||
params: { region: $route.params.region, name: player.name },
|
params: { region: $route.params.region, name: player.name },
|
||||||
|
|
@ -228,7 +228,7 @@
|
||||||
{{ player.rank.shortName }}
|
{{ player.rank.shortName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="player.rank === undefined">
|
<div v-else-if="!ranksLoaded">
|
||||||
<DotsLoader width="30px" dot-width="10px" />
|
<DotsLoader width="30px" dot-width="10px" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-5 h-5">
|
<div v-else class="w-5 h-5">
|
||||||
|
|
@ -350,6 +350,10 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
ranksLoaded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,23 @@
|
||||||
></div>
|
></div>
|
||||||
<div class="flex flex-col justify-around ml-2">
|
<div class="flex flex-col justify-around ml-2">
|
||||||
<div
|
<div
|
||||||
:style="{backgroundImage: `url(${data.firstSum})`}"
|
v-if="data.summonerSpell1"
|
||||||
|
:style="{backgroundImage: `url(${data.summonerSpell1.icon})`}"
|
||||||
class="w-6 h-6 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-6 h-6 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
:style="{backgroundImage: `url(${data.secondSum})`}"
|
v-else
|
||||||
|
class="w-6 h-6 rounded-md bg-blue-1000"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="data.summonerSpell2"
|
||||||
|
:style="{backgroundImage: `url(${data.summonerSpell2.icon})`}"
|
||||||
class="w-6 h-6 bg-center bg-cover rounded-md bg-blue-1000"
|
class="w-6 h-6 bg-center bg-cover rounded-md bg-blue-1000"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="w-6 h-6 rounded-md bg-blue-1000"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-around ml-1">
|
<div class="flex flex-col justify-around ml-1">
|
||||||
<div
|
<div
|
||||||
|
|
@ -156,7 +166,7 @@
|
||||||
<svg class="w-5 h-5 text-blue-200">
|
<svg class="w-5 h-5 text-blue-200">
|
||||||
<use xlink:href="#stopwatch" />
|
<use xlink:href="#stopwatch" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-lg font-medium text-teal-400">{{ data.time|secToTime }}</div>
|
<div class="text-lg font-medium text-teal-400">{{ (data.time)|secToTime }}</div>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="text-xs font-medium text-white">{{ data.date }}</div>
|
<div class="text-xs font-medium text-white">{{ data.date }}</div>
|
||||||
|
|
@ -175,7 +185,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Ripple>
|
</Ripple>
|
||||||
<DetailedMatch :data="getMatchDetails(data.gameId) || {}" :details-open="showDetails" />
|
<DetailedMatch :data="getMatchDetails(data.matchId) || {}" :details-open="showDetails" />
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -223,8 +233,8 @@ export default {
|
||||||
displayDetails() {
|
displayDetails() {
|
||||||
this.showDetails = !this.showDetails
|
this.showDetails = !this.showDetails
|
||||||
|
|
||||||
if (!this.getMatchDetails(this.data.gameId)) {
|
if (!this.getMatchDetails(this.data.matchId)) {
|
||||||
this.matchDetails(this.data.gameId)
|
this.matchDetails(this.data.matchId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSummonerProfile(account_id) {
|
isSummonerProfile(account_id) {
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
class="flex flex-col items-center runes"
|
class="flex flex-col items-center runes"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{backgroundImage: `url('${player.runes.primaryRune}')`}"
|
:style="{backgroundImage: `url('${getPrimarRune(player.perks)}')`}"
|
||||||
class="w-6 h-6 bg-center bg-cover"
|
class="w-6 h-6 bg-center bg-cover"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
:style="{backgroundImage: `url('${player.runes.secondaryRune}')`}"
|
:style="{backgroundImage: `url('${getSecondaryRune(player.perks)}')`}"
|
||||||
class="w-3 h-3 mt-1 bg-center bg-cover"
|
class="w-3 h-3 mt-1 bg-center bg-cover"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -72,7 +72,11 @@
|
||||||
:class="[player.summonerId === account.id ? 'text-yellow-500' : 'hover:text-blue-200']"
|
:class="[player.summonerId === account.id ? 'text-yellow-500' : 'hover:text-blue-200']"
|
||||||
class="font-semibold"
|
class="font-semibold"
|
||||||
>{{ player.summonerName }}</router-link>
|
>{{ player.summonerName }}</router-link>
|
||||||
<div class="text-xs">Level {{ player.level }}</div>
|
<div
|
||||||
|
:class="[ally ? 'text-teal-300 ' : 'text-red-400 ']"
|
||||||
|
class="text-xs"
|
||||||
|
>{{ player.champion.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -185,7 +189,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'vuex'
|
import { mapActions, mapState } from 'vuex'
|
||||||
import { getSummonerLink } from '@/helpers/summoner.js'
|
import { getSummonerLink, getPrimarRune, getSecondaryRune } from '@/helpers/summoner.js'
|
||||||
import { ContentLoader } from 'vue-content-loader'
|
import { ContentLoader } from 'vue-content-loader'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -264,17 +268,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectRunes(player) {
|
selectRunes(player) {
|
||||||
if(!player.perks) {
|
if(!player.perks)
|
||||||
return
|
return
|
||||||
}
|
this.displayOrHideRunes(player.perks)
|
||||||
|
|
||||||
this.displayOrHideRunes({
|
|
||||||
primaryStyle: player.perks.perkStyle,
|
|
||||||
secondaryStyle: player.perks.perkSubStyle,
|
|
||||||
selected: player.perks.perkIds
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
getSummonerLink,
|
getSummonerLink,
|
||||||
|
getPrimarRune,
|
||||||
|
getSecondaryRune,
|
||||||
...mapActions('cdragon', ['displayOrHideRunes']),
|
...mapActions('cdragon', ['displayOrHideRunes']),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<ul class="mt-1 text-gray-100">
|
<ul class="mt-1 text-gray-100">
|
||||||
<li
|
<li
|
||||||
v-for="mate in mates.slice(0, maxMates)"
|
v-for="mate in mates.slice(0, maxMates)"
|
||||||
:key="mate._id"
|
:key="mate.name"
|
||||||
class="flex items-center justify-between"
|
class="flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
|
|
||||||
|
|
@ -185,12 +185,12 @@
|
||||||
</div>
|
</div>
|
||||||
<ul class="mt-1 text-gray-100">
|
<ul class="mt-1 text-gray-100">
|
||||||
<li
|
<li
|
||||||
v-for="(championClass, index) in championClasses"
|
v-for="(championClass, index) in stats.class"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="{'bg-blue-760': index % 2 !== 0}"
|
:class="{'bg-blue-760': index % 2 !== 0}"
|
||||||
class="flex items-center justify-between px-4 py-1 leading-tight"
|
class="flex items-center justify-between px-4 py-1 leading-tight"
|
||||||
>
|
>
|
||||||
<div class="w-2/4 text-left capitalize">{{ championClass._id }}</div>
|
<div class="w-2/4 text-left capitalize">{{ championClass.id }}</div>
|
||||||
<div
|
<div
|
||||||
:class="calculateWinrate(championClass.wins, championClass.count).color"
|
:class="calculateWinrate(championClass.wins, championClass.count).color"
|
||||||
class="w-1/4"
|
class="w-1/4"
|
||||||
|
|
@ -241,16 +241,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
championClasses() {
|
|
||||||
const classes = [...this.stats.class]
|
|
||||||
return classes.sort((a, b) => b.count - a.count)
|
|
||||||
},
|
|
||||||
mostPlayedRole() {
|
mostPlayedRole() {
|
||||||
return Math.max(...this.stats.role.map(r => r.count), 0)
|
return Math.max(...this.stats.role.map(r => r.count), 0)
|
||||||
},
|
},
|
||||||
globalStatsKeys() {
|
globalStatsKeys() {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { _id, wins, losses, count, time, kp, ...rest } = this.stats.global
|
const { id, wins, losses, count, time, kp, ...rest } = this.stats.global
|
||||||
return rest
|
return rest
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState({
|
||||||
|
|
@ -270,10 +266,9 @@ export default {
|
||||||
leagueStatsByType(typeName) {
|
leagueStatsByType(typeName) {
|
||||||
return this.stats.league
|
return this.stats.league
|
||||||
.map(l => {
|
.map(l => {
|
||||||
return { ...l, ...gameModes[l._id] }
|
return { ...l, ...gameModes[l.id] }
|
||||||
})
|
})
|
||||||
.filter(l => l.type === typeName)
|
.filter(l => l.type === typeName)
|
||||||
.sort((a, b) => b.count - a.count)
|
|
||||||
},
|
},
|
||||||
roundedRoleLosses(win, count) {
|
roundedRoleLosses(win, count) {
|
||||||
return win === count ? 'rounded-full' : 'rounded-b-full'
|
return win === count ? 'rounded-full' : 'rounded-b-full'
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
import Tooltip from '@/components/Common/Tooltip.vue'
|
import Tooltip from '@/components/Common/Tooltip.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -64,15 +65,6 @@ export default {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
|
||||||
matches: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
gridDays: [],
|
gridDays: [],
|
||||||
|
|
@ -86,8 +78,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
recentActivity: state => state.summoner.basic.recentActivity
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
matches() {
|
recentActivity() {
|
||||||
this.fillGrid()
|
this.fillGrid()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -118,16 +116,15 @@ export default {
|
||||||
},
|
},
|
||||||
fillGrid() {
|
fillGrid() {
|
||||||
// Add all the matches made by the summoner
|
// Add all the matches made by the summoner
|
||||||
for (const key in this.matches) {
|
for (const match of this.recentActivity) {
|
||||||
const match = this.matches[key]
|
const matchTime = new Date(match.day)
|
||||||
const matchTime = new Date(match.timestamp)
|
|
||||||
const formattedTime = matchTime.toLocaleString(undefined, this.options)
|
const formattedTime = matchTime.toLocaleString(undefined, this.options)
|
||||||
|
|
||||||
const dayOfTheMatch = this.gridDays.filter(
|
const dayOfTheMatch = this.gridDays.filter(
|
||||||
e => e.date === formattedTime
|
e => e.date === formattedTime
|
||||||
)
|
)
|
||||||
if (dayOfTheMatch.length > 0) {
|
if (dayOfTheMatch.length > 0) {
|
||||||
dayOfTheMatch[0].matches++
|
dayOfTheMatch[0].matches = match.count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
`${hover ? gradientHover : gradient},
|
`${hover ? gradientHover : gradient},
|
||||||
url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-splashes/${record.champion.id}/${record.champion.id}000.jpg')`
|
url('https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-splashes/${record.champion_id}/${record.champion_id}000.jpg')`
|
||||||
}"
|
}"
|
||||||
:class="borderColor"
|
:class="borderColor"
|
||||||
class="relative w-full p-4 mx-2 mt-6 leading-none bg-center bg-cover border rounded-lg record-card"
|
class="relative w-full p-4 mx-2 mt-6 leading-none bg-center bg-cover border rounded-lg record-card"
|
||||||
|
|
@ -21,18 +21,18 @@
|
||||||
<span :class="textColor" class="ml-0">{{ title }}</span>
|
<span :class="textColor" class="ml-0">{{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
:src="`https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${record.champion.id}.png`"
|
:src="`https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/${record.champion_id}.png`"
|
||||||
:class="[{'opacity-0 scale-125': hover}, borderColor]"
|
:class="[{'opacity-0 scale-125': hover}, borderColor]"
|
||||||
class="block w-16 h-16 mx-auto mt-10 transition duration-500 ease-in transform border-2 rounded-full"
|
class="block w-16 h-16 mx-auto mt-10 transition duration-500 ease-in transform border-2 rounded-full"
|
||||||
alt="Champion Played"
|
alt="Champion Played"
|
||||||
/>
|
/>
|
||||||
<div :style="{textShadow: `-2px 1px 6px ${color}`}" class="mt-6 text-4xl">{{ record[property] }}</div>
|
<div :style="{textShadow: `-2px 1px 6px ${color}`}" class="mt-6 text-4xl">{{ record.amount }}</div>
|
||||||
|
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<span
|
<span
|
||||||
:class="record.result === 'Win' ? 'text-green-400' : 'text-red-400'"
|
:class="record.result ? 'text-green-400' : 'text-red-400'"
|
||||||
>{{ record.result === 'Win' ? 'Won' : 'Lost' }}</span>
|
>{{ record.result ? 'Won' : 'Lost' }}</span>
|
||||||
<span class="ml-1 font-semibold">{{ timeDifference(record.date) }}</span>
|
<span class="ml-1 font-semibold">{{ timeDifference(record.date) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-gray-500">
|
<div class="mt-2 text-gray-500">
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 text-xs font-light text-right text-gray-200 opacity-25">
|
<div class="mt-6 text-xs font-light text-right text-gray-200 opacity-25">
|
||||||
<span v-if="hover">match {{ record.gameId }}</span>
|
<span v-if="hover">{{ record.id }}</span>
|
||||||
<span v-else>{{ gameModes[record.gamemode].name }}</span>
|
<span v-else>{{ gameModes[record.gamemode].name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,10 +65,6 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
property: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
record: {
|
record: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export function secToTime(seconds) {
|
||||||
* Sort an array of players by role
|
* Sort an array of players by role
|
||||||
*/
|
*/
|
||||||
export function sortTeamByRole(a, b) {
|
export function sortTeamByRole(a, b) {
|
||||||
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
||||||
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,37 @@
|
||||||
import { secToTime, timeDifference } from '@/helpers/functions.js'
|
import { createCDragonAssetUrl, secToTime, timeDifference } from '@/helpers/functions.js'
|
||||||
import { maps, gameModes } from '@/data/data.js'
|
import { maps, gameModes } from '@/data/data.js'
|
||||||
import summonerSpells from '@/data/summonerSpells.json'
|
import summonerSpells from '@/data/summonerSpells.json'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
const leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of the of the player primary rune
|
||||||
|
* @param {Object} perks : from the API
|
||||||
|
*/
|
||||||
|
export function getPrimarRune(perks) {
|
||||||
|
const primaryRune = perks.selected.length ? store.state.cdragon.runes.perks[perks.selected[0]] : null
|
||||||
|
return primaryRune ? createCDragonAssetUrl(primaryRune.icon) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of the of the player secondary rune
|
||||||
|
* @param {Object} perks : from the API
|
||||||
|
*/
|
||||||
|
export function getSecondaryRune(perks) {
|
||||||
|
const secondaryRune = store.state.cdragon.runes.perkstyles[perks.secondaryStyle]
|
||||||
|
return secondaryRune ? createCDragonAssetUrl(secondaryRune.icon) : null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the infos about a list of matches built with the Riot API data
|
* Return all the infos about a list of matches built with the Riot API data
|
||||||
* @param {Object} RiotData : all data from the Riot API
|
* @param {Object} RiotData : all data from the Riot API
|
||||||
*/
|
*/
|
||||||
export function createMatchData(matches) {
|
export function createMatchData(matches) {
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
match.firstSum = getSummonerLink(match.firstSum)
|
// Runes
|
||||||
match.secondSum = getSummonerLink(match.secondSum)
|
match.primaryRune = getPrimarRune(match.perks)
|
||||||
|
match.secondaryRune = getSecondaryRune(match.perks)
|
||||||
|
|
||||||
const date = new Date(match.date)
|
const date = new Date(match.date)
|
||||||
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }
|
const dateOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }
|
||||||
|
|
@ -62,21 +82,22 @@ export function createBasicSummonerData(RiotData) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the formatted records of a summoner
|
* Return the formatted records of a summoner
|
||||||
* @param {Object} records : raw records from the database stats
|
* @param {Object} recordsDto : raw records from the database stats
|
||||||
*/
|
*/
|
||||||
export function createRecordsData(records) {
|
export function createRecordsData(recordsDto) {
|
||||||
records.maxTime.time = secToTime(records.maxTime.time)
|
const records = recordsDto.reduce((acc, record) => {
|
||||||
records.maxGold.gold = records.maxGold.gold.toLocaleString()
|
acc[record.what] = record
|
||||||
records.maxDmgTaken.dmgTaken = records.maxDmgTaken.dmgTaken.toLocaleString()
|
return acc
|
||||||
records.maxDmgChamp.dmgChamp = records.maxDmgChamp.dmgChamp.toLocaleString()
|
}, {})
|
||||||
records.maxDmgObj.dmgObj = records.maxDmgObj.dmgObj.toLocaleString()
|
|
||||||
records.maxKp.kp = `${records.maxKp.kp}%`
|
|
||||||
|
|
||||||
// New record fields
|
records.game_duration.amount = secToTime(records.game_duration.amount)
|
||||||
if (records.maxLiving) {
|
records.gold.amount = records.gold.amount.toLocaleString()
|
||||||
records.maxLiving.longestLiving = secToTime(records.maxLiving.longestLiving)
|
records.damage_taken.amount = records.damage_taken.amount.toLocaleString()
|
||||||
records.maxHeal.heal = records.maxHeal.heal.toLocaleString()
|
records.damage_dealt_champions.amount = records.damage_dealt_champions.amount.toLocaleString()
|
||||||
}
|
records.damage_dealt_objectives.amount = records.damage_dealt_objectives.amount.toLocaleString()
|
||||||
|
records.kp.amount = `${records.kp.amount}%`
|
||||||
|
records.time_spent_living.amount = secToTime(records.time_spent_living.amount)
|
||||||
|
records.heal.amount = records.heal.amount.toLocaleString()
|
||||||
|
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<RecentActivity :matches="basic.matchList" />
|
<RecentActivity />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default new Vuex.Store({
|
||||||
'tr': 'tr1',
|
'tr': 'tr1',
|
||||||
'ru': 'ru'
|
'ru': 'ru'
|
||||||
},
|
},
|
||||||
roles: ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
roles: ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
||||||
},
|
},
|
||||||
strict: debug
|
strict: debug
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,47 +8,70 @@ export const state = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
MATCH_LOADING(state, gameId) {
|
MATCH_LOADING(state, matchId) {
|
||||||
const alreadyIn = state.matches.find(m => m.gameId === gameId)
|
const alreadyIn = state.matches.find(m => m.matchId === matchId)
|
||||||
if (!alreadyIn) {
|
if (!alreadyIn) {
|
||||||
state.matches.push({ gameId: gameId, status: 'loading' })
|
state.matches.push({ matchId, status: 'loading' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MATCH_FOUND(state, matchDetails) {
|
MATCH_FOUND(state, {matchDetails, ranksLoaded }) {
|
||||||
matchDetails.status = 'loaded'
|
matchDetails.status = 'loaded'
|
||||||
|
matchDetails.ranksLoaded = ranksLoaded
|
||||||
|
|
||||||
|
// Set SoloQ as rank for now
|
||||||
|
if(ranksLoaded) {
|
||||||
|
for (const player of matchDetails.blueTeam.players) {
|
||||||
|
player.rank = player.rank && player.rank[420]
|
||||||
|
}
|
||||||
|
for (const player of matchDetails.redTeam.players) {
|
||||||
|
player.rank = player.rank && player.rank[420]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const index = state.matches.findIndex(m => m.gameId === matchDetails.gameId)
|
const index = state.matches.findIndex(m => m.gameId === matchDetails.gameId)
|
||||||
Vue.set(state.matches, index, matchDetails)
|
Vue.set(state.matches, index, matchDetails)
|
||||||
},
|
},
|
||||||
MATCH_RANKS_FOUND(state, { gameId, blueTeam, redTeam }) {
|
MATCH_RANKS_FOUND(state, { gameId, ranksByPlayer }) {
|
||||||
const match = state.matches.find(m => m.gameId === gameId)
|
const match = state.matches.find(m => m.gameId === gameId)
|
||||||
match.blueTeam.players = blueTeam
|
|
||||||
match.redTeam.players = redTeam
|
for (const player of match.blueTeam.players) {
|
||||||
|
const ranks = ranksByPlayer[player.id]
|
||||||
|
if(!ranks) continue
|
||||||
|
Vue.set(player, 'rank', ranks[420])
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const player of match.redTeam.players) {
|
||||||
|
const ranks = ranksByPlayer[player.id]
|
||||||
|
if(!ranks) continue
|
||||||
|
Vue.set(player, 'rank', ranks[420])
|
||||||
|
}
|
||||||
|
|
||||||
|
match.ranksLoaded = true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async matchDetails({ commit, rootState }, gameId) {
|
async matchDetails({ commit }, matchId) {
|
||||||
commit('MATCH_LOADING', gameId)
|
commit('MATCH_LOADING', matchId)
|
||||||
const region = rootState.regionsList[rootState.settings.region]
|
console.log('MATCH DETAILS STORE', matchId)
|
||||||
console.log('MATCH DETAILS STORE', gameId, region)
|
|
||||||
|
|
||||||
const resp = await axios(({ url: 'match/details', data: { gameId, region }, method: 'POST' })).catch(() => { })
|
const resp = await axios(({ url: 'match/details', data: { matchId }, method: 'POST' })).catch(() => { })
|
||||||
console.log('--- DETAILS INFOS ---')
|
console.log('--- DETAILS INFOS ---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
commit('MATCH_FOUND', resp.data.matchDetails)
|
const {matchDetails, ranksLoaded} = resp.data
|
||||||
|
commit('MATCH_FOUND', {matchDetails, ranksLoaded })
|
||||||
|
|
||||||
// If the ranks of the players are not yet known
|
// If the ranks of the players are not yet known
|
||||||
if (resp.data.matchDetails.blueTeam.players[0].rank === undefined) {
|
if (!ranksLoaded) {
|
||||||
const ranks = await axios(({ url: 'match/details/ranks', data: { gameId, region }, method: 'POST' })).catch(() => { })
|
const ranks = await axios(({ url: 'match/details/ranks', data: { matchId }, method: 'POST' })).catch(() => { })
|
||||||
if (!ranks) return
|
if (!ranks) return
|
||||||
console.log('--- RANK OF MATCH DETAILS ---')
|
console.log('--- RANK OF MATCH DETAILS ---')
|
||||||
console.log(ranks.data)
|
console.log(ranks.data)
|
||||||
commit('MATCH_RANKS_FOUND', { gameId, ...ranks.data })
|
commit('MATCH_RANKS_FOUND', { matchId, ranksByPlayer: ranks.data })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getMatchDetails: state => gameId => state.matches.find(m => m.gameId === gameId),
|
getMatchDetails: state => matchId => state.matches.find(m => m.matchId === matchId),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export const state = {
|
||||||
currentSeason: null,
|
currentSeason: null,
|
||||||
matchList: [],
|
matchList: [],
|
||||||
ranked: {},
|
ranked: {},
|
||||||
|
recentActivity: [],
|
||||||
seasons: [],
|
seasons: [],
|
||||||
status: '',
|
status: '',
|
||||||
},
|
},
|
||||||
|
|
@ -24,7 +25,7 @@ export const state = {
|
||||||
championsLoaded: false
|
championsLoaded: false
|
||||||
},
|
},
|
||||||
records: {
|
records: {
|
||||||
list: [],
|
list: {},
|
||||||
recordsLoaded: false
|
recordsLoaded: false
|
||||||
},
|
},
|
||||||
live: {
|
live: {
|
||||||
|
|
@ -66,18 +67,21 @@ export const mutations = {
|
||||||
state.overview.matchesLoading = true
|
state.overview.matchesLoading = true
|
||||||
},
|
},
|
||||||
MATCHES_FOUND(state, { newMatches, stats }) {
|
MATCHES_FOUND(state, { newMatches, stats }) {
|
||||||
|
state.basic.recentActivity = stats.recentActivity
|
||||||
state.overview.matchesLoading = false
|
state.overview.matchesLoading = false
|
||||||
state.overview.matches = [...state.overview.matches, ...newMatches]
|
state.overview.matches = [...state.overview.matches, ...newMatches]
|
||||||
state.overview.matchIndex += newMatches.length
|
state.overview.matchIndex += 10
|
||||||
state.overview.stats = stats
|
state.overview.stats = stats
|
||||||
state.champions.championsLoaded = false
|
state.champions.championsLoaded = false
|
||||||
state.records.recordsLoaded = false
|
state.records.recordsLoaded = false
|
||||||
},
|
},
|
||||||
OVERVIEW_FOUND(state, infos) {
|
OVERVIEW_FOUND(state, infos) {
|
||||||
|
state.basic.recentActivity = infos.stats.recentActivity
|
||||||
state.overview.matches = infos.matches
|
state.overview.matches = infos.matches
|
||||||
state.overview.matchIndex = infos.matches.length
|
state.overview.matchIndex = infos.matches.length
|
||||||
state.overview.stats = infos.stats
|
state.overview.stats = infos.stats
|
||||||
state.overview.loaded = true
|
state.overview.loaded = true
|
||||||
|
state.records.recordsLoaded = false
|
||||||
},
|
},
|
||||||
RECORDS_FOUND(state, { records }) {
|
RECORDS_FOUND(state, { records }) {
|
||||||
state.records.list = records
|
state.records.list = records
|
||||||
|
|
@ -87,6 +91,7 @@ export const mutations = {
|
||||||
state.basic.account = infos.account
|
state.basic.account = infos.account
|
||||||
state.basic.matchList = infos.matchList
|
state.basic.matchList = infos.matchList
|
||||||
state.basic.ranked = infos.ranked
|
state.basic.ranked = infos.ranked
|
||||||
|
state.basic.recentActivity = infos.recentActivity
|
||||||
state.basic.seasons = infos.seasons.sort((a, b) => b - a)
|
state.basic.seasons = infos.seasons.sort((a, b) => b - a)
|
||||||
state.basic.status = 'found'
|
state.basic.status = 'found'
|
||||||
state.live.match = infos.current
|
state.live.match = infos.current
|
||||||
|
|
@ -178,17 +183,15 @@ export const actions = {
|
||||||
async moreMatches({ commit, getters, rootState }) {
|
async moreMatches({ commit, getters, rootState }) {
|
||||||
commit('MATCHES_LOADING')
|
commit('MATCHES_LOADING')
|
||||||
|
|
||||||
const gameIds = getters.filteredMatchList
|
const matchIds = getters.filteredMatchList
|
||||||
.slice(state.overview.matchIndex, state.overview.matchIndex + 10)
|
.slice(state.overview.matchIndex, state.overview.matchIndex + 10)
|
||||||
.map(({ gameId }) => gameId)
|
|
||||||
|
|
||||||
const resp = await axios(({
|
const resp = await axios(({
|
||||||
url: 'match',
|
url: 'match',
|
||||||
data: {
|
data: {
|
||||||
puuid: state.basic.account.puuid,
|
puuid: state.basic.account.puuid,
|
||||||
accountId: state.basic.account.accountId,
|
|
||||||
region: rootState.regionsList[rootState.settings.region],
|
region: rootState.regionsList[rootState.settings.region],
|
||||||
gameIds
|
matchIds
|
||||||
},
|
},
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
})).catch(() => { })
|
})).catch(() => { })
|
||||||
|
|
@ -216,7 +219,7 @@ export const actions = {
|
||||||
const resp = await axios(({ url: 'summoner/records', data: { puuid: state.basic.account.puuid }, method: 'POST' })).catch(() => { })
|
const resp = await axios(({ url: 'summoner/records', data: { puuid: state.basic.account.puuid }, method: 'POST' })).catch(() => { })
|
||||||
console.log('---RECORDS---')
|
console.log('---RECORDS---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
const records = resp.data ? createRecordsData(resp.data) : {}
|
const records = resp.data.length ? createRecordsData(resp.data) : {}
|
||||||
|
|
||||||
commit('RECORDS_FOUND', { records })
|
commit('RECORDS_FOUND', { records })
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ export default {
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
|
|
||||||
|
this.getRunes()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -67,6 +69,7 @@ export default {
|
||||||
this.liveMatchRequest()
|
this.liveMatchRequest()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
...mapActions('cdragon', ['getRunes']),
|
||||||
...mapActions('summoner', ['liveMatchRequest']),
|
...mapActions('summoner', ['liveMatchRequest']),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div key="records">
|
<div key="records">
|
||||||
<template v-if="!recordsLoaded || (recordsLoaded && records.maxKda)">
|
<template v-if="!recordsLoaded || (recordsLoaded && records.assists)">
|
||||||
<div
|
<div
|
||||||
class="relative pl-6 text-2xl text-blue-200 border-b-2 border-blue-800 category blue-900"
|
class="relative pl-6 text-2xl text-blue-200 border-b-2 border-blue-800 category blue-900"
|
||||||
>Basics</div>
|
>Basics</div>
|
||||||
|
|
@ -10,48 +10,42 @@
|
||||||
color="#63b3ed"
|
color="#63b3ed"
|
||||||
text-color="text-blue-400"
|
text-color="text-blue-400"
|
||||||
border-color="border-blue-400"
|
border-color="border-blue-400"
|
||||||
property="kda"
|
:record="records.kda"
|
||||||
:record="records.maxKda"
|
|
||||||
title="KDA"
|
title="KDA"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#68D391"
|
color="#68D391"
|
||||||
text-color="text-green-400"
|
text-color="text-green-400"
|
||||||
border-color="border-green-400"
|
border-color="border-green-400"
|
||||||
property="kills"
|
:record="records.kills"
|
||||||
:record="records.maxKills"
|
|
||||||
title="Kills"
|
title="Kills"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#9F7AEA"
|
color="#9F7AEA"
|
||||||
text-color="text-purple-500"
|
text-color="text-purple-500"
|
||||||
border-color="border-purple-500"
|
border-color="border-purple-500"
|
||||||
property="assists"
|
:record="records.assists"
|
||||||
:record="records.maxAssists"
|
|
||||||
title="Assists"
|
title="Assists"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#F56565"
|
color="#F56565"
|
||||||
text-color="text-red-500"
|
text-color="text-red-500"
|
||||||
border-color="border-red-500"
|
border-color="border-red-500"
|
||||||
property="deaths"
|
:record="records.deaths"
|
||||||
:record="records.maxDeaths"
|
|
||||||
title="Deaths"
|
title="Deaths"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-600"
|
text-color="text-yellow-600"
|
||||||
border-color="border-yellow-600"
|
border-color="border-yellow-600"
|
||||||
property="gold"
|
:record="records.gold"
|
||||||
:record="records.maxGold"
|
|
||||||
title="Gold earned"
|
title="Gold earned"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#81E6D9"
|
color="#81E6D9"
|
||||||
text-color="text-teal-300"
|
text-color="text-teal-300"
|
||||||
border-color="border-teal-300"
|
border-color="border-teal-300"
|
||||||
property="minions"
|
:record="records.minions"
|
||||||
:record="records.maxMinions"
|
|
||||||
title="Minions killed"
|
title="Minions killed"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -81,24 +75,21 @@
|
||||||
color="#FC8181"
|
color="#FC8181"
|
||||||
text-color="text-red-400"
|
text-color="text-red-400"
|
||||||
border-color="border-red-400"
|
border-color="border-red-400"
|
||||||
property="dmgChamp"
|
:record="records.damage_dealt_champions"
|
||||||
:record="records.maxDmgChamp"
|
|
||||||
title="Damage champions"
|
title="Damage champions"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-400"
|
text-color="text-yellow-400"
|
||||||
border-color="border-yellow-400"
|
border-color="border-yellow-400"
|
||||||
property="dmgObj"
|
:record="records.damage_dealt_objectives"
|
||||||
:record="records.maxDmgObj"
|
|
||||||
title="Damage objectives"
|
title="Damage objectives"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#FC8181"
|
color="#FC8181"
|
||||||
text-color="text-red-400"
|
text-color="text-red-400"
|
||||||
border-color="border-red-400"
|
border-color="border-red-400"
|
||||||
property="dmgTaken"
|
:record="records.damage_taken"
|
||||||
:record="records.maxDmgTaken"
|
|
||||||
title="Damage taken"
|
title="Damage taken"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
|
|
@ -106,24 +97,21 @@
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-400"
|
text-color="text-yellow-400"
|
||||||
border-color="border-yellow-400"
|
border-color="border-yellow-400"
|
||||||
property="towers"
|
:record="records.turret_kills"
|
||||||
:record="records.maxTowers"
|
|
||||||
title="Towers"
|
title="Towers"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#68D391"
|
color="#68D391"
|
||||||
text-color="text-green-400"
|
text-color="text-green-400"
|
||||||
border-color="border-green-400"
|
border-color="border-green-400"
|
||||||
property="kp"
|
:record="records.kp"
|
||||||
:record="records.maxKp"
|
|
||||||
title="Kill participation"
|
title="Kill participation"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-400"
|
text-color="text-yellow-400"
|
||||||
border-color="border-yellow-400"
|
border-color="border-yellow-400"
|
||||||
property="vision"
|
:record="records.vision_score"
|
||||||
:record="records.maxVision"
|
|
||||||
title="Vision score"
|
title="Vision score"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -153,35 +141,28 @@
|
||||||
color="#4299E1"
|
color="#4299E1"
|
||||||
text-color="text-blue-500"
|
text-color="text-blue-500"
|
||||||
border-color="border-blue-500"
|
border-color="border-blue-500"
|
||||||
property="time"
|
:record="records.game_duration"
|
||||||
:record="records.maxTime"
|
|
||||||
title="Longest game"
|
title="Longest game"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
v-if="records.maxLiving"
|
|
||||||
color="#4299E1"
|
color="#4299E1"
|
||||||
text-color="text-blue-500"
|
text-color="text-blue-500"
|
||||||
border-color="border-blue-500"
|
border-color="border-blue-500"
|
||||||
property="longestLiving"
|
:record="records.time_spent_living"
|
||||||
:record="records.maxLiving"
|
|
||||||
title="Longest living"
|
title="Longest living"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
v-if="records.maxCriticalStrike"
|
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-400"
|
text-color="text-yellow-400"
|
||||||
border-color="border-yellow-400"
|
border-color="border-yellow-400"
|
||||||
property="criticalStrike"
|
:record="records.critical_strike"
|
||||||
:record="records.maxCriticalStrike"
|
|
||||||
title="Critical Strike"
|
title="Critical Strike"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
v-if="records.maxHeal"
|
|
||||||
color="#68D391"
|
color="#68D391"
|
||||||
text-color="text-green-400"
|
text-color="text-green-400"
|
||||||
border-color="border-green-400"
|
border-color="border-green-400"
|
||||||
property="heal"
|
:record="records.heal"
|
||||||
:record="records.maxHeal"
|
|
||||||
title="Heal"
|
title="Heal"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -204,47 +185,42 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="records.maxDouble" class="relative pl-6 mt-3 text-2xl text-blue-200 border-b-2 border-blue-800 category">Multi kills</div>
|
<div class="relative pl-6 mt-3 text-2xl text-blue-200 border-b-2 border-blue-800 category">Multi kills</div>
|
||||||
<div v-if="records.maxDouble" class="flex flex-wrap -mx-2">
|
<div class="flex flex-wrap -mx-2">
|
||||||
<template v-if="recordsLoaded">
|
<template v-if="recordsLoaded">
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#FEFCBF"
|
color="#FEFCBF"
|
||||||
text-color="text-yellow-200"
|
text-color="text-yellow-200"
|
||||||
border-color="border-yellow-200"
|
border-color="border-yellow-200"
|
||||||
property="doubleKills"
|
:record="records.double_kills"
|
||||||
:record="records.maxDouble"
|
|
||||||
title="Double kills"
|
title="Double kills"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#F6E05E"
|
color="#F6E05E"
|
||||||
text-color="text-yellow-400"
|
text-color="text-yellow-400"
|
||||||
border-color="border-yellow-400"
|
border-color="border-yellow-400"
|
||||||
property="tripleKills"
|
:record="records.triple_kills"
|
||||||
:record="records.maxTriple"
|
|
||||||
title="Triple kills"
|
title="Triple kills"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#D69E2E"
|
color="#D69E2E"
|
||||||
text-color="text-yellow-600"
|
text-color="text-yellow-600"
|
||||||
border-color="border-yellow-600"
|
border-color="border-yellow-600"
|
||||||
property="quadraKills"
|
:record="records.quadra_kills"
|
||||||
:record="records.maxQuadra"
|
|
||||||
title="Quadra kills"
|
title="Quadra kills"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#F56565"
|
color="#F56565"
|
||||||
text-color="text-red-500"
|
text-color="text-red-500"
|
||||||
border-color="border-red-500"
|
border-color="border-red-500"
|
||||||
property="pentaKills"
|
:record="records.penta_kills"
|
||||||
:record="records.maxPenta"
|
|
||||||
title="Penta kills"
|
title="Penta kills"
|
||||||
/>
|
/>
|
||||||
<RecordCard
|
<RecordCard
|
||||||
color="#63b3ed"
|
color="#63b3ed"
|
||||||
text-color="text-blue-400"
|
text-color="text-blue-400"
|
||||||
border-color="border-blue-400"
|
border-color="border-blue-400"
|
||||||
property="killingSpree"
|
:record="records.killing_spree"
|
||||||
:record="records.maxKillingSpree"
|
|
||||||
title="Killing Spree"
|
title="Killing Spree"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -268,7 +244,7 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="recordsLoaded && !records.maxKda">
|
<template v-if="recordsLoaded && !records.assists">
|
||||||
<div class="flex flex-col items-center mt-4">
|
<div class="flex flex-col items-center mt-4">
|
||||||
<div>No records have been found.</div>
|
<div>No records have been found.</div>
|
||||||
<div>😕</div>
|
<div>😕</div>
|
||||||
|
|
|
||||||
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
leaguestats-redis:
|
||||||
|
container_name: leaguestats-redis
|
||||||
|
image: redis:6-alpine
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:6379:6379'
|
||||||
|
volumes:
|
||||||
|
- leaguestats-redisData:/data
|
||||||
|
restart: always
|
||||||
|
leaguestats-postgres:
|
||||||
|
container_name: leaguestats-postgres
|
||||||
|
image: postgres:12-alpine
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:5432:5432'
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=leaguestats
|
||||||
|
- POSTGRES_USER=root
|
||||||
|
- POSTGRES_PASSWORD=root
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
volumes:
|
||||||
|
- leaguestats-postgresData:/var/lib/postgresql/data
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
leaguestats-redisData:
|
||||||
|
leaguestats-postgresData:
|
||||||
|
|
@ -2,28 +2,36 @@
|
||||||
"typescript": true,
|
"typescript": true,
|
||||||
"commands": [
|
"commands": [
|
||||||
"./commands",
|
"./commands",
|
||||||
"@adonisjs/core/build/commands",
|
"@adonisjs/core/build/commands/index.js",
|
||||||
"@zakodium/adonis-mongodb/lib/commands"
|
"@adonisjs/repl/build/commands",
|
||||||
|
"@adonisjs/lucid/build/commands"
|
||||||
],
|
],
|
||||||
"exceptionHandlerNamespace": "App/Exceptions/Handler",
|
"exceptionHandlerNamespace": "App/Exceptions/Handler",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"App": "app",
|
"App": "app",
|
||||||
"Contracts": "contracts",
|
|
||||||
"Config": "config",
|
"Config": "config",
|
||||||
"Database": "database"
|
"Database": "database",
|
||||||
|
"Contracts": "contracts"
|
||||||
},
|
},
|
||||||
"preloads": [
|
"preloads": [
|
||||||
"./start/routes",
|
"./start/routes",
|
||||||
"./start/kernel"
|
"./start/kernel",
|
||||||
|
{
|
||||||
|
"file": "./start/events",
|
||||||
|
"environment": [
|
||||||
|
"console",
|
||||||
|
"repl",
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"providers": [
|
"providers": [
|
||||||
"./providers/AppProvider",
|
"./providers/AppProvider",
|
||||||
"@adonisjs/core",
|
"@adonisjs/core",
|
||||||
"@zakodium/adonis-mongodb",
|
"@adonisjs/lucid",
|
||||||
"@adonisjs/redis"
|
"@adonisjs/redis"
|
||||||
],
|
],
|
||||||
"metaFiles": [
|
"aceProviders": [
|
||||||
".env",
|
"@adonisjs/repl"
|
||||||
".adonisrc.json"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@ PORT=3333
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
DRIVE_DISK=local
|
||||||
|
|
||||||
MONGODB_URL=mongodb://localhost:27017
|
DB_CONNECTION=pg
|
||||||
MONGODB_DATABASE=leaguestats
|
PG_HOST=localhost
|
||||||
|
PG_PORT=5432
|
||||||
|
PG_USER=
|
||||||
|
PG_PASSWORD=
|
||||||
|
PG_DB_NAME=
|
||||||
|
|
||||||
REDIS_CONNECTION=local
|
REDIS_CONNECTION=local
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
RIOT_API_KEY=
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["plugin:adonis/typescriptApp", "prettier"],
|
||||||
"plugin:adonis/typescriptApp"
|
"plugins": ["prettier"],
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"max-len": [
|
"prettier/prettier": ["error"]
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"code": 130
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
server/.prettierignore
Normal file
1
server/.prettierignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build
|
||||||
11
server/.prettierrc
Normal file
11
server/.prettierrc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"useTabs": false,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"printWidth": 100,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,14 @@
|
||||||
| Ace Commands
|
| Ace Commands
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This file is the entry point for running ace commands. For typescript
|
| This file is the entry point for running ace commands.
|
||||||
| projects, the ace commands will fallback to the compiled code and
|
|
||||||
| hence this file has to be executable by node directly.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require('reflect-metadata')
|
require('reflect-metadata')
|
||||||
require('source-map-support').install({ handleUncaughtExceptions: false })
|
require('source-map-support').install({ handleUncaughtExceptions: false })
|
||||||
|
|
||||||
const { Ignitor } = require('@adonisjs/core/build/src/Ignitor')
|
const { Ignitor } = require('@adonisjs/core/build/standalone')
|
||||||
new Ignitor(__dirname)
|
new Ignitor(__dirname)
|
||||||
.ace()
|
.ace()
|
||||||
.handle(process.argv.slice(2))
|
.handle(process.argv.slice(2))
|
||||||
.catch(console.error)
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,294 @@
|
||||||
{"dump:rcfile":{"settings":{},"commandPath":"@adonisjs/core/build/commands/DumpRc","commandName":"dump:rcfile","description":"Dump contents of .adonisrc.json file along with defaults","args":[],"flags":[]},"list:routes":{"settings":{"loadApp":true},"commandPath":"@adonisjs/core/build/commands/ListRoutes","commandName":"list:routes","description":"List application routes","args":[],"flags":[{"name":"json","propertyName":"json","type":"boolean","description":"Output as JSON"}]},"generate:key":{"settings":{},"commandPath":"@adonisjs/core/build/commands/GenerateKey","commandName":"generate:key","description":"Generate a new APP_KEY secret","args":[],"flags":[]},"mongodb:make:migration":{"settings":{"loadApp":true},"commandPath":"@zakodium/adonis-mongodb/lib/commands/MongodbMakeMigration","commandName":"mongodb:make:migration","description":"Make a new migration file","args":[{"type":"string","propertyName":"name","name":"name","required":true,"description":"Name of the migration file"}],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Database connection to use for the migration"}]},"mongodb:migration:run":{"settings":{"loadApp":true},"commandPath":"@zakodium/adonis-mongodb/lib/commands/MongodbMigrate","commandName":"mongodb:migration:run","description":"Execute pending migrations","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Database connection to use for the migration"}]},"mongodb:migration:status":{"settings":{"loadApp":true},"commandPath":"@zakodium/adonis-mongodb/lib/commands/MongodbListMigrations","commandName":"mongodb:migration:status","description":"Show pending migrations","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Database connection to use for the migration"}]}}
|
{
|
||||||
|
"commands": {
|
||||||
|
"load:v4": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true,
|
||||||
|
"stayAlive": false
|
||||||
|
},
|
||||||
|
"commandPath": "./commands/LoadV4Matches",
|
||||||
|
"commandName": "load:v4",
|
||||||
|
"description": "Load matches for a given Summoner from the old Match-V4 endpoint",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "summoner",
|
||||||
|
"name": "summoner",
|
||||||
|
"required": true,
|
||||||
|
"description": "Summoner name to seach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "region",
|
||||||
|
"name": "region",
|
||||||
|
"required": true,
|
||||||
|
"description": "League region of the summoner"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"dump:rcfile": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/DumpRc",
|
||||||
|
"commandName": "dump:rcfile",
|
||||||
|
"description": "Dump contents of .adonisrc.json file along with defaults",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"list:routes": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/ListRoutes",
|
||||||
|
"commandName": "list:routes",
|
||||||
|
"description": "List application routes",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "json",
|
||||||
|
"propertyName": "json",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Output as JSON"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"generate:key": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/core/build/commands/GenerateKey",
|
||||||
|
"commandName": "generate:key",
|
||||||
|
"description": "Generate a new APP_KEY secret",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"repl": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true,
|
||||||
|
"environment": "repl",
|
||||||
|
"stayAlive": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/repl/build/commands/AdonisRepl",
|
||||||
|
"commandName": "repl",
|
||||||
|
"description": "Start a new REPL session",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"db:seed": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/DbSeed",
|
||||||
|
"commandName": "db:seed",
|
||||||
|
"description": "Execute database seeder files",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection for the seeders",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "interactive",
|
||||||
|
"propertyName": "interactive",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Run seeders in interactive mode",
|
||||||
|
"alias": "i"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "files",
|
||||||
|
"propertyName": "files",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Define a custom set of seeders files names to run",
|
||||||
|
"alias": "f"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:model": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeModel",
|
||||||
|
"commandName": "make:model",
|
||||||
|
"description": "Make a new Lucid model",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the model class"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "migration",
|
||||||
|
"propertyName": "migration",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "m",
|
||||||
|
"description": "Generate the migration for the model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "controller",
|
||||||
|
"propertyName": "controller",
|
||||||
|
"type": "boolean",
|
||||||
|
"alias": "c",
|
||||||
|
"description": "Generate the controller for the model"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:migration": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeMigration",
|
||||||
|
"commandName": "make:migration",
|
||||||
|
"description": "Make a new migration file",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the migration file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "The connection flag is used to lookup the directory for the migration file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder",
|
||||||
|
"propertyName": "folder",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Pre-select a migration directory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create",
|
||||||
|
"propertyName": "create",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define the table name for creating a new table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "table",
|
||||||
|
"propertyName": "table",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define the table name for altering an existing table"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"make:seeder": {
|
||||||
|
"settings": {},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/MakeSeeder",
|
||||||
|
"commandName": "make:seeder",
|
||||||
|
"description": "Make a new Seeder file",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"propertyName": "name",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"description": "Name of the seeder class"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": []
|
||||||
|
},
|
||||||
|
"migration:run": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Run",
|
||||||
|
"commandName": "migration:run",
|
||||||
|
"description": "Run pending migrations",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explicitly force to run migrations in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Print SQL queries, instead of running the migrations"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:rollback": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Rollback",
|
||||||
|
"commandName": "migration:rollback",
|
||||||
|
"description": "Rollback migrations to a given batch number",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "force",
|
||||||
|
"propertyName": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Explictly force to run migrations in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dry-run",
|
||||||
|
"propertyName": "dryRun",
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Print SQL queries, instead of running the migrations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "batch",
|
||||||
|
"propertyName": "batch",
|
||||||
|
"type": "number",
|
||||||
|
"description": "Define custom batch number for rollback. Use 0 to rollback to initial state"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migration:status": {
|
||||||
|
"settings": {
|
||||||
|
"loadApp": true
|
||||||
|
},
|
||||||
|
"commandPath": "@adonisjs/lucid/build/commands/Migration/Status",
|
||||||
|
"commandName": "migration:status",
|
||||||
|
"description": "Check migrations current status.",
|
||||||
|
"args": [],
|
||||||
|
"aliases": [],
|
||||||
|
"flags": [
|
||||||
|
{
|
||||||
|
"name": "connection",
|
||||||
|
"propertyName": "connection",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Define a custom database connection",
|
||||||
|
"alias": "c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aliases": {}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import Redis from '@ioc:Adonis/Addons/Redis'
|
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import RuneSerializer from 'App/Serializers/RuneSerializer'
|
||||||
import Jax from 'App/Services/Jax'
|
import Jax from 'App/Services/Jax'
|
||||||
import RuneTransformer from 'App/Transformers/RuneTransformer'
|
|
||||||
|
|
||||||
export default class CDragonController {
|
export default class CDragonController {
|
||||||
public async runes ({ response }: HttpContextContract) {
|
public async runes({ response }: HttpContextContract) {
|
||||||
const cacheUrl = 'cdragon-runes'
|
const cacheUrl = 'cdragon-runes'
|
||||||
|
|
||||||
const requestCached = await Redis.get(cacheUrl)
|
const requestCached = await Redis.get(cacheUrl)
|
||||||
|
|
@ -16,8 +16,8 @@ export default class CDragonController {
|
||||||
const perkstyles = await Jax.CDragon.perkstyles()
|
const perkstyles = await Jax.CDragon.perkstyles()
|
||||||
|
|
||||||
const runesData = {
|
const runesData = {
|
||||||
perks: RuneTransformer.transformPerks(perks),
|
perks: RuneSerializer.serializePerks(perks),
|
||||||
perkstyles: RuneTransformer.transformStyles(perkstyles.styles),
|
perkstyles: RuneSerializer.serializeStyles(perkstyles.styles),
|
||||||
}
|
}
|
||||||
|
|
||||||
await Redis.set(cacheUrl, JSON.stringify(runesData), 'EX', 36000)
|
await Redis.set(cacheUrl, JSON.stringify(runesData), 'EX', 36000)
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,23 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
import DetailedMatch, { DetailedMatchModel } from 'App/Models/DetailedMatch'
|
import Match from 'App/Models/Match'
|
||||||
import { ParticipantDetails } from 'App/Models/Match'
|
import MatchPlayerRankParser from 'App/Parsers/MatchPlayerRankParser'
|
||||||
import Summoner from 'App/Models/Summoner'
|
import DetailedMatchSerializer from 'App/Serializers/DetailedMatchSerializer'
|
||||||
import Jax from 'App/Services/Jax'
|
import MatchPlayerRankSerializer from 'App/Serializers/MatchPlayerRankSerializer'
|
||||||
import MatchService from 'App/Services/MatchService'
|
import MatchService from 'App/Services/MatchService'
|
||||||
import StatsService from 'App/Services/StatsService'
|
import StatsService from 'App/Services/StatsService'
|
||||||
import SummonerService from 'App/Services/SummonerService'
|
|
||||||
import DetailedMatchTransformer from 'App/Transformers/DetailedMatchTransformer'
|
|
||||||
import DetailedMatchValidator from 'App/Validators/DetailedMatchValidator'
|
import DetailedMatchValidator from 'App/Validators/DetailedMatchValidator'
|
||||||
import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
|
import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
|
||||||
|
|
||||||
export default class MatchesController {
|
export default class MatchesController {
|
||||||
/**
|
/**
|
||||||
* Get the soloQ rank of all the players of the team
|
* POST - Return data from matches searched by matchIds
|
||||||
* @param summoner all the data of the summoner
|
|
||||||
* @param region of the match
|
|
||||||
*/
|
|
||||||
private async getPlayerRank (summoner: ParticipantDetails, region: string) {
|
|
||||||
const account = await Jax.Summoner.summonerId(summoner.summonerId, region)
|
|
||||||
if (account) {
|
|
||||||
const ranked = await SummonerService.getRanked(account, region)
|
|
||||||
summoner.rank = ranked.soloQ ? (({ tier, shortName }) => ({ tier, shortName }))(ranked.soloQ) : null
|
|
||||||
} else {
|
|
||||||
summoner.rank = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return summoner
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST - Return data from matches searched by gameIds
|
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async index ({ request, response }: HttpContextContract) {
|
public async index({ request, response }: HttpContextContract) {
|
||||||
console.log('More Matches Request')
|
const { puuid, region, matchIds, season } = await request.validate(MatchesIndexValidator)
|
||||||
const { puuid, accountId, region, gameIds, season } = await request.validate(MatchesIndexValidator)
|
const matches = await MatchService.getMatches(region, matchIds, puuid)
|
||||||
|
|
||||||
const summonerDB = await Summoner.findOne({ puuid })
|
|
||||||
if (!summonerDB) {
|
|
||||||
return response.json(null)
|
|
||||||
}
|
|
||||||
const matches = await MatchService.getMatches(puuid, accountId, region, gameIds, summonerDB)
|
|
||||||
|
|
||||||
await summonerDB.save()
|
|
||||||
|
|
||||||
const stats = await StatsService.getSummonerStats(puuid, season)
|
const stats = await StatsService.getSummonerStats(puuid, season)
|
||||||
|
|
||||||
return response.json({
|
return response.json({
|
||||||
matches,
|
matches,
|
||||||
stats,
|
stats,
|
||||||
|
|
@ -56,26 +28,25 @@ export default class MatchesController {
|
||||||
* POST - Return details data for one specific match
|
* POST - Return details data for one specific match
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async show ({ request, response }: HttpContextContract) {
|
public async show({ request, response }: HttpContextContract) {
|
||||||
console.time('MatchDetails')
|
console.time('MatchDetails')
|
||||||
const { gameId, region } = await request.validate(DetailedMatchValidator)
|
const { matchId } = await request.validate(DetailedMatchValidator)
|
||||||
|
|
||||||
let matchDetails: DetailedMatchModel
|
const match = await Match.query()
|
||||||
const alreadySaved = await DetailedMatch.findOne({ gameId, region })
|
.where('id', matchId)
|
||||||
|
.preload('teams')
|
||||||
|
.preload('players', (playersQuery) => {
|
||||||
|
playersQuery.preload('ranks')
|
||||||
|
})
|
||||||
|
.firstOrFail()
|
||||||
|
|
||||||
if (alreadySaved) {
|
const { match: matchDetails, ranksLoaded } = DetailedMatchSerializer.serializeOneMatch(match)
|
||||||
console.log('MATCH DETAILS ALREADY SAVED')
|
|
||||||
matchDetails = alreadySaved
|
|
||||||
} else {
|
|
||||||
const match = await Jax.Match.get(gameId, region)
|
|
||||||
matchDetails = await DetailedMatchTransformer.transform(match)
|
|
||||||
await DetailedMatch.create(matchDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.timeEnd('MatchDetails')
|
console.timeEnd('MatchDetails')
|
||||||
|
|
||||||
return response.json({
|
return response.json({
|
||||||
matchDetails,
|
matchDetails,
|
||||||
|
ranksLoaded,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,28 +54,14 @@ export default class MatchesController {
|
||||||
* POST - Return ranks of players for a specific game
|
* POST - Return ranks of players for a specific game
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async showRanks ({ request, response }: HttpContextContract) {
|
public async showRanks({ request, response }: HttpContextContract) {
|
||||||
console.time('Ranks')
|
console.time('Ranks')
|
||||||
const { gameId, region } = await request.validate(DetailedMatchValidator)
|
const { matchId } = await request.validate(DetailedMatchValidator)
|
||||||
|
const match = await Match.query().where('id', matchId).preload('players').firstOrFail()
|
||||||
|
const parsedRanks = await MatchPlayerRankParser.parse(match)
|
||||||
|
const serializedRanks = MatchPlayerRankSerializer.serialize(parsedRanks)
|
||||||
|
|
||||||
let matchDetails = await DetailedMatch.findOne({ gameId, region })
|
|
||||||
|
|
||||||
if (!matchDetails) {
|
|
||||||
return response.json(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestsBlue = matchDetails.blueTeam.players.map(p => this.getPlayerRank(p, region))
|
|
||||||
matchDetails.blueTeam.players = await Promise.all(requestsBlue)
|
|
||||||
|
|
||||||
const requestsRed = matchDetails.redTeam.players.map(p => this.getPlayerRank(p, region))
|
|
||||||
matchDetails.redTeam.players = await Promise.all(requestsRed)
|
|
||||||
|
|
||||||
matchDetails.save()
|
|
||||||
console.timeEnd('Ranks')
|
console.timeEnd('Ranks')
|
||||||
|
return response.json(serializedRanks)
|
||||||
return response.json({
|
|
||||||
blueTeam: matchDetails.blueTeam.players,
|
|
||||||
redTeam: matchDetails.redTeam.players,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { getCurrentSeason } from 'App/helpers'
|
||||||
import Summoner from 'App/Models/Summoner'
|
import Summoner from 'App/Models/Summoner'
|
||||||
import MatchRepository from 'App/Repositories/MatchRepository'
|
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||||
|
import BasicMatchSerializer from 'App/Serializers/BasicMatchSerializer'
|
||||||
|
import LiveMatchSerializer from 'App/Serializers/LiveMatchSerializer'
|
||||||
import Jax from 'App/Services/Jax'
|
import Jax from 'App/Services/Jax'
|
||||||
import MatchService from 'App/Services/MatchService'
|
import MatchService from 'App/Services/MatchService'
|
||||||
import StatsService from 'App/Services/StatsService'
|
import StatsService from 'App/Services/StatsService'
|
||||||
import SummonerService from 'App/Services/SummonerService'
|
import SummonerService from 'App/Services/SummonerService'
|
||||||
import LiveMatchTransformer from 'App/Transformers/LiveMatchTransformer'
|
|
||||||
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
|
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
|
||||||
import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator'
|
import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator'
|
||||||
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
|
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
|
||||||
|
|
@ -13,21 +15,8 @@ import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
|
||||||
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
|
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
|
||||||
|
|
||||||
export default class SummonersController {
|
export default class SummonersController {
|
||||||
/**
|
public async basic({ request, response }: HttpContextContract) {
|
||||||
* Get all played seasons for a summoner
|
console.time('BASIC_REQUEST')
|
||||||
* @param puuid of the summoner
|
|
||||||
*/
|
|
||||||
private async getSeasons (puuid: string): Promise<number[]> {
|
|
||||||
const 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')
|
|
||||||
const { summoner, region } = await request.validate(SummonerBasicValidator)
|
const { summoner, region } = await request.validate(SummonerBasicValidator)
|
||||||
const finalJSON: any = {}
|
const finalJSON: any = {}
|
||||||
|
|
||||||
|
|
@ -37,24 +26,20 @@ export default class SummonersController {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return response.json(null)
|
return response.json(null)
|
||||||
}
|
}
|
||||||
account.region = region
|
|
||||||
finalJSON.account = account
|
finalJSON.account = account
|
||||||
|
|
||||||
// Summoner in DB
|
// Summoner in DB
|
||||||
let summonerDB = await Summoner.findOne({ puuid: account.puuid })
|
const summonerDB = await Summoner.firstOrCreate({ puuid: account.puuid })
|
||||||
if (!summonerDB) {
|
|
||||||
summonerDB = await Summoner.create({ puuid: account.puuid })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summoner names
|
// Summoner names
|
||||||
finalJSON.account.names = SummonerService.getAllSummonerNames(account, summonerDB)
|
finalJSON.account.names = await SummonerService.getAllSummonerNames(account, summonerDB)
|
||||||
|
|
||||||
// MATCH LIST
|
// MATCH LIST
|
||||||
await MatchService.updateMatchList(account, summonerDB)
|
finalJSON.matchList = await MatchService.updateMatchList(account, region, summonerDB)
|
||||||
finalJSON.matchList = summonerDB.matchList
|
|
||||||
|
|
||||||
// All seasons the summoner has played
|
// All seasons the summoner has played
|
||||||
finalJSON.seasons = await this.getSeasons(account.puuid)
|
// TODO: check if there is a way to do that with V5...
|
||||||
|
finalJSON.seasons = [getCurrentSeason()]
|
||||||
|
|
||||||
// CURRENT GAME
|
// CURRENT GAME
|
||||||
const currentGame = await Jax.Spectator.summonerID(account.id, region)
|
const currentGame = await Jax.Spectator.summonerID(account.id, region)
|
||||||
|
|
@ -62,53 +47,44 @@ export default class SummonersController {
|
||||||
finalJSON.current = currentGame
|
finalJSON.current = currentGame
|
||||||
|
|
||||||
// RANKED STATS
|
// RANKED STATS
|
||||||
finalJSON.ranked = await SummonerService.getRanked(account, region)
|
finalJSON.ranked = await SummonerService.getRanked(account.id, region)
|
||||||
|
|
||||||
// SAVE IN DB
|
// RECENT ACTIVITY
|
||||||
await summonerDB.save()
|
finalJSON.recentActivity = await StatsService.getRecentActivity(account.puuid)
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.log('username not found')
|
console.log(e)
|
||||||
console.log(error)
|
console.timeEnd('BASIC_REQUEST')
|
||||||
return response.json(null)
|
return response.json(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.timeEnd('all')
|
console.timeEnd('BASIC_REQUEST')
|
||||||
return response.json(finalJSON)
|
return response.json(finalJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async overview({ request, response }: HttpContextContract) {
|
||||||
* POST: get overview view summoner data
|
console.time('OVERVIEW_REQUEST')
|
||||||
* @param ctx
|
const { puuid, region, season } = await request.validate(SummonerOverviewValidator)
|
||||||
*/
|
|
||||||
public async overview ({ request, response }: HttpContextContract) {
|
|
||||||
console.time('overview')
|
|
||||||
const { puuid, accountId, region, season } = await request.validate(SummonerOverviewValidator)
|
|
||||||
const finalJSON: any = {}
|
const finalJSON: any = {}
|
||||||
|
|
||||||
// Summoner in DB
|
// Summoner in DB
|
||||||
let summonerDB = await Summoner.findOne({ puuid: puuid })
|
const summonerDB = await Summoner.firstOrCreate({ puuid: puuid })
|
||||||
if (!summonerDB) {
|
|
||||||
summonerDB = await Summoner.create({ puuid: puuid })
|
|
||||||
}
|
|
||||||
|
|
||||||
// MATCHES BASIC
|
// MATCHES BASIC
|
||||||
const gameIds = summonerDB.matchList!.slice(0)
|
const matchlist = await summonerDB
|
||||||
.filter(m => {
|
.related('matchList')
|
||||||
return season ? m.seasonMatch === season : true
|
.query()
|
||||||
})
|
.select('matchId')
|
||||||
.slice(0, 10)
|
.orderBy('matchId', 'desc')
|
||||||
.map(({ gameId }) => gameId)
|
.limit(10)
|
||||||
finalJSON.matchesDetails = await MatchService.getMatches(puuid, accountId, region, gameIds, summonerDB)
|
const matchIds = matchlist.map((m) => m.matchId)
|
||||||
|
|
||||||
|
finalJSON.matchesDetails = await MatchService.getMatches(region, matchIds, puuid)
|
||||||
|
|
||||||
// STATS
|
|
||||||
console.time('STATS')
|
console.time('STATS')
|
||||||
finalJSON.stats = await StatsService.getSummonerStats(puuid, season)
|
finalJSON.stats = await StatsService.getSummonerStats(puuid, season)
|
||||||
console.timeEnd('STATS')
|
console.timeEnd('STATS')
|
||||||
|
|
||||||
// SAVE IN DB
|
console.timeEnd('OVERVIEW_REQUEST')
|
||||||
await summonerDB.save()
|
|
||||||
|
|
||||||
console.timeEnd('overview')
|
|
||||||
return response.json(finalJSON)
|
return response.json(finalJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,44 +92,57 @@ export default class SummonersController {
|
||||||
* POST: get champions view summoner data
|
* POST: get champions view summoner data
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async champions ({ request, response }: HttpContextContract) {
|
public async champions({ request, response }: HttpContextContract) {
|
||||||
console.time('championsRequest')
|
console.time('championsRequest')
|
||||||
const { puuid, queue, season } = await request.validate(SummonerChampionValidator)
|
const { puuid, queue, season } = await request.validate(SummonerChampionValidator)
|
||||||
const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
|
const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
|
||||||
|
const championStatsSerialized = championStats.map((champion) => {
|
||||||
|
return {
|
||||||
|
...champion,
|
||||||
|
champion: BasicMatchSerializer.getChampion(champion.id),
|
||||||
|
}
|
||||||
|
})
|
||||||
console.timeEnd('championsRequest')
|
console.timeEnd('championsRequest')
|
||||||
return response.json(championStats)
|
return response.json(championStatsSerialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST: get records view summoner data
|
* POST: get records view summoner data
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async records ({ request, response }: HttpContextContract) {
|
public async records({ request, response }: HttpContextContract) {
|
||||||
console.time('recordsRequest')
|
console.time('recordsRequest')
|
||||||
const { puuid, season } = await request.validate(SummonerRecordValidator)
|
const { puuid, season } = await request.validate(SummonerRecordValidator)
|
||||||
const records = await MatchRepository.records(puuid, season)
|
const records = await MatchRepository.records(puuid)
|
||||||
|
const recordsSerialized = records.map((record) => {
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
what: record.what.split('.')[1],
|
||||||
|
champion: BasicMatchSerializer.getChampion(record.champion_id),
|
||||||
|
}
|
||||||
|
})
|
||||||
console.timeEnd('recordsRequest')
|
console.timeEnd('recordsRequest')
|
||||||
return response.json(records)
|
return response.json(recordsSerialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST - Return live match detail
|
* POST - Return live match detail
|
||||||
* @param ctx
|
* @param ctx
|
||||||
*/
|
*/
|
||||||
public async liveMatchDetails ({ request, response }: HttpContextContract) {
|
public async liveMatchDetails({ request, response }: HttpContextContract) {
|
||||||
console.time('liveMatchDetails')
|
console.time('liveMatchDetails')
|
||||||
const { id, region } = await request.validate(SummonerLiveValidator)
|
const { id, region } = await request.validate(SummonerLiveValidator)
|
||||||
|
|
||||||
// CURRENT GAME
|
// CURRENT GAME
|
||||||
let currentGame = await Jax.Spectator.summonerID(id, region)
|
const currentGame = await Jax.Spectator.summonerID(id, region)
|
||||||
|
|
||||||
if (!currentGame) {
|
if (!currentGame) {
|
||||||
return response.json(null)
|
return response.json(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentGame = await LiveMatchTransformer.transform(currentGame, { region })
|
const currentGameSerialized = await LiveMatchSerializer.serializeOneMatch(currentGame, region)
|
||||||
console.timeEnd('liveMatchDetails')
|
console.timeEnd('liveMatchDetails')
|
||||||
|
|
||||||
return response.json(currentGame)
|
return response.json(currentGameSerialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import Logger from '@ioc:Adonis/Core/Logger'
|
||||||
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
|
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
|
||||||
|
|
||||||
export default class ExceptionHandler extends HttpExceptionHandler {
|
export default class ExceptionHandler extends HttpExceptionHandler {
|
||||||
constructor () {
|
constructor() {
|
||||||
super(Logger)
|
super(Logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { Model } from '@ioc:Mongodb/Model'
|
|
||||||
import { Champion, ParticipantDetails } from 'App/Models/Match'
|
|
||||||
|
|
||||||
export interface DetailedMatchModel {
|
|
||||||
gameId: number,
|
|
||||||
season: number,
|
|
||||||
blueTeam: Team,
|
|
||||||
redTeam: Team,
|
|
||||||
map: number,
|
|
||||||
gamemode: number,
|
|
||||||
date: number,
|
|
||||||
region: string,
|
|
||||||
time: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Team {
|
|
||||||
bans: Ban[],
|
|
||||||
barons: number,
|
|
||||||
color: string,
|
|
||||||
dragons: number,
|
|
||||||
inhibitors: number,
|
|
||||||
players: ParticipantDetails[],
|
|
||||||
result: string,
|
|
||||||
riftHerald: number,
|
|
||||||
teamStats: TeamStats,
|
|
||||||
towers: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Ban {
|
|
||||||
championId: number,
|
|
||||||
pickTurn: number,
|
|
||||||
champion: Champion<null | number, null | string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamStats {
|
|
||||||
kills: number,
|
|
||||||
deaths: number,
|
|
||||||
assists: number,
|
|
||||||
gold: number,
|
|
||||||
dmgChamp: number,
|
|
||||||
dmgObj: number,
|
|
||||||
dmgTaken: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class DetailedMatch extends Model implements DetailedMatchModel {
|
|
||||||
public static collectionName = 'detailed_matches'
|
|
||||||
|
|
||||||
public gameId: number
|
|
||||||
public season: number
|
|
||||||
public blueTeam: Team
|
|
||||||
public redTeam: Team
|
|
||||||
public map: number
|
|
||||||
public gamemode: number
|
|
||||||
public date: number
|
|
||||||
public region: string
|
|
||||||
public time: number
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +1,40 @@
|
||||||
import { Model } from '@ioc:Mongodb/Model'
|
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import MatchPlayer from './MatchPlayer'
|
||||||
|
import MatchTeam from './MatchTeam'
|
||||||
|
|
||||||
export interface MatchModel extends ParticipantDetails {
|
export default class Match extends BaseModel {
|
||||||
account_id: string,
|
public static selfAssignPrimaryKey = true
|
||||||
summoner_puuid: string,
|
|
||||||
gameId: number,
|
|
||||||
result: string,
|
|
||||||
allyTeam: ParticipantBasic[],
|
|
||||||
enemyTeam: ParticipantBasic[],
|
|
||||||
map: number,
|
|
||||||
gamemode: number,
|
|
||||||
date: number,
|
|
||||||
region: string,
|
|
||||||
season: number,
|
|
||||||
time: number,
|
|
||||||
newMatch?: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParticipantDetails {
|
@column({ isPrimary: true })
|
||||||
name: string,
|
public id: string
|
||||||
summonerId: string,
|
|
||||||
champion: Champion,
|
|
||||||
role: string,
|
|
||||||
primaryRune: string | null,
|
|
||||||
secondaryRune: string | null,
|
|
||||||
level: number,
|
|
||||||
items: (Item | null)[],
|
|
||||||
firstSum: SummonerSpell | number | null,
|
|
||||||
secondSum: SummonerSpell | number | null,
|
|
||||||
stats: Stats,
|
|
||||||
percentStats?: PercentStats
|
|
||||||
rank?: Rank | null,
|
|
||||||
perks?: Perks
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Champion<T = number, U = string> {
|
@column()
|
||||||
id: number | T,
|
|
||||||
name: string | U,
|
|
||||||
alias?: string,
|
|
||||||
roles?: string[],
|
|
||||||
icon?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SummonerSpell {
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Rank {
|
|
||||||
tier: string,
|
|
||||||
shortName: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Perks {
|
|
||||||
primaryStyle: number;
|
|
||||||
secondaryStyle: number;
|
|
||||||
selected: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParticipantBasic {
|
|
||||||
account_id: string,
|
|
||||||
name: string,
|
|
||||||
role: string,
|
|
||||||
champion: Champion
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Item {
|
|
||||||
image: string,
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
price: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Stats {
|
|
||||||
kills: number,
|
|
||||||
deaths: number,
|
|
||||||
assists: number,
|
|
||||||
minions: number,
|
|
||||||
vision: number,
|
|
||||||
gold: number,
|
|
||||||
dmgChamp: number,
|
|
||||||
dmgObj: number,
|
|
||||||
dmgTaken: number,
|
|
||||||
kda: number | string,
|
|
||||||
realKda: number,
|
|
||||||
criticalStrike?: number,
|
|
||||||
killingSpree?: number,
|
|
||||||
doubleKills?: number,
|
|
||||||
tripleKills?: number,
|
|
||||||
quadraKills?: number,
|
|
||||||
pentaKills?: number,
|
|
||||||
heal?: number,
|
|
||||||
towers?: number,
|
|
||||||
longestLiving?: number,
|
|
||||||
kp: number | string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PercentStats {
|
|
||||||
minions: number,
|
|
||||||
vision: number,
|
|
||||||
gold: string,
|
|
||||||
dmgChamp: string,
|
|
||||||
dmgObj: string,
|
|
||||||
dmgTaken: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Match extends Model implements MatchModel {
|
|
||||||
public static collectionName = 'matches'
|
|
||||||
|
|
||||||
public account_id: string
|
|
||||||
public summoner_puuid: string
|
|
||||||
public gameId: number
|
public gameId: number
|
||||||
public result: string
|
|
||||||
public allyTeam: ParticipantBasic[]
|
@column()
|
||||||
public enemyTeam: ParticipantBasic[]
|
|
||||||
public map: number
|
public map: number
|
||||||
|
|
||||||
|
@column()
|
||||||
public gamemode: number
|
public gamemode: number
|
||||||
|
|
||||||
|
@column()
|
||||||
public date: number
|
public date: number
|
||||||
|
|
||||||
|
@column()
|
||||||
public region: string
|
public region: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public result: number
|
||||||
|
|
||||||
|
@column()
|
||||||
public season: number
|
public season: number
|
||||||
public time: number
|
|
||||||
public name: string
|
@column()
|
||||||
public summonerId: string
|
public gameDuration: number
|
||||||
public champion: Champion
|
|
||||||
public role: string
|
@hasMany(() => MatchTeam)
|
||||||
public primaryRune: string
|
public teams: HasMany<typeof MatchTeam>
|
||||||
public secondaryRune: string
|
|
||||||
public level: number
|
@hasMany(() => MatchPlayer)
|
||||||
public items: Item[]
|
public players: HasMany<typeof MatchPlayer>
|
||||||
public firstSum: number
|
|
||||||
public secondSum: number
|
|
||||||
public stats: Stats
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
165
server/app/Models/MatchPlayer.ts
Normal file
165
server/app/Models/MatchPlayer.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { BaseModel, BelongsTo, belongsTo, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import Match from './Match'
|
||||||
|
import MatchPlayerRank from './MatchPlayerRank'
|
||||||
|
import Summoner from './Summoner'
|
||||||
|
|
||||||
|
export default class MatchPlayer extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public matchId: number
|
||||||
|
|
||||||
|
@belongsTo(() => Match)
|
||||||
|
public match: BelongsTo<typeof Match>
|
||||||
|
|
||||||
|
@hasMany(() => MatchPlayerRank, {
|
||||||
|
localKey: 'id',
|
||||||
|
foreignKey: 'playerId',
|
||||||
|
})
|
||||||
|
public ranks: HasMany<typeof MatchPlayerRank>
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public participantId: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summonerId: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summonerPuuid: string
|
||||||
|
|
||||||
|
@belongsTo(() => Summoner, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public summoner: BelongsTo<typeof Summoner>
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summonerName: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public team: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public teamPosition: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public win: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public loss: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public remake: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public kills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public deaths: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public assists: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public kda: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public kp: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public champLevel: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public championId: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public championRole: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public doubleKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public tripleKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public quadraKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public pentaKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public baronKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public dragonKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public turretKills: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public visionScore: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public gold: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summoner1Id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summoner2Id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item0: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item1: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item2: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item3: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item4: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item5: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public item6: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public damageDealtObjectives: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public damageDealtChampions: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public damageTaken: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public heal: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public minions: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public criticalStrike: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public killingSpree: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public timeSpentLiving: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public perksPrimaryStyle: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public perksSecondaryStyle: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public perksSelected: number[]
|
||||||
|
}
|
||||||
38
server/app/Models/MatchPlayerRank.ts
Normal file
38
server/app/Models/MatchPlayerRank.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import MatchPlayer from './MatchPlayer'
|
||||||
|
|
||||||
|
export default class MatchPlayerRank extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public playerId: number
|
||||||
|
|
||||||
|
@belongsTo(() => MatchPlayer, {
|
||||||
|
localKey: 'id',
|
||||||
|
foreignKey: 'playerId',
|
||||||
|
})
|
||||||
|
public player: BelongsTo<typeof MatchPlayer>
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public gamemode: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public tier: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public rank: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public lp: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public wins: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public losses: number
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime
|
||||||
|
}
|
||||||
40
server/app/Models/MatchTeam.ts
Normal file
40
server/app/Models/MatchTeam.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import Match from './Match'
|
||||||
|
|
||||||
|
export default class MatchTeam extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public matchId: string
|
||||||
|
|
||||||
|
@belongsTo(() => Match)
|
||||||
|
public match: BelongsTo<typeof Match>
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public color: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public result: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public barons: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public dragons: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public inhibitors: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public riftHeralds: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public towers: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public bans?: number[]
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public banOrders?: number[]
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,36 @@
|
||||||
import { Model } from '@ioc:Mongodb/Model'
|
import { DateTime } from 'luxon'
|
||||||
import { MatchReferenceDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint'
|
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import SummonerMatchlist from './SummonerMatchlist'
|
||||||
|
import SummonerName from './SummonerName'
|
||||||
|
import MatchPlayer from './MatchPlayer'
|
||||||
|
|
||||||
export interface SummonerModel {
|
export default class Summoner extends BaseModel {
|
||||||
puuid: string,
|
public static selfAssignPrimaryKey = true
|
||||||
matchList?: MatchReferenceDto[],
|
|
||||||
names?: SummonerNames[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SummonerNames {
|
|
||||||
name: string,
|
|
||||||
date: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Summoner extends Model implements SummonerModel {
|
|
||||||
public static collectionName = 'summoners'
|
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
public puuid: string
|
public puuid: string
|
||||||
public matchList?: MatchReferenceDto[]
|
|
||||||
public names?: SummonerNames[]
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime
|
||||||
|
|
||||||
|
@hasMany(() => SummonerMatchlist, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public matchList: HasMany<typeof SummonerMatchlist>
|
||||||
|
|
||||||
|
@hasMany(() => MatchPlayer, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public matches: HasMany<typeof MatchPlayer>
|
||||||
|
|
||||||
|
@hasMany(() => SummonerName, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public names: HasMany<typeof SummonerName>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
server/app/Models/SummonerMatchlist.ts
Normal file
21
server/app/Models/SummonerMatchlist.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import Summoner from './Summoner'
|
||||||
|
|
||||||
|
export default class SummonerMatchlist extends BaseModel {
|
||||||
|
public static table = 'summoner_matchlist'
|
||||||
|
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summonerPuuid: string
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public matchId: string
|
||||||
|
|
||||||
|
@belongsTo(() => Summoner, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public summoner: BelongsTo<typeof Summoner>
|
||||||
|
}
|
||||||
23
server/app/Models/SummonerName.ts
Normal file
23
server/app/Models/SummonerName.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
|
||||||
|
import Summoner from './Summoner'
|
||||||
|
|
||||||
|
export default class SummonerName extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public summonerPuuid: string
|
||||||
|
|
||||||
|
@belongsTo(() => Summoner, {
|
||||||
|
localKey: 'puuid',
|
||||||
|
foreignKey: 'summonerPuuid',
|
||||||
|
})
|
||||||
|
public summoner: BelongsTo<typeof Summoner>
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public name: string
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime
|
||||||
|
}
|
||||||
160
server/app/Parsers/MatchParser.ts
Normal file
160
server/app/Parsers/MatchParser.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import Database 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'
|
||||||
|
import CDragonService from 'App/Services/CDragonService'
|
||||||
|
import { ChampionRoles, TeamPosition } from './ParsedType'
|
||||||
|
class MatchParser {
|
||||||
|
public async parseOneMatch(match: MatchDto) {
|
||||||
|
// Parse + store in database
|
||||||
|
// - 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: Math.round(match.info.gameDuration / 1000),
|
||||||
|
})
|
||||||
|
|
||||||
|
const isRemake = match.info.gameDuration < 300000
|
||||||
|
|
||||||
|
// - 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// - 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Load Match relations
|
||||||
|
await parsedMatch.load((loader) => {
|
||||||
|
loader.load('teams').load('players')
|
||||||
|
})
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
return parsedMatches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MatchParser()
|
||||||
43
server/app/Parsers/MatchPlayerRankParser.ts
Normal file
43
server/app/Parsers/MatchPlayerRankParser.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database'
|
||||||
|
import { notEmpty } from 'App/helpers'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import SummonerService from 'App/Services/SummonerService'
|
||||||
|
import { PlayerRankParsed } from './ParsedType'
|
||||||
|
|
||||||
|
class MatchPlayerRankParser {
|
||||||
|
public async parse(match: Match): Promise<PlayerRankParsed[]> {
|
||||||
|
const requests = match.players.map((p) => SummonerService.getRanked(p.summonerId, match.region))
|
||||||
|
const ranks = await Promise.all(requests)
|
||||||
|
|
||||||
|
const parsedRanks = ranks
|
||||||
|
.map((rank) => {
|
||||||
|
return Object.entries(rank).map(([queue, data]) => {
|
||||||
|
let player: MatchPlayer | undefined
|
||||||
|
if (!data || !(player = match.players.find((p) => p.summonerId === data.summonerId))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const rank: PlayerRankParsed = {
|
||||||
|
player_id: player.id,
|
||||||
|
gamemode: queue === 'soloQ' ? 420 : 440,
|
||||||
|
tier: data.tier,
|
||||||
|
rank: SummonerService.leaguesNumbers[data.rank],
|
||||||
|
lp: data.leaguePoints,
|
||||||
|
wins: data.wins,
|
||||||
|
losses: data.losses,
|
||||||
|
}
|
||||||
|
return rank
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter(notEmpty)
|
||||||
|
|
||||||
|
// Store ranks in DB
|
||||||
|
await Database.table('match_player_ranks').multiInsert(parsedRanks)
|
||||||
|
|
||||||
|
return parsedRanks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MatchPlayerRankParser()
|
||||||
246
server/app/Parsers/MatchV4Parser.ts
Normal file
246
server/app/Parsers/MatchV4Parser.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
import Database from '@ioc:Adonis/Lucid/Database'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import { getSeasonNumber, notEmpty, PlayerRole, queuesWithRole, supportItems } from 'App/helpers'
|
||||||
|
import CDragonService from 'App/Services/CDragonService'
|
||||||
|
import { ChampionRoles, TeamPosition } from './ParsedType'
|
||||||
|
import { V4MatchDto } from 'App/Services/Jax/src/Endpoints/MatchV4Endpoint'
|
||||||
|
import RoleIdentificationService from 'App/Services/RoleIdentificationService'
|
||||||
|
import Jax from 'App/Services/Jax'
|
||||||
|
|
||||||
|
class MatchV4Parser {
|
||||||
|
public createMatchId(gameId: number, region: string) {
|
||||||
|
return `${region.toUpperCase()}_${gameId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTeamRoles(team: PlayerRole[]) {
|
||||||
|
const teamJunglers = team.filter((p) => p.jungle && !p.support)
|
||||||
|
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
||||||
|
const teamSupports = team.filter((p) => p.support && !p.jungle)
|
||||||
|
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
||||||
|
|
||||||
|
return RoleIdentificationService.getRoles(
|
||||||
|
CDragonService.championRoles,
|
||||||
|
team.map((p) => p.champion),
|
||||||
|
jungle,
|
||||||
|
support
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMatchRoles(match: V4MatchDto) {
|
||||||
|
const blueChamps: PlayerRole[] = []
|
||||||
|
const redChamps: PlayerRole[] = []
|
||||||
|
|
||||||
|
match.participants.map((p) => {
|
||||||
|
const items = [
|
||||||
|
p.stats.item0,
|
||||||
|
p.stats.item1,
|
||||||
|
p.stats.item2,
|
||||||
|
p.stats.item3,
|
||||||
|
p.stats.item4,
|
||||||
|
p.stats.item5,
|
||||||
|
]
|
||||||
|
const playerRole = {
|
||||||
|
champion: p.championId,
|
||||||
|
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
||||||
|
support: supportItems.some((suppItem) => items.includes(suppItem)),
|
||||||
|
}
|
||||||
|
p.teamId === 100 ? blueChamps.push(playerRole) : redChamps.push(playerRole)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
blue: this.getTeamRoles(blueChamps),
|
||||||
|
red: this.getTeamRoles(redChamps),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async parseOneMatch(match: V4MatchDto) {
|
||||||
|
// Parse + store in database
|
||||||
|
const matchId = this.createMatchId(match.gameId, match.platformId)
|
||||||
|
|
||||||
|
if (match.participants.length !== 10) {
|
||||||
|
console.log(`Match not saved because < 10 players. Gamemode: ${match.queueId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// PUUID of the 10 players
|
||||||
|
const accountRequests = match.participantIdentities
|
||||||
|
.filter((p) => p.player.accountId !== '0')
|
||||||
|
.map((p) => Jax.Summoner.accountId(p.player.currentAccountId, match.platformId.toLowerCase()))
|
||||||
|
const playerAccounts = (await Promise.all(accountRequests)).filter(notEmpty)
|
||||||
|
|
||||||
|
if (!playerAccounts || !playerAccounts.length) {
|
||||||
|
console.log(`0 Account found from match: ${matchId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRemake = match.gameDuration < 300
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
const { blue: blueRoles, red: redRoles } = this.getMatchRoles(match)
|
||||||
|
|
||||||
|
// - 10x MatchPlayer
|
||||||
|
const matchPlayers: any[] = []
|
||||||
|
for (const player of match.participants) {
|
||||||
|
const identity = match.participantIdentities.find(
|
||||||
|
(p) => p.participantId === player.participantId
|
||||||
|
)!
|
||||||
|
const isBot = identity.player.accountId === '0'
|
||||||
|
const account = isBot
|
||||||
|
? null
|
||||||
|
: playerAccounts.find((p) => p.accountId === identity.player.currentAccountId)
|
||||||
|
|
||||||
|
if (!account && !isBot) {
|
||||||
|
console.log(`Account not found ${identity.player.currentAccountId}`)
|
||||||
|
console.log(`Match ${matchId} not saved in the database.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const kda =
|
||||||
|
player.stats.kills + player.stats.assists !== 0 && player.stats.deaths === 0
|
||||||
|
? player.stats.kills + player.stats.assists
|
||||||
|
: +(
|
||||||
|
player.stats.deaths === 0
|
||||||
|
? 0
|
||||||
|
: (player.stats.kills + player.stats.assists) / player.stats.deaths
|
||||||
|
).toFixed(2)
|
||||||
|
|
||||||
|
const team = match.teams[0].teamId === player.teamId ? match.teams[0] : match.teams[1]
|
||||||
|
const totalKills = match.participants.reduce((prev, current) => {
|
||||||
|
if (current.teamId !== player.teamId) {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
return prev + current.stats.kills
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
const kp =
|
||||||
|
totalKills === 0
|
||||||
|
? 0
|
||||||
|
: +(((player.stats.kills + player.stats.assists) * 100) / totalKills).toFixed(1)
|
||||||
|
|
||||||
|
// Perks
|
||||||
|
const primaryStyle = player.stats.perkPrimaryStyle
|
||||||
|
const secondaryStyle = player.stats.perkSubStyle
|
||||||
|
const perksSelected: number[] = []
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
perksSelected.push(player.stats[`perk${i}`])
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
perksSelected.push(player.stats[`statPerk${i}`])
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalChampionData = CDragonService.champions[player.championId]
|
||||||
|
const champRoles = originalChampionData.roles
|
||||||
|
|
||||||
|
// Role
|
||||||
|
const teamRoles = player.teamId === 100 ? blueRoles : redRoles
|
||||||
|
const role = Object.entries(teamRoles).find(
|
||||||
|
([, champion]) => player.championId === champion
|
||||||
|
)![0]
|
||||||
|
|
||||||
|
matchPlayers.push({
|
||||||
|
match_id: matchId,
|
||||||
|
participant_id: player.participantId,
|
||||||
|
summoner_id: isBot ? 'BOT' : identity.player.summonerId,
|
||||||
|
summoner_puuid: account ? account.puuid : 'BOT',
|
||||||
|
summoner_name: identity.player.summonerName,
|
||||||
|
win: team.win === 'Win' ? 1 : 0,
|
||||||
|
loss: team.win === 'Fail' ? 1 : 0,
|
||||||
|
remake: isRemake ? 1 : 0,
|
||||||
|
team: player.teamId,
|
||||||
|
team_position: queuesWithRole.includes(match.queueId)
|
||||||
|
? TeamPosition[role]
|
||||||
|
: TeamPosition.NONE,
|
||||||
|
kills: player.stats.kills,
|
||||||
|
deaths: player.stats.deaths,
|
||||||
|
assists: player.stats.assists,
|
||||||
|
kda: kda,
|
||||||
|
kp: kp,
|
||||||
|
champ_level: player.stats.champLevel,
|
||||||
|
champion_id: player.championId,
|
||||||
|
champion_role: ChampionRoles[champRoles[0]],
|
||||||
|
double_kills: player.stats.doubleKills,
|
||||||
|
triple_kills: player.stats.tripleKills,
|
||||||
|
quadra_kills: player.stats.quadraKills,
|
||||||
|
penta_kills: player.stats.pentaKills,
|
||||||
|
baron_kills: 0,
|
||||||
|
dragon_kills: 0,
|
||||||
|
turret_kills: player.stats.turretKills,
|
||||||
|
vision_score: player.stats.visionScore,
|
||||||
|
gold: player.stats.goldEarned,
|
||||||
|
summoner1_id: player.spell1Id,
|
||||||
|
summoner2_id: player.spell2Id,
|
||||||
|
item0: player.stats.item0,
|
||||||
|
item1: player.stats.item1,
|
||||||
|
item2: player.stats.item2,
|
||||||
|
item3: player.stats.item3,
|
||||||
|
item4: player.stats.item4,
|
||||||
|
item5: player.stats.item5,
|
||||||
|
item6: player.stats.item6,
|
||||||
|
damage_dealt_objectives: player.stats.damageDealtToObjectives,
|
||||||
|
damage_dealt_champions: player.stats.totalDamageDealtToChampions,
|
||||||
|
damage_taken: player.stats.totalDamageTaken,
|
||||||
|
heal: player.stats.totalHeal,
|
||||||
|
minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled,
|
||||||
|
critical_strike: player.stats.largestCriticalStrike,
|
||||||
|
killing_spree: player.stats.killingSprees,
|
||||||
|
time_spent_living: player.stats.longestTimeSpentLiving,
|
||||||
|
perks_primary_style: primaryStyle ?? 8100,
|
||||||
|
perks_secondary_style: secondaryStyle ?? 8000,
|
||||||
|
perks_selected: perksSelected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await Database.table('match_players').multiInsert(matchPlayers)
|
||||||
|
|
||||||
|
// - 1x Match
|
||||||
|
const parsedMatch = await Match.create({
|
||||||
|
id: matchId,
|
||||||
|
gameId: match.gameId,
|
||||||
|
map: match.mapId,
|
||||||
|
gamemode: match.queueId,
|
||||||
|
date: match.gameCreation,
|
||||||
|
region: match.platformId.toLowerCase(),
|
||||||
|
result: match.teams[0].win === 'Win' ? match.teams[0].teamId : match.teams[1].teamId,
|
||||||
|
season: getSeasonNumber(match.gameCreation),
|
||||||
|
gameDuration: match.gameDuration,
|
||||||
|
})
|
||||||
|
|
||||||
|
// - 2x MatchTeam : Red and Blue
|
||||||
|
for (const team of match.teams) {
|
||||||
|
let result = team.win === 'Win' ? 'Win' : 'Fail'
|
||||||
|
if (isRemake) {
|
||||||
|
result = 'Remake'
|
||||||
|
}
|
||||||
|
await parsedMatch.related('teams').create({
|
||||||
|
matchId: matchId,
|
||||||
|
color: team.teamId,
|
||||||
|
result: result,
|
||||||
|
barons: team.baronKills,
|
||||||
|
dragons: team.dragonKills,
|
||||||
|
inhibitors: team.inhibitorKills,
|
||||||
|
riftHeralds: team.riftHeraldKills,
|
||||||
|
towers: team.towerKills,
|
||||||
|
bans: team.bans.length ? team.bans.map((ban) => ban.championId) : undefined,
|
||||||
|
banOrders: team.bans.length ? team.bans.map((ban) => ban.pickTurn) : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Match relations
|
||||||
|
await parsedMatch.load((loader) => {
|
||||||
|
loader.load('teams').load('players')
|
||||||
|
})
|
||||||
|
return parsedMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
public async parse(matches: V4MatchDto[]) {
|
||||||
|
// Loop on all matches and call .parseOneMatch on it
|
||||||
|
const parsedMatches: Match[] = []
|
||||||
|
for (const match of matches) {
|
||||||
|
const parsed = await this.parseOneMatch(match)
|
||||||
|
if (parsed) {
|
||||||
|
parsedMatches.push(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsedMatches.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MatchV4Parser()
|
||||||
27
server/app/Parsers/ParsedType.ts
Normal file
27
server/app/Parsers/ParsedType.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export enum ChampionRoles {
|
||||||
|
assassin,
|
||||||
|
fighter,
|
||||||
|
mage,
|
||||||
|
marksman,
|
||||||
|
support,
|
||||||
|
tank,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TeamPosition {
|
||||||
|
NONE,
|
||||||
|
TOP,
|
||||||
|
JUNGLE,
|
||||||
|
MIDDLE,
|
||||||
|
BOTTOM,
|
||||||
|
UTILITY,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerRankParsed {
|
||||||
|
player_id: number
|
||||||
|
gamemode: number
|
||||||
|
tier: string
|
||||||
|
rank: number
|
||||||
|
lp: number
|
||||||
|
wins: number
|
||||||
|
losses: number
|
||||||
|
}
|
||||||
|
|
@ -1,337 +1,257 @@
|
||||||
import mongodb from '@ioc:Mongodb/Database'
|
import Database from '@ioc:Adonis/Lucid/Database'
|
||||||
import { Collection } from 'mongodb'
|
|
||||||
|
|
||||||
class MatchRepository {
|
class MatchRepository {
|
||||||
private collection: Collection
|
private readonly JOIN_MATCHES = 'INNER JOIN matches ON matches.id = match_players.match_id'
|
||||||
|
private readonly JOIN_TEAMS =
|
||||||
|
'INNER JOIN match_teams ON match_players.match_id = match_teams.match_id AND match_players.team = match_teams.color'
|
||||||
|
private readonly JOIN_ALL = `${this.JOIN_MATCHES} ${this.JOIN_TEAMS}`
|
||||||
|
|
||||||
constructor () {
|
private readonly GLOBAL_FILTERS = `
|
||||||
this.getCollection()
|
match_players.summoner_puuid = :puuid
|
||||||
|
AND match_players.remake = 0
|
||||||
|
AND matches.gamemode NOT IN (800, 810, 820, 830, 840, 850, 2000, 2010, 2020)
|
||||||
|
`
|
||||||
|
|
||||||
|
public async recentActivity(puuid: string) {
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
to_timestamp(matches.date/1000)::date as day,
|
||||||
|
COUNT(match_players.id) as count
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
match_players.summoner_puuid = :puuid
|
||||||
|
GROUP BY
|
||||||
|
day
|
||||||
|
ORDER BY
|
||||||
|
day
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async globalStats(puuid: string) {
|
||||||
* Basic matchParams used in a lot of requests
|
const query = `
|
||||||
* @param puuid of the summoner
|
SELECT
|
||||||
*/
|
SUM(match_players.kills) as kills,
|
||||||
private matchParams (puuid: string, season?: number) {
|
SUM(match_players.deaths) as deaths,
|
||||||
return {
|
SUM(match_players.assists) as assists,
|
||||||
summoner_puuid: puuid,
|
SUM(match_players.minions) as minions,
|
||||||
result: { $not: { $eq: 'Remake' } },
|
SUM(matches.game_duration) as time,
|
||||||
gamemode: { $nin: [800, 810, 820, 830, 840, 850, 2000, 2010, 2020] },
|
SUM(match_players.vision_score) as vision,
|
||||||
season: season ? season : { $exists: true },
|
COUNT(match_players.id) as count,
|
||||||
}
|
AVG(match_players.kp) as kp,
|
||||||
|
SUM(match_players.win) as wins,
|
||||||
|
SUM(match_players.loss) as losses
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
return rows[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async gamemodeStats(puuid: string) {
|
||||||
* Build the aggregate mongo query
|
const query = `
|
||||||
* @param puuid
|
SELECT
|
||||||
* @param matchParams
|
matches.gamemode as id,
|
||||||
* @param intermediateSteps
|
COUNT(match_players.id) as count,
|
||||||
* @param groupId
|
SUM(match_players.win) as wins,
|
||||||
* @param groupParams
|
SUM(match_players.loss) as losses
|
||||||
* @param finalSteps
|
FROM
|
||||||
*/
|
match_players
|
||||||
private async aggregate (
|
${this.JOIN_MATCHES}
|
||||||
puuid: string,
|
WHERE
|
||||||
matchParams: object,
|
${this.GLOBAL_FILTERS}
|
||||||
intermediateSteps: any[],
|
GROUP BY
|
||||||
groupId: any,
|
matches.gamemode
|
||||||
groupParams: object,
|
ORDER BY
|
||||||
finalSteps: any[],
|
count DESC
|
||||||
season?: number,
|
`
|
||||||
) {
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
return this.collection.aggregate([
|
return rows
|
||||||
{
|
|
||||||
$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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async roleStats(puuid: string) {
|
||||||
* Get MongoDB matches collection
|
const query = `
|
||||||
*/
|
SELECT
|
||||||
public async getCollection () {
|
match_players.team_position as role,
|
||||||
if (!this.collection) {
|
COUNT(match_players.id) as count,
|
||||||
this.collection = await mongodb.connection().collection('matches')
|
SUM(match_players.win) as wins,
|
||||||
}
|
SUM(match_players.loss) as losses
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
AND match_players.team_position != 0
|
||||||
|
GROUP BY
|
||||||
|
role
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async championStats(puuid: string, limit: number) {
|
||||||
* Get Summoner's statistics for the N most played champions
|
const query = `
|
||||||
* @param puuid of the summoner
|
SELECT
|
||||||
* @param limit number of champions to fetch
|
match_players.champion_id as id,
|
||||||
* @param season
|
SUM(match_players.assists) as assists,
|
||||||
*/
|
SUM(match_players.deaths) as deaths,
|
||||||
public async championStats (puuid: string, limit = 5, season?: number) {
|
SUM(match_players.kills) as kills,
|
||||||
const groupParams = {
|
COUNT(match_players.id) as count,
|
||||||
champion: { $first: '$champion' },
|
SUM(match_players.win) as wins,
|
||||||
kills: { $sum: '$stats.kills' },
|
SUM(match_players.loss) as losses
|
||||||
deaths: { $sum: '$stats.deaths' },
|
FROM
|
||||||
assists: { $sum: '$stats.assists' },
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
GROUP BY
|
||||||
|
match_players.champion_id
|
||||||
|
ORDER BY
|
||||||
|
count DESC, match_players.champion_id
|
||||||
|
LIMIT
|
||||||
|
:limit
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid, limit })
|
||||||
|
return rows
|
||||||
}
|
}
|
||||||
const finalSteps = [
|
|
||||||
{ $sort: { 'count': -1, 'champion.name': 1 } },
|
public async championClassStats(puuid: string) {
|
||||||
{ $limit: limit },
|
const query = `
|
||||||
|
SELECT
|
||||||
|
match_players.champion_role as id,
|
||||||
|
COUNT(match_players.id) as count,
|
||||||
|
SUM(match_players.win) as wins,
|
||||||
|
SUM(match_players.loss) as losses
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
GROUP BY
|
||||||
|
match_players.champion_role
|
||||||
|
ORDER BY
|
||||||
|
count DESC
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
public async championCompleteStats(puuid: string, queue?: number, season?: number) {
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
match_players.champion_id as id,
|
||||||
|
SUM(match_players.assists) as assists,
|
||||||
|
SUM(match_players.deaths) as deaths,
|
||||||
|
SUM(match_players.kills) as kills,
|
||||||
|
COUNT(match_players.id) as count,
|
||||||
|
SUM(match_players.win) as wins,
|
||||||
|
SUM(match_players.loss) as losses,
|
||||||
|
AVG(matches.game_duration)::int as "gameLength",
|
||||||
|
AVG(match_players.minions)::int as minions,
|
||||||
|
AVG(match_players.gold)::int as gold,
|
||||||
|
AVG(match_players.damage_dealt_champions)::int as "dmgChamp",
|
||||||
|
AVG(match_players.damage_taken)::int as "dmgTaken",
|
||||||
|
AVG(match_players.kp) as kp,
|
||||||
|
MAX(matches.date) as date
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
GROUP BY
|
||||||
|
match_players.champion_id
|
||||||
|
ORDER BY
|
||||||
|
count DESC, match_players.champion_id
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
public async mates(puuid: string) {
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
(array_agg(mates.summoner_name ORDER BY mates.match_id DESC))[1] as name,
|
||||||
|
COUNT(match_players.id) as count,
|
||||||
|
SUM(match_players.win) as wins,
|
||||||
|
SUM(match_players.loss) as losses
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_ALL}
|
||||||
|
INNER JOIN match_players as mates ON match_players.match_id = mates.match_id AND match_players.team = mates.team
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
GROUP BY
|
||||||
|
mates.summoner_puuid
|
||||||
|
ORDER BY
|
||||||
|
count DESC, wins DESC
|
||||||
|
LIMIT
|
||||||
|
15
|
||||||
|
`
|
||||||
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
|
|
||||||
|
// Remove the Summoner himself + unique game mates
|
||||||
|
return rows.splice(1).filter((row) => row.count > 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async records(puuid: string) {
|
||||||
|
const fields = [
|
||||||
|
'match_players.kills',
|
||||||
|
'match_players.deaths',
|
||||||
|
'match_players.assists',
|
||||||
|
'match_players.gold',
|
||||||
|
'matches.game_duration',
|
||||||
|
'match_players.minions',
|
||||||
|
'match_players.kda',
|
||||||
|
'match_players.damage_taken',
|
||||||
|
'match_players.damage_dealt_champions',
|
||||||
|
'match_players.damage_dealt_objectives',
|
||||||
|
'match_players.kp',
|
||||||
|
'match_players.vision_score',
|
||||||
|
'match_players.critical_strike',
|
||||||
|
'match_players.time_spent_living',
|
||||||
|
'match_players.heal',
|
||||||
|
'match_players.turret_kills',
|
||||||
|
'match_players.killing_spree',
|
||||||
|
'match_players.double_kills',
|
||||||
|
'match_players.triple_kills',
|
||||||
|
'match_players.quadra_kills',
|
||||||
|
'match_players.penta_kills',
|
||||||
]
|
]
|
||||||
return this.aggregate(puuid, {}, [], '$champion.id', groupParams, finalSteps, season)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const query = fields
|
||||||
* Get Summoner's statistics for all played champion classes
|
.map((field) => {
|
||||||
* @param puuid of the summoner
|
return `
|
||||||
* @param season
|
(SELECT
|
||||||
*/
|
'${field}' AS what,
|
||||||
public async championClassStats (puuid: string, season?: number) {
|
${field} AS amount,
|
||||||
const groupId = { '$arrayElemAt': ['$champion.roles', 0] }
|
match_players.win as result,
|
||||||
return this.aggregate(puuid, {}, [], groupId, {}, [], season)
|
matches.id,
|
||||||
}
|
matches.date,
|
||||||
|
matches.gamemode,
|
||||||
|
match_players.champion_id
|
||||||
|
FROM
|
||||||
|
match_players
|
||||||
|
${this.JOIN_MATCHES}
|
||||||
|
WHERE
|
||||||
|
${this.GLOBAL_FILTERS}
|
||||||
|
ORDER BY
|
||||||
|
${field} DESC, matches.id
|
||||||
|
LIMIT
|
||||||
|
1)
|
||||||
|
`
|
||||||
|
})
|
||||||
|
.join('UNION ALL ')
|
||||||
|
|
||||||
/**
|
const { rows } = await Database.rawQuery(query, { puuid })
|
||||||
* Get Summoner's complete statistics for the all played champs
|
return rows
|
||||||
* @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 statistics for all played modes
|
|
||||||
* @param puuid of the summoner
|
|
||||||
* @param season
|
|
||||||
*/
|
|
||||||
public async gamemodeStats (puuid: string, season?: number) {
|
|
||||||
return this.aggregate(puuid, {}, [], '$gamemode', {}, [], season)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get global Summoner's statistics
|
|
||||||
* @param puuid of the summoner
|
|
||||||
* @param season
|
|
||||||
*/
|
|
||||||
public async globalStats (puuid: string, season?: number) {
|
|
||||||
const groupParams = {
|
|
||||||
time: { $sum: '$time' },
|
|
||||||
kills: { $sum: '$stats.kills' },
|
|
||||||
deaths: { $sum: '$stats.deaths' },
|
|
||||||
assists: { $sum: '$stats.assists' },
|
|
||||||
minions: { $sum: '$stats.minions' },
|
|
||||||
vision: { $sum: '$stats.vision' },
|
|
||||||
kp: { $avg: '$stats.kp' },
|
|
||||||
}
|
|
||||||
return this.aggregate(puuid, {}, [], null, groupParams, [], season)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner's all records
|
|
||||||
* @param puuid of the summoner
|
|
||||||
* @param season of the matches to fetch, if null get all seasons
|
|
||||||
*/
|
|
||||||
public async records (puuid: string, season?: number) {
|
|
||||||
const records = await this.collection.aggregate([
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
...this.matchParams(puuid, season),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$group: {
|
|
||||||
_id: null,
|
|
||||||
maxKills: { $max: '$stats.kills' },
|
|
||||||
maxDeaths: { $max: '$stats.deaths' },
|
|
||||||
maxAssists: { $max: '$stats.assists' },
|
|
||||||
maxGold: { $max: '$stats.gold' },
|
|
||||||
maxTime: { $max: '$time' },
|
|
||||||
maxMinions: { $max: '$stats.minions' },
|
|
||||||
maxKda: { $max: '$stats.realKda' },
|
|
||||||
maxDmgTaken: { $max: '$stats.dmgTaken' },
|
|
||||||
maxDmgChamp: { $max: '$stats.dmgChamp' },
|
|
||||||
maxDmgObj: { $max: '$stats.dmgObj' },
|
|
||||||
maxKp: { $max: '$stats.kp' },
|
|
||||||
maxVision: { $max: '$stats.vision' },
|
|
||||||
maxCriticalStrike: { $max: '$stats.criticalStrike' },
|
|
||||||
maxLiving: { $max: '$stats.longestLiving' },
|
|
||||||
maxHeal: { $max: '$stats.heal' },
|
|
||||||
maxTowers: { $max: '$stats.towers' },
|
|
||||||
maxKillingSpree: { $max: '$stats.killingSpree' },
|
|
||||||
maxDouble: { $max: '$stats.doubleKills' },
|
|
||||||
maxTriple: { $max: '$stats.tripleKills' },
|
|
||||||
maxQuadra: { $max: '$stats.quadraKills' },
|
|
||||||
maxPenta: { $max: '$stats.pentaKills' },
|
|
||||||
docs: {
|
|
||||||
'$push': {
|
|
||||||
'champion': '$champion',
|
|
||||||
'gameId': '$gameId',
|
|
||||||
'kills': '$stats.kills',
|
|
||||||
'deaths': '$stats.deaths',
|
|
||||||
'assists': '$stats.assists',
|
|
||||||
'gold': '$stats.gold',
|
|
||||||
'time': '$time',
|
|
||||||
'minions': '$stats.minions',
|
|
||||||
'kda': '$stats.realKda',
|
|
||||||
'dmgTaken': '$stats.dmgTaken',
|
|
||||||
'dmgChamp': '$stats.dmgChamp',
|
|
||||||
'dmgObj': '$stats.dmgObj',
|
|
||||||
'kp': '$stats.kp',
|
|
||||||
'vision': '$stats.vision',
|
|
||||||
'criticalStrike': '$stats.criticalStrike',
|
|
||||||
'longestLiving': '$stats.longestLiving',
|
|
||||||
'heal': '$stats.heal',
|
|
||||||
'towers': '$stats.towers',
|
|
||||||
'killingSpree': '$stats.killingSpree',
|
|
||||||
'doubleKills': '$stats.doubleKills',
|
|
||||||
'tripleKills': '$stats.tripleKills',
|
|
||||||
'quadraKills': '$stats.quadraKills',
|
|
||||||
'pentaKills': '$stats.pentaKills',
|
|
||||||
'result': '$result',
|
|
||||||
'date': '$date',
|
|
||||||
'gamemode': '$gamemode',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
_id: 0,
|
|
||||||
/* eslint-disable max-len */
|
|
||||||
maxKills: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kills', '$maxKills'] } } }, 0] },
|
|
||||||
maxDeaths: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.deaths', '$maxDeaths'] } } }, 0] },
|
|
||||||
maxAssists: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.assists', '$maxAssists'] } } }, 0] },
|
|
||||||
maxGold: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.gold', '$maxGold'] } } }, 0] },
|
|
||||||
maxTime: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.time', '$maxTime'] } } }, 0] },
|
|
||||||
maxMinions: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.minions', '$maxMinions'] } } }, 0] },
|
|
||||||
maxKda: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kda', '$maxKda'] } } }, 0] },
|
|
||||||
maxDmgTaken: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgTaken', '$maxDmgTaken'] } } }, 0] },
|
|
||||||
maxDmgChamp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgChamp', '$maxDmgChamp'] } } }, 0] },
|
|
||||||
maxDmgObj: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgObj', '$maxDmgObj'] } } }, 0] },
|
|
||||||
maxKp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kp', '$maxKp'] } } }, 0] },
|
|
||||||
maxVision: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.vision', '$maxVision'] } } }, 0] },
|
|
||||||
maxCriticalStrike: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.criticalStrike', '$maxCriticalStrike'] } } }, 0] },
|
|
||||||
maxLiving: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.longestLiving', '$maxLiving'] } } }, 0] },
|
|
||||||
maxHeal: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.heal', '$maxHeal'] } } }, 0] },
|
|
||||||
maxTowers: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.towers', '$maxTowers'] } } }, 0] },
|
|
||||||
maxKillingSpree: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.killingSpree', '$maxKillingSpree'] } } }, 0] },
|
|
||||||
maxDouble: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.doubleKills', '$maxDouble'] } } }, 0] },
|
|
||||||
maxTriple: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.tripleKills', '$maxTriple'] } } }, 0] },
|
|
||||||
maxQuadra: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.quadraKills', '$maxQuadra'] } } }, 0] },
|
|
||||||
maxPenta: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.pentaKills', '$maxPenta'] } } }, 0] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]).toArray()
|
|
||||||
|
|
||||||
return records[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner's statistics for the 5 differnt roles
|
|
||||||
* @param puuid of the summoner
|
|
||||||
* @param season
|
|
||||||
*/
|
|
||||||
public async roleStats (puuid: string, season?: number) {
|
|
||||||
const matchParams = {
|
|
||||||
role: { $not: { $eq: 'NONE' } },
|
|
||||||
}
|
|
||||||
const finalSteps = [
|
|
||||||
{
|
|
||||||
$project: {
|
|
||||||
role: '$_id',
|
|
||||||
count: '$count',
|
|
||||||
wins: '$wins',
|
|
||||||
losses: '$losses',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
return this.aggregate(puuid, matchParams, [], '$role', {}, finalSteps, season)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner's played seasons
|
|
||||||
* @param puuid of the summoner
|
|
||||||
*/
|
|
||||||
public async seasons (puuid: string) {
|
|
||||||
return this.collection.aggregate([
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
...this.matchParams(puuid),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$group: { _id: '$season' },
|
|
||||||
},
|
|
||||||
]).toArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner's mates list
|
|
||||||
* @param puuid of the summoner
|
|
||||||
* @param season
|
|
||||||
*/
|
|
||||||
public async mates (puuid: string, season?: number) {
|
|
||||||
const intermediateSteps = [
|
|
||||||
{ $sort: { 'gameId': -1 } },
|
|
||||||
{ $unwind: '$allyTeam' },
|
|
||||||
]
|
|
||||||
const groupParams = {
|
|
||||||
account_id: { $first: '$account_id' },
|
|
||||||
name: { $first: '$allyTeam.name' },
|
|
||||||
mateId: { $first: '$allyTeam.account_id' },
|
|
||||||
}
|
|
||||||
const finalSteps = [
|
|
||||||
{
|
|
||||||
'$addFields': {
|
|
||||||
'idEq': { '$eq': ['$mateId', '$account_id'] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$match: {
|
|
||||||
'idEq': false,
|
|
||||||
'count': { $gte: 2 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ $sort: { 'count': -1, 'name': 1 } },
|
|
||||||
{ $limit: 15 },
|
|
||||||
]
|
|
||||||
return this.aggregate(puuid, {}, intermediateSteps, '$allyTeam.account_id', groupParams, finalSteps, season)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
80
server/app/Serializers/BasicMatchSerializer.ts
Normal file
80
server/app/Serializers/BasicMatchSerializer.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { getSeasonNumber, sortTeamByRole } from 'App/helpers'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import { TeamPosition } from 'App/Parsers/ParsedType'
|
||||||
|
import MatchSerializer from './MatchSerializer'
|
||||||
|
import { SerializedMatch, SerializedMatchStats, SerializedMatchTeamPlayer } from './SerializedTypes'
|
||||||
|
|
||||||
|
class BasicMatchSerializer extends MatchSerializer {
|
||||||
|
protected getPlayerSummary(player: MatchPlayer): SerializedMatchTeamPlayer {
|
||||||
|
return {
|
||||||
|
puuid: player.summonerPuuid,
|
||||||
|
champion: this.getChampion(player.championId),
|
||||||
|
name: player.summonerName,
|
||||||
|
role: TeamPosition[player.teamPosition],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTeamSummary(players: MatchPlayer[]): SerializedMatchTeamPlayer[] {
|
||||||
|
return players.map((p) => this.getPlayerSummary(p)).sort(sortTeamByRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getStats(player: MatchPlayer): SerializedMatchStats {
|
||||||
|
return {
|
||||||
|
kills: player.kills,
|
||||||
|
deaths: player.deaths,
|
||||||
|
assists: player.assists,
|
||||||
|
minions: player.minions,
|
||||||
|
vision: player.visionScore,
|
||||||
|
gold: player.gold,
|
||||||
|
dmgChamp: player.damageDealtChampions,
|
||||||
|
dmgObj: player.damageDealtObjectives,
|
||||||
|
dmgTaken: player.damageTaken,
|
||||||
|
kp: player.kp,
|
||||||
|
kda: player.kills + player.assists !== 0 && player.deaths === 0 ? '∞' : player.kda,
|
||||||
|
realKda: player.kda,
|
||||||
|
criticalStrike: player.criticalStrike,
|
||||||
|
killingSpree: player.killingSpree,
|
||||||
|
doubleKills: player.doubleKills,
|
||||||
|
tripleKills: player.tripleKills,
|
||||||
|
quadraKills: player.quadraKills,
|
||||||
|
pentaKills: player.pentaKills,
|
||||||
|
heal: player.heal,
|
||||||
|
towers: player.turretKills,
|
||||||
|
longestLiving: player.timeSpentLiving,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public serializeOneMatch(match: Match, puuid: string, newMatch = false): SerializedMatch {
|
||||||
|
const identity = match.players.find((p) => p.summonerPuuid === puuid)!
|
||||||
|
const allyTeam = match.teams.find((t) => t.color === identity.team)!
|
||||||
|
|
||||||
|
const allyPlayers: MatchPlayer[] = []
|
||||||
|
const enemyPlayers: MatchPlayer[] = []
|
||||||
|
|
||||||
|
for (const p of match.players) {
|
||||||
|
p.team === identity.team ? allyPlayers.push(p) : enemyPlayers.push(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allyTeam: this.getTeamSummary(allyPlayers),
|
||||||
|
date: match.date,
|
||||||
|
enemyTeam: this.getTeamSummary(enemyPlayers),
|
||||||
|
matchId: match.id,
|
||||||
|
gamemode: match.gamemode,
|
||||||
|
map: match.map,
|
||||||
|
newMatch,
|
||||||
|
region: match.region,
|
||||||
|
result: allyTeam.result,
|
||||||
|
season: getSeasonNumber(match.date),
|
||||||
|
stats: this.getStats(identity),
|
||||||
|
time: match.gameDuration,
|
||||||
|
...this.getPlayerBase(identity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public serialize(matches: Match[], puuid: string, newMatches = false): SerializedMatch[] {
|
||||||
|
return matches.map((match) => this.serializeOneMatch(match, puuid, newMatches))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new BasicMatchSerializer()
|
||||||
153
server/app/Serializers/DetailedMatchSerializer.ts
Normal file
153
server/app/Serializers/DetailedMatchSerializer.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { sortTeamByRole } from 'App/helpers'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import MatchTeam from 'App/Models/MatchTeam'
|
||||||
|
import MatchSerializer from './MatchSerializer'
|
||||||
|
import {
|
||||||
|
SerializedDetailedMatch,
|
||||||
|
SerializedDetailedMatchBan,
|
||||||
|
SerializedDetailedMatchPlayer,
|
||||||
|
SerializedDetailedMatchStats,
|
||||||
|
SerializedDetailedMatchTeam,
|
||||||
|
SerializedDetailedMatchTeamStats,
|
||||||
|
} from './SerializedTypes'
|
||||||
|
|
||||||
|
class DetailedMatchSerializer extends MatchSerializer {
|
||||||
|
protected getTeamBans(team: MatchTeam): SerializedDetailedMatchBan[] {
|
||||||
|
if (!team.bans || !team.banOrders) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return team.bans.map((banId, index) => {
|
||||||
|
return {
|
||||||
|
champion: this.getChampion(banId),
|
||||||
|
championId: banId,
|
||||||
|
pickTurn: team.banOrders![index],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTeamStats(players: MatchPlayer[]): SerializedDetailedMatchTeamStats {
|
||||||
|
return players.reduce(
|
||||||
|
(acc, player) => {
|
||||||
|
acc.kills += player.kills
|
||||||
|
acc.deaths += player.deaths
|
||||||
|
acc.assists += player.assists
|
||||||
|
acc.gold += player.gold
|
||||||
|
acc.dmgChamp += player.damageDealtChampions
|
||||||
|
acc.dmgObj += player.damageDealtObjectives
|
||||||
|
acc.dmgTaken += player.damageTaken
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{ kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPlayersDetailed(
|
||||||
|
players: MatchPlayer[],
|
||||||
|
teamStats: SerializedDetailedMatchTeamStats,
|
||||||
|
gameDuration: number
|
||||||
|
): SerializedDetailedMatchPlayer[] {
|
||||||
|
return players
|
||||||
|
.map((player) => {
|
||||||
|
const stats: SerializedDetailedMatchStats = {
|
||||||
|
kills: player.kills,
|
||||||
|
deaths: player.deaths,
|
||||||
|
assists: player.assists,
|
||||||
|
minions: player.minions,
|
||||||
|
vision: player.visionScore,
|
||||||
|
gold: player.gold,
|
||||||
|
dmgChamp: player.damageDealtChampions,
|
||||||
|
dmgObj: player.damageDealtObjectives,
|
||||||
|
dmgTaken: player.damageTaken,
|
||||||
|
kp: player.kp.toFixed(1) + '%',
|
||||||
|
kda: player.kills + player.assists !== 0 && player.deaths === 0 ? '∞' : player.kda,
|
||||||
|
realKda: player.kda,
|
||||||
|
}
|
||||||
|
const percentStats = {
|
||||||
|
minions: +(player.minions / (gameDuration / 60)).toFixed(2),
|
||||||
|
vision: +(player.visionScore / (gameDuration / 60)).toFixed(2),
|
||||||
|
gold: +((player.gold * 100) / teamStats.gold).toFixed(1) + '%',
|
||||||
|
dmgChamp: +((player.damageDealtChampions * 100) / teamStats.dmgChamp).toFixed(1) + '%',
|
||||||
|
dmgObj:
|
||||||
|
+(
|
||||||
|
teamStats.dmgObj ? (player.damageDealtObjectives * 100) / teamStats.dmgObj : 0
|
||||||
|
).toFixed(1) + '%',
|
||||||
|
dmgTaken: +((player.damageTaken * 100) / teamStats.dmgTaken).toFixed(1) + '%',
|
||||||
|
}
|
||||||
|
const rank = player.ranks.length
|
||||||
|
? player.ranks.reduce((acc, rank) => {
|
||||||
|
acc[rank.gamemode] = this.getPlayerRank(rank)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
: undefined
|
||||||
|
return {
|
||||||
|
...this.getPlayerBase(player),
|
||||||
|
...this.getRuneIcons(player.perksSelected, player.perksSecondaryStyle),
|
||||||
|
id: player.id,
|
||||||
|
stats,
|
||||||
|
percentStats,
|
||||||
|
rank,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort(sortTeamByRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getTeamDetailed(
|
||||||
|
team: MatchTeam,
|
||||||
|
players: MatchPlayer[],
|
||||||
|
gameDuration: number
|
||||||
|
): SerializedDetailedMatchTeam {
|
||||||
|
const teamStats = this.getTeamStats(players)
|
||||||
|
|
||||||
|
return {
|
||||||
|
bans: this.getTeamBans(team),
|
||||||
|
barons: team.barons,
|
||||||
|
color: team.color === 100 ? 'Blue' : 'Red',
|
||||||
|
dragons: team.dragons,
|
||||||
|
inhibitors: team.inhibitors,
|
||||||
|
players: this.getPlayersDetailed(players, teamStats, gameDuration),
|
||||||
|
result: team.result,
|
||||||
|
riftHeralds: team.riftHeralds,
|
||||||
|
teamStats,
|
||||||
|
towers: team.towers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public serializeOneMatch(match: Match): { match: SerializedDetailedMatch; ranksLoaded: boolean } {
|
||||||
|
const blueTeam = match.teams.find((team) => team.color === 100)!
|
||||||
|
const redTeam = match.teams.find((team) => team.color === 200)!
|
||||||
|
|
||||||
|
const bluePlayers: MatchPlayer[] = []
|
||||||
|
const redPlayers: MatchPlayer[] = []
|
||||||
|
|
||||||
|
let ranksLoaded = false
|
||||||
|
|
||||||
|
for (const p of match.players) {
|
||||||
|
p.team === 100 ? bluePlayers.push(p) : redPlayers.push(p)
|
||||||
|
|
||||||
|
if (p.ranks.length) {
|
||||||
|
ranksLoaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const serializedMatch = {
|
||||||
|
blueTeam: this.getTeamDetailed(blueTeam, bluePlayers, match.gameDuration),
|
||||||
|
date: match.date,
|
||||||
|
matchId: match.id,
|
||||||
|
gamemode: match.gamemode,
|
||||||
|
map: match.map,
|
||||||
|
redTeam: this.getTeamDetailed(redTeam, redPlayers, match.gameDuration),
|
||||||
|
region: match.region,
|
||||||
|
season: match.season,
|
||||||
|
time: match.gameDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
match: serializedMatch,
|
||||||
|
ranksLoaded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DetailedMatchSerializer()
|
||||||
81
server/app/Serializers/LiveMatchSerializer.ts
Normal file
81
server/app/Serializers/LiveMatchSerializer.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { PlayerRole, queuesWithRole } from 'App/helpers'
|
||||||
|
import CDragonService from 'App/Services/CDragonService'
|
||||||
|
import { CurrentGameInfoDTO } from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
||||||
|
import { RoleComposition } from 'App/Services/RoleIdentificationService'
|
||||||
|
import SummonerService from 'App/Services/SummonerService'
|
||||||
|
import MatchSerializer from './MatchSerializer'
|
||||||
|
import { SerializedLiveMatch, SerializedLiveMatchPlayer } from './SerializedTypes'
|
||||||
|
|
||||||
|
class LiveMatchSerializer extends MatchSerializer {
|
||||||
|
public async serializeOneMatch(
|
||||||
|
liveMatch: CurrentGameInfoDTO,
|
||||||
|
region: string
|
||||||
|
): Promise<SerializedLiveMatch> {
|
||||||
|
// Roles
|
||||||
|
const blueTeam: PlayerRole[] = [] // 100
|
||||||
|
const redTeam: PlayerRole[] = [] // 200
|
||||||
|
let blueRoles: RoleComposition = {}
|
||||||
|
let redRoles: RoleComposition = {}
|
||||||
|
const needsRole =
|
||||||
|
CDragonService.championRoles &&
|
||||||
|
(queuesWithRole.includes(liveMatch.gameQueueConfigId) ||
|
||||||
|
(liveMatch.gameType === 'CUSTOM_GAME' && liveMatch.participants.length === 10))
|
||||||
|
|
||||||
|
if (needsRole) {
|
||||||
|
liveMatch.participants.map((p) => {
|
||||||
|
const playerRole = {
|
||||||
|
champion: p.championId,
|
||||||
|
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
||||||
|
}
|
||||||
|
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
|
||||||
|
})
|
||||||
|
|
||||||
|
blueRoles = super.getTeamRoles(blueTeam)
|
||||||
|
redRoles = super.getTeamRoles(redTeam)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranks
|
||||||
|
const requestsRanks = liveMatch.participants.map((p) =>
|
||||||
|
SummonerService.getRanked(p.summonerId, region)
|
||||||
|
)
|
||||||
|
const ranks = await Promise.all(requestsRanks)
|
||||||
|
|
||||||
|
// Players
|
||||||
|
const players: SerializedLiveMatchPlayer[] = liveMatch.participants.map((player, index) => {
|
||||||
|
let role: string | undefined
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
if (needsRole) {
|
||||||
|
const roles = player.teamId === 100 ? blueRoles : redRoles
|
||||||
|
role = Object.entries(roles).find(([, champion]) => player.championId === champion)![0]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...player,
|
||||||
|
role,
|
||||||
|
rank: ranks[index],
|
||||||
|
champion: this.getChampion(player.championId),
|
||||||
|
perks: {
|
||||||
|
primaryStyle: player.perks.perkStyle,
|
||||||
|
secondaryStyle: player.perks.perkSubStyle,
|
||||||
|
selected: player.perks.perkIds,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
gameId: liveMatch.gameId,
|
||||||
|
gameType: liveMatch.gameType,
|
||||||
|
gameStartTime: liveMatch.gameStartTime,
|
||||||
|
mapId: liveMatch.mapId,
|
||||||
|
gameLength: liveMatch.gameLength,
|
||||||
|
platformId: liveMatch.platformId,
|
||||||
|
gameMode: liveMatch.gameMode,
|
||||||
|
bannedChampions: liveMatch.bannedChampions,
|
||||||
|
gameQueueConfigId: liveMatch.gameQueueConfigId,
|
||||||
|
observers: liveMatch.observers,
|
||||||
|
participants: players,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LiveMatchSerializer()
|
||||||
20
server/app/Serializers/MatchPlayerRankSerializer.ts
Normal file
20
server/app/Serializers/MatchPlayerRankSerializer.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { PlayerRankParsed } from 'App/Parsers/ParsedType'
|
||||||
|
import MatchSerializer from './MatchSerializer'
|
||||||
|
import { SerializedPlayerRanksList } from './SerializedTypes'
|
||||||
|
|
||||||
|
class MatchPlayerRankSerializer extends MatchSerializer {
|
||||||
|
public serialize(ranks: PlayerRankParsed[]): SerializedPlayerRanksList {
|
||||||
|
const result = ranks.reduce((acc, rank) => {
|
||||||
|
if (!acc[rank.player_id]) {
|
||||||
|
acc[rank.player_id] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[rank.player_id][rank.gamemode] = this.getPlayerRank(rank)
|
||||||
|
return acc
|
||||||
|
}, {} as SerializedPlayerRanksList)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MatchPlayerRankSerializer()
|
||||||
135
server/app/Serializers/MatchSerializer.ts
Normal file
135
server/app/Serializers/MatchSerializer.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { PlayerRole } from 'App/helpers'
|
||||||
|
import MatchPlayer from 'App/Models/MatchPlayer'
|
||||||
|
import MatchPlayerRank from 'App/Models/MatchPlayerRank'
|
||||||
|
import { PlayerRankParsed, TeamPosition } from 'App/Parsers/ParsedType'
|
||||||
|
import CDragonService from 'App/Services/CDragonService'
|
||||||
|
import RoleIdentificationService, { RoleComposition } from 'App/Services/RoleIdentificationService'
|
||||||
|
import SummonerService from 'App/Services/SummonerService'
|
||||||
|
import {
|
||||||
|
SerializedBasePlayer,
|
||||||
|
SerializedMatchChampion,
|
||||||
|
SerializedMatchItem,
|
||||||
|
SerializedMatchPerks,
|
||||||
|
SerializedMatchSummonerSpell,
|
||||||
|
} from './SerializedTypes'
|
||||||
|
|
||||||
|
export default abstract class MatchSerializer {
|
||||||
|
/**
|
||||||
|
* Get champion specific data
|
||||||
|
* @param id of the champion
|
||||||
|
*/
|
||||||
|
public getChampion(id: number): SerializedMatchChampion {
|
||||||
|
const originalChampionData = CDragonService.champions[id]
|
||||||
|
const icon = CDragonService.createAssetUrl(originalChampionData.squarePortraitPath)
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon,
|
||||||
|
id: originalChampionData.id,
|
||||||
|
name: originalChampionData.name,
|
||||||
|
alias: originalChampionData.alias,
|
||||||
|
roles: originalChampionData.roles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Summoner Spell Data from CDragon
|
||||||
|
* @param id of the summonerSpell
|
||||||
|
*/
|
||||||
|
public getSummonerSpell(id: number): SerializedMatchSummonerSpell | null {
|
||||||
|
const spell = CDragonService.summonerSpells[id]
|
||||||
|
if (id === 0 || !spell) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: spell.name,
|
||||||
|
description: spell.description,
|
||||||
|
icon: CDragonService.createAssetUrl(spell.iconPath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getItems(player: MatchPlayer): Array<SerializedMatchItem | null> {
|
||||||
|
const items: (SerializedMatchItem | null)[] = []
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
const id = player['item' + i]
|
||||||
|
if (id === 0) {
|
||||||
|
items.push(null)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = CDragonService.items[id]
|
||||||
|
if (!item) {
|
||||||
|
items.push(null)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
image: CDragonService.createAssetUrl(item.iconPath),
|
||||||
|
name: item.name,
|
||||||
|
description: item.description,
|
||||||
|
price: item.priceTotal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPerks(player: MatchPlayer): SerializedMatchPerks {
|
||||||
|
return {
|
||||||
|
primaryStyle: player.perksPrimaryStyle,
|
||||||
|
secondaryStyle: player.perksSecondaryStyle,
|
||||||
|
selected: player.perksSelected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRuneIcons(perksSelected: number[], perksSecondaryStyle: number) {
|
||||||
|
const primaryRune = perksSelected.length ? CDragonService.perks[perksSelected[0]] : null
|
||||||
|
const secondaryRune = CDragonService.perkstyles[perksSecondaryStyle]
|
||||||
|
|
||||||
|
return {
|
||||||
|
primaryRune: primaryRune ? CDragonService.createAssetUrl(primaryRune.iconPath) : null,
|
||||||
|
secondaryRune: secondaryRune ? CDragonService.createAssetUrl(secondaryRune.iconPath) : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPlayerBase(player: MatchPlayer): SerializedBasePlayer {
|
||||||
|
return {
|
||||||
|
champion: this.getChampion(player.championId),
|
||||||
|
items: this.getItems(player),
|
||||||
|
level: player.champLevel,
|
||||||
|
name: player.summonerName,
|
||||||
|
perks: this.getPerks(player),
|
||||||
|
role: TeamPosition[player.teamPosition],
|
||||||
|
summonerId: player.summonerId,
|
||||||
|
summonerPuuid: player.summonerPuuid,
|
||||||
|
summonerSpell1: this.getSummonerSpell(player.summoner1Id),
|
||||||
|
summonerSpell2: this.getSummonerSpell(player.summoner2Id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPlayerRank(rank: PlayerRankParsed | MatchPlayerRank) {
|
||||||
|
return {
|
||||||
|
tier: rank.tier,
|
||||||
|
rank: rank.rank,
|
||||||
|
lp: rank.lp,
|
||||||
|
wins: rank.wins,
|
||||||
|
losses: rank.losses,
|
||||||
|
shortName: SummonerService.getRankedShortName(rank),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the 5 roles of a team based on champions
|
||||||
|
* @param team 5 champions + smite from a team
|
||||||
|
*/
|
||||||
|
protected getTeamRoles(team: PlayerRole[]): RoleComposition {
|
||||||
|
const teamJunglers = team.filter((p) => p.jungle && !p.support)
|
||||||
|
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
||||||
|
const teamSupports = team.filter((p) => p.support && !p.jungle)
|
||||||
|
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
||||||
|
|
||||||
|
return RoleIdentificationService.getRoles(
|
||||||
|
CDragonService.championRoles,
|
||||||
|
team.map((p) => p.champion),
|
||||||
|
jungle,
|
||||||
|
support
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { PerkDTO, PerkStyleDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
import { PerkDTO, PerkStyleDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
||||||
|
|
||||||
class RuneTransformer {
|
class RuneSerializer {
|
||||||
public transformPerks (perks: PerkDTO[]) {
|
public serializePerks(perks: PerkDTO[]) {
|
||||||
return perks.reduce((acc, perk) => {
|
return perks.reduce((acc, perk) => {
|
||||||
acc[perk.id] = {
|
acc[perk.id] = {
|
||||||
name: perk.name,
|
name: perk.name,
|
||||||
|
|
@ -12,18 +12,16 @@ class RuneTransformer {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
public transformStyles (styles: PerkStyleDTO[]) {
|
public serializeStyles(styles: PerkStyleDTO[]) {
|
||||||
return styles.reduce((acc, style) => {
|
return styles.reduce((acc, style) => {
|
||||||
acc[style.id] = {
|
acc[style.id] = {
|
||||||
name: style.name,
|
name: style.name,
|
||||||
icon: style.iconPath,
|
icon: style.iconPath,
|
||||||
slots: style.slots
|
slots: style.slots.filter((s) => s.type !== 'kStatMod').map((s) => s.perks),
|
||||||
.filter(s => s.type !== 'kStatMod')
|
|
||||||
.map(s => s.perks),
|
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new RuneTransformer()
|
export default new RuneSerializer()
|
||||||
211
server/app/Serializers/SerializedTypes.ts
Normal file
211
server/app/Serializers/SerializedTypes.ts
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
import {
|
||||||
|
CurrentGameInfoDTO,
|
||||||
|
GameCustomizationObjectDTO,
|
||||||
|
} from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
||||||
|
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
||||||
|
|
||||||
|
export interface SerializedBasePlayer {
|
||||||
|
champion: SerializedMatchChampion
|
||||||
|
items: Array<SerializedMatchItem | null>
|
||||||
|
level: number
|
||||||
|
name: string
|
||||||
|
perks: SerializedMatchPerks
|
||||||
|
role: string
|
||||||
|
summonerId: string
|
||||||
|
summonerPuuid: string
|
||||||
|
summonerSpell1: SerializedMatchSummonerSpell | null
|
||||||
|
summonerSpell2: SerializedMatchSummonerSpell | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatch extends SerializedBasePlayer {
|
||||||
|
allyTeam: SerializedMatchTeamPlayer[]
|
||||||
|
date: number
|
||||||
|
enemyTeam: SerializedMatchTeamPlayer[]
|
||||||
|
matchId: string
|
||||||
|
gamemode: number
|
||||||
|
map: number
|
||||||
|
newMatch: boolean
|
||||||
|
region: string
|
||||||
|
result: string
|
||||||
|
season: number
|
||||||
|
stats: SerializedMatchStats
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchTeamPlayer {
|
||||||
|
puuid: string
|
||||||
|
champion: SerializedMatchChampion
|
||||||
|
name: string
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchChampion {
|
||||||
|
alias: string
|
||||||
|
icon: string
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
roles: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchSummonerSpell {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchItem {
|
||||||
|
description: string
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
price: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchPerks {
|
||||||
|
primaryStyle: number
|
||||||
|
secondaryStyle: number
|
||||||
|
selected: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedMatchStats {
|
||||||
|
assists: number
|
||||||
|
criticalStrike: number
|
||||||
|
deaths: number
|
||||||
|
dmgChamp: number
|
||||||
|
dmgObj: number
|
||||||
|
dmgTaken: number
|
||||||
|
doubleKills: number
|
||||||
|
gold: number
|
||||||
|
heal: number
|
||||||
|
kda: number | string
|
||||||
|
killingSpree: number
|
||||||
|
kills: number
|
||||||
|
kp: number
|
||||||
|
longestLiving: number
|
||||||
|
minions: number
|
||||||
|
pentaKills: number
|
||||||
|
quadraKills: number
|
||||||
|
realKda: number
|
||||||
|
towers: number
|
||||||
|
tripleKills: number
|
||||||
|
vision: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================
|
||||||
|
|
||||||
|
Detailed Match
|
||||||
|
|
||||||
|
============================ */
|
||||||
|
export interface SerializedDetailedMatch {
|
||||||
|
blueTeam: SerializedDetailedMatchTeam
|
||||||
|
date: number
|
||||||
|
matchId: string
|
||||||
|
gamemode: number
|
||||||
|
map: number
|
||||||
|
redTeam: SerializedDetailedMatchTeam
|
||||||
|
region: string
|
||||||
|
season: number
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchTeam {
|
||||||
|
bans: SerializedDetailedMatchBan[]
|
||||||
|
barons: number
|
||||||
|
color: string
|
||||||
|
dragons: number
|
||||||
|
inhibitors: number
|
||||||
|
players: SerializedDetailedMatchPlayer[]
|
||||||
|
result: string
|
||||||
|
riftHeralds: number
|
||||||
|
teamStats: SerializedDetailedMatchTeamStats
|
||||||
|
towers: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchBan {
|
||||||
|
champion: SerializedMatchChampion
|
||||||
|
championId: number
|
||||||
|
pickTurn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchPlayer extends SerializedBasePlayer {
|
||||||
|
id: number
|
||||||
|
stats: SerializedDetailedMatchStats
|
||||||
|
percentStats: SerializedDetailedMatchPercentStats
|
||||||
|
primaryRune: string | null
|
||||||
|
secondaryRune: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchTeamStats {
|
||||||
|
assists: number
|
||||||
|
deaths: number
|
||||||
|
dmgChamp: number
|
||||||
|
dmgObj: number
|
||||||
|
dmgTaken: number
|
||||||
|
gold: number
|
||||||
|
kills: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchStats {
|
||||||
|
assists: number
|
||||||
|
deaths: number
|
||||||
|
dmgChamp: number
|
||||||
|
dmgObj: number
|
||||||
|
dmgTaken: number
|
||||||
|
gold: number
|
||||||
|
kda: string | number
|
||||||
|
kills: number
|
||||||
|
kp: string
|
||||||
|
minions: number
|
||||||
|
realKda: number
|
||||||
|
vision: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedDetailedMatchPercentStats {
|
||||||
|
dmgChamp: string
|
||||||
|
dmgObj: string
|
||||||
|
dmgTaken: string
|
||||||
|
gold: string
|
||||||
|
minions: number
|
||||||
|
vision: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedPlayerRanksList {
|
||||||
|
[summonerId: string]: SerializedPlayerRanks
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedPlayerRanks {
|
||||||
|
[gamemode: number]: SerializedPlayerRank
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedPlayerRank {
|
||||||
|
tier: string
|
||||||
|
rank: number
|
||||||
|
lp: number
|
||||||
|
wins: number
|
||||||
|
losses: number
|
||||||
|
shortName: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================
|
||||||
|
|
||||||
|
Live Match
|
||||||
|
|
||||||
|
============================ */
|
||||||
|
export interface SerializedLiveMatch extends Omit<CurrentGameInfoDTO, 'participants'> {
|
||||||
|
participants: SerializedLiveMatchPlayer[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedLiveMatchPlayer {
|
||||||
|
bot: boolean
|
||||||
|
champion: SerializedMatchChampion
|
||||||
|
championId: number
|
||||||
|
gameCustomizationObjects: GameCustomizationObjectDTO[]
|
||||||
|
perks: SerializedMatchPerks
|
||||||
|
profileIconId: number
|
||||||
|
rank: LeagueEntriesByQueue
|
||||||
|
role?: string
|
||||||
|
spell1Id: number
|
||||||
|
spell2Id: number
|
||||||
|
summonerId: string
|
||||||
|
summonerName: string
|
||||||
|
teamId: number
|
||||||
|
}
|
||||||
64
server/app/Services/CDragonService.ts
Normal file
64
server/app/Services/CDragonService.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import Jax from 'App/Services/Jax'
|
||||||
|
import {
|
||||||
|
ChampionDTO,
|
||||||
|
ItemDTO,
|
||||||
|
PerkDTO,
|
||||||
|
PerkStyleDTO,
|
||||||
|
SummonerSpellDTO,
|
||||||
|
} from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
||||||
|
import RoleIdentificationService, {
|
||||||
|
ChampionsPlayRate,
|
||||||
|
} from 'App/Services/RoleIdentificationService'
|
||||||
|
|
||||||
|
interface Identifiable {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CDragonCache<T> {
|
||||||
|
[id: string]: T
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDragonService {
|
||||||
|
public champions: CDragonCache<ChampionDTO>
|
||||||
|
public items: CDragonCache<ItemDTO>
|
||||||
|
public perks: CDragonCache<PerkDTO>
|
||||||
|
public perkstyles: CDragonCache<PerkStyleDTO>
|
||||||
|
public summonerSpells: CDragonCache<SummonerSpellDTO>
|
||||||
|
public championRoles: ChampionsPlayRate
|
||||||
|
|
||||||
|
public readonly BASE_URL =
|
||||||
|
'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/'
|
||||||
|
|
||||||
|
private setupCache<T extends Identifiable>(dto: T[]) {
|
||||||
|
return dto.reduce((obj, item) => ((obj[item.id] = item), obj), {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the full CDragon image path from the iconPath field
|
||||||
|
*/
|
||||||
|
public createAssetUrl(iconPath: string) {
|
||||||
|
const name = iconPath.split('/assets/')[1].toLowerCase()
|
||||||
|
return `${this.BASE_URL}${name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get global Context with CDragon Data
|
||||||
|
*/
|
||||||
|
public async getContext() {
|
||||||
|
const items = await Jax.CDragon.items()
|
||||||
|
const champions = await Jax.CDragon.champions()
|
||||||
|
const perks = await Jax.CDragon.perks()
|
||||||
|
const perkstyles = await Jax.CDragon.perkstyles()
|
||||||
|
const summonerSpells = await Jax.CDragon.summonerSpells()
|
||||||
|
const championRoles = await RoleIdentificationService.pullData().catch(() => {})
|
||||||
|
|
||||||
|
this.champions = this.setupCache(champions)
|
||||||
|
this.items = this.setupCache(items)
|
||||||
|
this.perks = this.setupCache(perks)
|
||||||
|
this.perkstyles = this.setupCache(perkstyles.styles)
|
||||||
|
this.summonerSpells = this.setupCache(summonerSpells)
|
||||||
|
this.championRoles = championRoles as ChampionsPlayRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CDragonService()
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import Env from '@ioc:Adonis/Core/Env'
|
import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
|
||||||
export interface JaxConfig {
|
export interface JaxConfig {
|
||||||
key: string,
|
key: string
|
||||||
region: string,
|
region: string
|
||||||
requestOptions: JaxConfigRequestOptions
|
requestOptions: JaxConfigRequestOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JaxConfigRequestOptions {
|
export interface JaxConfigRequestOptions {
|
||||||
retriesBeforeAbort: number,
|
retriesBeforeAbort: number
|
||||||
delayBeforeRetry: number,
|
delayBeforeRetry: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JAX_CONFIG: JaxConfig = {
|
export const JAX_CONFIG: JaxConfig = {
|
||||||
key: Env.get('API_KEY') as string,
|
key: Env.get('RIOT_API_KEY') as string,
|
||||||
region: 'euw1',
|
region: 'euw1',
|
||||||
requestOptions: {
|
requestOptions: {
|
||||||
retriesBeforeAbort: 3,
|
retriesBeforeAbort: 3,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default class CDragonRequest {
|
||||||
private retries: number
|
private retries: number
|
||||||
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
|
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
|
||||||
|
|
||||||
constructor (config: JaxConfig, endpoint: string, cacheTime: number) {
|
constructor(config: JaxConfig, endpoint: string, cacheTime: number) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.cacheTime = cacheTime
|
this.cacheTime = cacheTime
|
||||||
|
|
@ -26,7 +26,7 @@ export default class CDragonRequest {
|
||||||
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json
|
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json
|
||||||
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json
|
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json
|
||||||
|
|
||||||
public async execute () {
|
public async execute() {
|
||||||
const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}`
|
const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}`
|
||||||
|
|
||||||
const requestCached = await Redis.get(url)
|
const requestCached = await Redis.get(url)
|
||||||
|
|
|
||||||
|
|
@ -2,103 +2,103 @@ import { JaxConfig } from '../../JaxConfig'
|
||||||
import CDragonRequest from '../CDragonRequest'
|
import CDragonRequest from '../CDragonRequest'
|
||||||
|
|
||||||
export interface ChampionDTO {
|
export interface ChampionDTO {
|
||||||
id: number,
|
id: number
|
||||||
name: string,
|
name: string
|
||||||
alias: string,
|
alias: string
|
||||||
squarePortraitPath: string,
|
squarePortraitPath: string
|
||||||
roles: string[]
|
roles: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemDTO {
|
export interface ItemDTO {
|
||||||
id: number,
|
id: number
|
||||||
name: string,
|
name: string
|
||||||
description: string,
|
description: string
|
||||||
active: boolean,
|
active: boolean
|
||||||
inStore: boolean,
|
inStore: boolean
|
||||||
from: number[],
|
from: number[]
|
||||||
to: number[],
|
to: number[]
|
||||||
categories: string[],
|
categories: string[]
|
||||||
mapStringIdInclusions: string[],
|
mapStringIdInclusions: string[]
|
||||||
maxStacks: number,
|
maxStacks: number
|
||||||
modeNameInclusions: string[],
|
modeNameInclusions: string[]
|
||||||
requiredChampion: string,
|
requiredChampion: string
|
||||||
requiredAlly: string,
|
requiredAlly: string
|
||||||
requiredBuffCurrencyName: string,
|
requiredBuffCurrencyName: string
|
||||||
requiredBuffCurrencyCost: number,
|
requiredBuffCurrencyCost: number
|
||||||
specialRecipe: number,
|
specialRecipe: number
|
||||||
isEnchantment: boolean,
|
isEnchantment: boolean
|
||||||
price: number,
|
price: number
|
||||||
priceTotal: number,
|
priceTotal: number
|
||||||
iconPath: string
|
iconPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PerkDTO {
|
export interface PerkDTO {
|
||||||
id: number,
|
id: number
|
||||||
name: string,
|
name: string
|
||||||
majorChangePatchVersion: string,
|
majorChangePatchVersion: string
|
||||||
tooltip: string,
|
tooltip: string
|
||||||
shortDesc: string,
|
shortDesc: string
|
||||||
longDesc: string,
|
longDesc: string
|
||||||
iconPath: string,
|
iconPath: string
|
||||||
endOfGameStatDescs: string[]
|
endOfGameStatDescs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PerkStyleResponse {
|
export interface PerkStyleResponse {
|
||||||
schemaVersion: string,
|
schemaVersion: string
|
||||||
styles: PerkStyleDTO[]
|
styles: PerkStyleDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PerkStyleDTO {
|
export interface PerkStyleDTO {
|
||||||
id: number,
|
id: number
|
||||||
name: string,
|
name: string
|
||||||
tooltip: string,
|
tooltip: string
|
||||||
iconPath: string,
|
iconPath: string
|
||||||
assetMap: { [key: string]: string },
|
assetMap: { [key: string]: string }
|
||||||
isAdvanced: boolean,
|
isAdvanced: boolean
|
||||||
allowedSubStyles: number[],
|
allowedSubStyles: number[]
|
||||||
subStyleBonus: { styleId: number, perkId: number }[],
|
subStyleBonus: { styleId: number; perkId: number }[]
|
||||||
slots: { type: string, slotLabel: string, perks: number[] }[],
|
slots: { type: string; slotLabel: string; perks: number[] }[]
|
||||||
defaultPageName: string,
|
defaultPageName: string
|
||||||
defaultSubStyle: number,
|
defaultSubStyle: number
|
||||||
defaultPerks: number[],
|
defaultPerks: number[]
|
||||||
defaultPerksWhenSplashed: number[],
|
defaultPerksWhenSplashed: number[]
|
||||||
defaultStatModsPerSubStyle: { id: string, perks: number[] }[]
|
defaultStatModsPerSubStyle: { id: string; perks: number[] }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SummonerSpellDTO {
|
export interface SummonerSpellDTO {
|
||||||
id: number,
|
id: number
|
||||||
name: string,
|
name: string
|
||||||
description: string,
|
description: string
|
||||||
summonerLevel: number,
|
summonerLevel: number
|
||||||
cooldown: number,
|
cooldown: number
|
||||||
gameModes: string[],
|
gameModes: string[]
|
||||||
iconPath: string
|
iconPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CDragonEndpoint {
|
export default class CDragonEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
|
|
||||||
constructor (config: JaxConfig) {
|
constructor(config: JaxConfig) {
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
public async champions (): Promise<ChampionDTO[]> {
|
public async champions(): Promise<ChampionDTO[]> {
|
||||||
return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute()
|
return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async items (): Promise<ItemDTO[]> {
|
public async items(): Promise<ItemDTO[]> {
|
||||||
return new CDragonRequest(this.config, 'items.json', 36000).execute()
|
return new CDragonRequest(this.config, 'items.json', 36000).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async perks (): Promise<PerkDTO[]> {
|
public async perks(): Promise<PerkDTO[]> {
|
||||||
return new CDragonRequest(this.config, 'perks.json', 36000).execute()
|
return new CDragonRequest(this.config, 'perks.json', 36000).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async perkstyles (): Promise<PerkStyleResponse> {
|
public async perkstyles(): Promise<PerkStyleResponse> {
|
||||||
return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute()
|
return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
public async summonerSpells (): Promise<SummonerSpellDTO[]> {
|
public async summonerSpells(): Promise<SummonerSpellDTO[]> {
|
||||||
return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute()
|
return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,26 @@ import { JaxConfig } from '../../JaxConfig'
|
||||||
import JaxRequest from '../JaxRequest'
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
export interface LeagueEntryDTO {
|
export interface LeagueEntryDTO {
|
||||||
leagueId: string;
|
leagueId: string
|
||||||
queueType: string;
|
queueType: string
|
||||||
tier: string;
|
tier: string
|
||||||
rank: string;
|
rank: string
|
||||||
summonerId: string;
|
summonerId: string
|
||||||
summonerName: string;
|
summonerName: string
|
||||||
leaguePoints: number;
|
leaguePoints: number
|
||||||
wins: number;
|
wins: number
|
||||||
losses: number;
|
losses: number
|
||||||
veteran: boolean;
|
veteran: boolean
|
||||||
inactive: boolean;
|
inactive: boolean
|
||||||
freshBlood: boolean;
|
freshBlood: boolean
|
||||||
hotStreak: boolean;
|
hotStreak: boolean
|
||||||
miniSeries?: MiniSeriesDTO
|
miniSeries?: MiniSeriesDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MiniSeriesDTO {
|
interface MiniSeriesDTO {
|
||||||
losses: number,
|
losses: number
|
||||||
progress: string,
|
progress: string
|
||||||
target: number,
|
target: number
|
||||||
wins: number
|
wins: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,12 +31,12 @@ export default class LeagueEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
private limiter: RiotRateLimiter
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.limiter = limiter
|
this.limiter = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
public summonerID (summonerID: string, region: string): Promise<LeagueEntryDTO[]> {
|
public summonerID(summonerID: string, region: string): Promise<LeagueEntryDTO[]> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
region,
|
||||||
this.config,
|
this.config,
|
||||||
|
|
|
||||||
|
|
@ -1,231 +1,216 @@
|
||||||
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
import { getRiotRegion } from 'App/helpers'
|
||||||
import RiotRateLimiter from 'riot-ratelimiter'
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
import { JaxConfig } from '../../JaxConfig'
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
import JaxRequest from '../JaxRequest'
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
export interface MatchDto {
|
export interface MatchDto {
|
||||||
gameId: number,
|
metadata: MetadataDto
|
||||||
participantIdentities: ParticipantIdentityDto[],
|
info: InfoDto
|
||||||
queueId: number,
|
}
|
||||||
gameType: string,
|
|
||||||
gameDuration: number,
|
export interface MetadataDto {
|
||||||
teams: TeamStatsDto[],
|
dataVersion: string
|
||||||
|
matchId: string
|
||||||
|
participants: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InfoDto {
|
||||||
|
gameCreation: number
|
||||||
|
gameDuration: number
|
||||||
|
gameId: number
|
||||||
|
gameMode: string
|
||||||
|
gameName: string
|
||||||
|
gameStartTimestamp: number
|
||||||
|
gameType: string
|
||||||
|
gameVersion: string
|
||||||
|
mapId: number
|
||||||
|
participants: ParticipantDto[]
|
||||||
platformId: string
|
platformId: string
|
||||||
gameCreation: number,
|
queueId: number
|
||||||
seasonId: number,
|
teams: TeamDto[]
|
||||||
gameVersion: string,
|
tournamentCode?: string
|
||||||
mapId: number,
|
|
||||||
gameMode: string,
|
|
||||||
participants: ParticipantDto[],
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParticipantIdentityDto {
|
|
||||||
participantId: number,
|
|
||||||
player: PlayerDto
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlayerDto {
|
|
||||||
profileIcon: number,
|
|
||||||
accountId: string,
|
|
||||||
matchHistoryUri: string,
|
|
||||||
currentAccountId: string,
|
|
||||||
currentPlatformId: string,
|
|
||||||
summonerName: string,
|
|
||||||
summonerId: string,
|
|
||||||
platformId: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamStatsDto {
|
|
||||||
towerKills: number,
|
|
||||||
riftHeraldKills: number,
|
|
||||||
firstBlood: boolean,
|
|
||||||
inhibitorKills: number,
|
|
||||||
bans: TeamBansDto[],
|
|
||||||
firstBaron: boolean,
|
|
||||||
firstDragon: boolean,
|
|
||||||
dominionVictoryScore: number,
|
|
||||||
dragonKills: number,
|
|
||||||
baronKills: number,
|
|
||||||
firstInhibitor: boolean
|
|
||||||
firstTower: boolean
|
|
||||||
vilemawKills: number,
|
|
||||||
firstRiftHerald: boolean
|
|
||||||
teamId: number, // 100 for blue side. 200 for red side.
|
|
||||||
win: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeamBansDto {
|
|
||||||
championId: number,
|
|
||||||
pickTurn: number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParticipantDto {
|
export interface ParticipantDto {
|
||||||
participantId: number,
|
assists: number
|
||||||
championId: number,
|
baronKills: number
|
||||||
runes: RuneDto[],
|
bountyLevel: number
|
||||||
stats: ParticipantStatsDto,
|
champExperience: number
|
||||||
teamId: number,
|
champLevel: number
|
||||||
timeline: ParticipantTimelineDto,
|
championId: number
|
||||||
spell1Id: number,
|
championName: string
|
||||||
spell2Id: number,
|
championTransform: ChampionTransformDto
|
||||||
highestAchievedSeasonTier?:
|
consumablesPurchased: number
|
||||||
'CHALLENGER' | 'MASTER' | 'DIAMOND' | 'PLATINUM' | 'GOLD' | 'SILVER' | 'BRONZE' | 'UNRANKED',
|
damageDealtToObjectives: number
|
||||||
masteries: MasteryDto[]
|
damageDealtToTurrets: number
|
||||||
}
|
damageSelfMitigated: number
|
||||||
|
deaths: number
|
||||||
export interface RuneDto {
|
detectorWardsPlaced: number
|
||||||
runeId: number,
|
doubleKills: number
|
||||||
rank: number,
|
dragonKills: number
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParticipantStatsDto {
|
|
||||||
item0: number,
|
|
||||||
item2: number,
|
|
||||||
totalUnitsHealed: number,
|
|
||||||
item1: number,
|
|
||||||
largestMultiKill: number,
|
|
||||||
goldEarned: number,
|
|
||||||
firstInhibitorKill: boolean,
|
|
||||||
physicalDamageTaken: number,
|
|
||||||
nodeNeutralizeAssist: number,
|
|
||||||
totalPlayerScore: number,
|
|
||||||
champLevel: number,
|
|
||||||
damageDealtToObjectives: number,
|
|
||||||
totalDamageTaken: number,
|
|
||||||
neutralMinionsKilled: number,
|
|
||||||
deaths: number,
|
|
||||||
tripleKills: number,
|
|
||||||
magicDamageDealtToChampions: number,
|
|
||||||
wardsKilled: number,
|
|
||||||
pentaKills: number,
|
|
||||||
damageSelfMitigated: number,
|
|
||||||
largestCriticalStrike: number,
|
|
||||||
nodeNeutralize: number,
|
|
||||||
totalTimeCrowdControlDealt: number,
|
|
||||||
firstTowerKill: boolean
|
|
||||||
magicDamageDealt: number,
|
|
||||||
totalScoreRank: number,
|
|
||||||
nodeCapture: number,
|
|
||||||
wardsPlaced: number,
|
|
||||||
totalDamageDealt: number,
|
|
||||||
timeCCingOthers: number,
|
|
||||||
magicalDamageTaken: number,
|
|
||||||
largestKillingSpree: number,
|
|
||||||
totalDamageDealtToChampions: number,
|
|
||||||
physicalDamageDealtToChampions: number,
|
|
||||||
neutralMinionsKilledTeamJungle: number,
|
|
||||||
totalMinionsKilled: number,
|
|
||||||
firstInhibitorAssist: boolean
|
|
||||||
visionWardsBoughtInGame: number,
|
|
||||||
objectivePlayerScore: number,
|
|
||||||
kills: number,
|
|
||||||
firstTowerAssist: boolean
|
|
||||||
combatPlayerScore: number,
|
|
||||||
inhibitorKills: number,
|
|
||||||
turretKills: number,
|
|
||||||
participantId: number,
|
|
||||||
trueDamageTaken: number,
|
|
||||||
firstBloodAssist: boolean
|
firstBloodAssist: boolean
|
||||||
nodeCaptureAssist: number,
|
firstBloodKill: boolean
|
||||||
assists: number,
|
firstTowerAssist: boolean
|
||||||
teamObjective: number,
|
firstTowerKill: boolean
|
||||||
altarsNeutralized: number,
|
gameEndedInEarlySurrender: boolean
|
||||||
goldSpent: number,
|
gameEndedInSurrender: boolean
|
||||||
damageDealtToTurrets: number,
|
goldEarned: number
|
||||||
altarsCaptured: number,
|
goldSpent: number
|
||||||
win: boolean,
|
individualPosition: 'Invalid' | TeamPositionDto // TODO
|
||||||
totalHeal: number,
|
inhibitorKills: number
|
||||||
unrealKills: number,
|
item0: number
|
||||||
visionScore: number,
|
item1: number
|
||||||
physicalDamageDealt: number,
|
item2: number
|
||||||
firstBloodKill: boolean,
|
item3: number
|
||||||
longestTimeSpentLiving: number,
|
item4: number
|
||||||
killingSprees: number,
|
item5: number
|
||||||
sightWardsBoughtInGame: number,
|
item6: number
|
||||||
trueDamageDealtToChampions: number,
|
itemsPurchased: number
|
||||||
neutralMinionsKilledEnemyJungle: number,
|
killingSprees: number
|
||||||
doubleKills: number,
|
kills: number
|
||||||
trueDamageDealt: number,
|
lane: LaneDto // TODO
|
||||||
quadraKills: number,
|
largestCriticalStrike: number
|
||||||
item4: number,
|
largestKillingSpree: number
|
||||||
item3: number,
|
largestMultiKill: number
|
||||||
item6: number,
|
longestTimeSpentLiving: number
|
||||||
item5: number,
|
magicDamageDealt: number
|
||||||
playerScore0: number,
|
magicDamageDealtToChampions: number
|
||||||
playerScore1: number,
|
magicDamageTaken: number
|
||||||
playerScore2: number,
|
neutralMinionsKilled: number
|
||||||
playerScore3: number,
|
nexusKills: number
|
||||||
playerScore4: number,
|
objectivesStolen: number
|
||||||
playerScore5: number,
|
objectivesStolenAssists: number
|
||||||
playerScore6: number,
|
participantId: number
|
||||||
playerScore7: number,
|
pentaKills: number
|
||||||
playerScore8: number,
|
perks: PerksDto
|
||||||
playerScore9: number,
|
physicalDamageDealt: number
|
||||||
perk0: number,
|
physicalDamageDealtToChampions: number
|
||||||
perk0Var1: number,
|
physicalDamageTaken: number
|
||||||
perk0Var2: number,
|
profileIcon: number
|
||||||
perk0Var3: number,
|
puuid: string
|
||||||
perk1: number,
|
quadraKills: number
|
||||||
perk1Var1: number,
|
riotIdName: string
|
||||||
perk1Var2: number,
|
riotIdTagline: string
|
||||||
perk1Var3: number,
|
role: RoleDto // TODO
|
||||||
perk2: number,
|
sightWardsBoughtInGame: number
|
||||||
perk2Var1: number,
|
spell1Casts: number
|
||||||
perk2Var2: number,
|
spell2Casts: number
|
||||||
perk2Var3: number,
|
spell3Casts: number
|
||||||
perk3: number,
|
spell4Casts: number
|
||||||
perk3Var1: number,
|
summoner1Casts: number
|
||||||
perk3Var2: number,
|
summoner1Id: number
|
||||||
perk3Var3: number,
|
summoner2Casts: number
|
||||||
perk4: number,
|
summoner2Id: number
|
||||||
perk4Var1: number,
|
summonerId: string
|
||||||
perk4Var2: number,
|
summonerLevel: number
|
||||||
perk4Var3: number,
|
summonerName: string
|
||||||
perk5: number,
|
teamEarlySurrendered: boolean
|
||||||
perk5Var1: number,
|
teamId: number
|
||||||
perk5Var2: number,
|
teamPosition: TeamPositionDto // TODO
|
||||||
perk5Var3: number,
|
timeCCingOthers: number
|
||||||
perkPrimaryStyle: number,
|
timePlayed: number
|
||||||
perkSubStyle: number,
|
totalDamageDealt: number
|
||||||
statPerk0: number,
|
totalDamageDealtToChampions: number
|
||||||
statPerk1: number,
|
totalDamageShieldedOnTeammates: number
|
||||||
statPerk2: number,
|
totalDamageTaken: number
|
||||||
|
totalHeal: number
|
||||||
|
totalHealsOnTeammates: number
|
||||||
|
totalMinionsKilled: number
|
||||||
|
totalTimeCCDealt: number
|
||||||
|
totalTimeSpentDead: number
|
||||||
|
totalUnitsHealed: number
|
||||||
|
tripleKills: number
|
||||||
|
trueDamageDealt: number
|
||||||
|
trueDamageDealtToChampions: number
|
||||||
|
trueDamageTaken: number
|
||||||
|
turretKills: number
|
||||||
|
unrealKills: number
|
||||||
|
visionScore: number
|
||||||
|
visionWardsBoughtInGame: number
|
||||||
|
wardsKilled: number
|
||||||
|
wardsPlaced: number
|
||||||
|
win: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParticipantTimelineDto {
|
export enum ChampionTransformDto {
|
||||||
participantId: number,
|
None,
|
||||||
csDiffPerMinDeltas: { [index: string]: number },
|
Slayer,
|
||||||
damageTakenPerMinDeltas: { [index: string]: number },
|
Assasin,
|
||||||
role: 'DUO' | 'NONE' | 'SOLO' | 'DUO_CARRY' | 'DUO_SUPPORT',
|
|
||||||
damageTakenDiffPerMinDeltas: { [index: string]: number },
|
|
||||||
xpPerMinDeltas: { [index: string]: number },
|
|
||||||
xpDiffPerMinDeltas: { [index: string]: number },
|
|
||||||
lane: 'MID' | 'MIDDLE' | 'TOP' | 'JUNGLE' | 'BOT' | 'BOTTOM',
|
|
||||||
creepsPerMinDeltas: { [index: string]: number },
|
|
||||||
goldPerMinDeltas: { [index: string]: number },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MasteryDto {
|
export type LaneDto = 'TOP' | 'JUNGLE' | 'MIDDLE' | 'BOTTOM'
|
||||||
rank: number,
|
|
||||||
masteryId: number,
|
export interface PerksDto {
|
||||||
|
statPerks: PerkStatsDto
|
||||||
|
styles: PerkStyleDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerkStatsDto {
|
||||||
|
defense: number
|
||||||
|
flex: number
|
||||||
|
offense: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerkStyleDto {
|
||||||
|
description: 'primaryStyle' | 'subStyle'
|
||||||
|
selections: PerkStyleSelectionDto[]
|
||||||
|
style: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerkStyleSelectionDto {
|
||||||
|
perk: number
|
||||||
|
var1: number
|
||||||
|
var2: number
|
||||||
|
var3: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoleDto = 'NONE' | 'DUO' | 'SOLO' | 'CARRY' | 'SUPPORT'
|
||||||
|
|
||||||
|
export type TeamPositionDto = 'TOP' | 'JUNGLE' | 'MIDDLE' | 'BOTTOM' | 'UTILITY'
|
||||||
|
|
||||||
|
export interface TeamDto {
|
||||||
|
bans: BanDto[]
|
||||||
|
objectives: ObjectivesDto
|
||||||
|
teamId: number
|
||||||
|
win: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BanDto {
|
||||||
|
championId: number
|
||||||
|
pickTurn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectivesDto {
|
||||||
|
baron: ObjectiveDto
|
||||||
|
champion: ObjectiveDto
|
||||||
|
dragon: ObjectiveDto
|
||||||
|
inhibitor: ObjectiveDto
|
||||||
|
riftHerald: ObjectiveDto
|
||||||
|
tower: ObjectiveDto
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectiveDto {
|
||||||
|
first: boolean
|
||||||
|
kills: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MatchEndpoint {
|
export default class MatchEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
private limiter: RiotRateLimiter
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.limiter = limiter
|
this.limiter = limiter
|
||||||
|
|
||||||
this.get = this.get.bind(this)
|
this.get = this.get.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
public get (matchID: number, region: string): Promise<MatchDto> {
|
public get(matchID: string, region: string): Promise<MatchDto> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
getRiotRegion(region),
|
||||||
this.config,
|
this.config,
|
||||||
`match/v4/matches/${matchID}`,
|
`match/v5/matches/${matchID}`,
|
||||||
this.limiter,
|
this.limiter,
|
||||||
1500
|
1500
|
||||||
).execute()
|
).execute()
|
||||||
|
|
|
||||||
240
server/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts
Normal file
240
server/app/Services/Jax/src/Endpoints/MatchV4Endpoint.ts
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
|
export interface V4MatchDto {
|
||||||
|
gameId: number
|
||||||
|
participantIdentities: V4ParticipantIdentityDto[]
|
||||||
|
queueId: number
|
||||||
|
gameType: string
|
||||||
|
gameDuration: number
|
||||||
|
teams: V4TeamStatsDto[]
|
||||||
|
platformId: string
|
||||||
|
gameCreation: number
|
||||||
|
seasonId: number
|
||||||
|
gameVersion: string
|
||||||
|
mapId: number
|
||||||
|
gameMode: string
|
||||||
|
participants: V4ParticipantDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4ParticipantIdentityDto {
|
||||||
|
participantId: number
|
||||||
|
player: V4PlayerDto
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4PlayerDto {
|
||||||
|
profileIcon: number
|
||||||
|
accountId: string
|
||||||
|
matchHistoryUri: string
|
||||||
|
currentAccountId: string
|
||||||
|
currentPlatformId: string
|
||||||
|
summonerName: string
|
||||||
|
summonerId: string
|
||||||
|
platformId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4TeamStatsDto {
|
||||||
|
towerKills: number
|
||||||
|
riftHeraldKills: number
|
||||||
|
firstBlood: boolean
|
||||||
|
inhibitorKills: number
|
||||||
|
bans: V4TeamBansDto[]
|
||||||
|
firstBaron: boolean
|
||||||
|
firstDragon: boolean
|
||||||
|
dominionVictoryScore: number
|
||||||
|
dragonKills: number
|
||||||
|
baronKills: number
|
||||||
|
firstInhibitor: boolean
|
||||||
|
firstTower: boolean
|
||||||
|
vilemawKills: number
|
||||||
|
firstRiftHerald: boolean
|
||||||
|
teamId: number // 100 for blue side. 200 for red side.
|
||||||
|
win: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4TeamBansDto {
|
||||||
|
championId: number
|
||||||
|
pickTurn: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4ParticipantDto {
|
||||||
|
participantId: number
|
||||||
|
championId: number
|
||||||
|
runes: V4RuneDto[]
|
||||||
|
stats: V4ParticipantStatsDto
|
||||||
|
teamId: number
|
||||||
|
timeline: V4ParticipantTimelineDto
|
||||||
|
spell1Id: number
|
||||||
|
spell2Id: number
|
||||||
|
highestAchievedSeasonTier?:
|
||||||
|
| 'CHALLENGER'
|
||||||
|
| 'MASTER'
|
||||||
|
| 'DIAMOND'
|
||||||
|
| 'PLATINUM'
|
||||||
|
| 'GOLD'
|
||||||
|
| 'SILVER'
|
||||||
|
| 'BRONZE'
|
||||||
|
| 'UNRANKED'
|
||||||
|
masteries: V4MasteryDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4RuneDto {
|
||||||
|
runeId: number
|
||||||
|
rank: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4ParticipantStatsDto {
|
||||||
|
item0: number
|
||||||
|
item2: number
|
||||||
|
totalUnitsHealed: number
|
||||||
|
item1: number
|
||||||
|
largestMultiKill: number
|
||||||
|
goldEarned: number
|
||||||
|
firstInhibitorKill: boolean
|
||||||
|
physicalDamageTaken: number
|
||||||
|
nodeNeutralizeAssist: number
|
||||||
|
totalPlayerScore: number
|
||||||
|
champLevel: number
|
||||||
|
damageDealtToObjectives: number
|
||||||
|
totalDamageTaken: number
|
||||||
|
neutralMinionsKilled: number
|
||||||
|
deaths: number
|
||||||
|
tripleKills: number
|
||||||
|
magicDamageDealtToChampions: number
|
||||||
|
wardsKilled: number
|
||||||
|
pentaKills: number
|
||||||
|
damageSelfMitigated: number
|
||||||
|
largestCriticalStrike: number
|
||||||
|
nodeNeutralize: number
|
||||||
|
totalTimeCrowdControlDealt: number
|
||||||
|
firstTowerKill: boolean
|
||||||
|
magicDamageDealt: number
|
||||||
|
totalScoreRank: number
|
||||||
|
nodeCapture: number
|
||||||
|
wardsPlaced: number
|
||||||
|
totalDamageDealt: number
|
||||||
|
timeCCingOthers: number
|
||||||
|
magicalDamageTaken: number
|
||||||
|
largestKillingSpree: number
|
||||||
|
totalDamageDealtToChampions: number
|
||||||
|
physicalDamageDealtToChampions: number
|
||||||
|
neutralMinionsKilledTeamJungle: number
|
||||||
|
totalMinionsKilled: number
|
||||||
|
firstInhibitorAssist: boolean
|
||||||
|
visionWardsBoughtInGame: number
|
||||||
|
objectivePlayerScore: number
|
||||||
|
kills: number
|
||||||
|
firstTowerAssist: boolean
|
||||||
|
combatPlayerScore: number
|
||||||
|
inhibitorKills: number
|
||||||
|
turretKills: number
|
||||||
|
participantId: number
|
||||||
|
trueDamageTaken: number
|
||||||
|
firstBloodAssist: boolean
|
||||||
|
nodeCaptureAssist: number
|
||||||
|
assists: number
|
||||||
|
teamObjective: number
|
||||||
|
altarsNeutralized: number
|
||||||
|
goldSpent: number
|
||||||
|
damageDealtToTurrets: number
|
||||||
|
altarsCaptured: number
|
||||||
|
win: boolean
|
||||||
|
totalHeal: number
|
||||||
|
unrealKills: number
|
||||||
|
visionScore: number
|
||||||
|
physicalDamageDealt: number
|
||||||
|
firstBloodKill: boolean
|
||||||
|
longestTimeSpentLiving: number
|
||||||
|
killingSprees: number
|
||||||
|
sightWardsBoughtInGame: number
|
||||||
|
trueDamageDealtToChampions: number
|
||||||
|
neutralMinionsKilledEnemyJungle: number
|
||||||
|
doubleKills: number
|
||||||
|
trueDamageDealt: number
|
||||||
|
quadraKills: number
|
||||||
|
item4: number
|
||||||
|
item3: number
|
||||||
|
item6: number
|
||||||
|
item5: number
|
||||||
|
playerScore0: number
|
||||||
|
playerScore1: number
|
||||||
|
playerScore2: number
|
||||||
|
playerScore3: number
|
||||||
|
playerScore4: number
|
||||||
|
playerScore5: number
|
||||||
|
playerScore6: number
|
||||||
|
playerScore7: number
|
||||||
|
playerScore8: number
|
||||||
|
playerScore9: number
|
||||||
|
perk0: number
|
||||||
|
perk0Var1: number
|
||||||
|
perk0Var2: number
|
||||||
|
perk0Var3: number
|
||||||
|
perk1: number
|
||||||
|
perk1Var1: number
|
||||||
|
perk1Var2: number
|
||||||
|
perk1Var3: number
|
||||||
|
perk2: number
|
||||||
|
perk2Var1: number
|
||||||
|
perk2Var2: number
|
||||||
|
perk2Var3: number
|
||||||
|
perk3: number
|
||||||
|
perk3Var1: number
|
||||||
|
perk3Var2: number
|
||||||
|
perk3Var3: number
|
||||||
|
perk4: number
|
||||||
|
perk4Var1: number
|
||||||
|
perk4Var2: number
|
||||||
|
perk4Var3: number
|
||||||
|
perk5: number
|
||||||
|
perk5Var1: number
|
||||||
|
perk5Var2: number
|
||||||
|
perk5Var3: number
|
||||||
|
perkPrimaryStyle: number
|
||||||
|
perkSubStyle: number
|
||||||
|
statPerk0: number
|
||||||
|
statPerk1: number
|
||||||
|
statPerk2: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4ParticipantTimelineDto {
|
||||||
|
participantId: number
|
||||||
|
csDiffPerMinDeltas: { [index: string]: number }
|
||||||
|
damageTakenPerMinDeltas: { [index: string]: number }
|
||||||
|
role: 'DUO' | 'NONE' | 'SOLO' | 'DUO_CARRY' | 'DUO_SUPPORT'
|
||||||
|
damageTakenDiffPerMinDeltas: { [index: string]: number }
|
||||||
|
xpPerMinDeltas: { [index: string]: number }
|
||||||
|
xpDiffPerMinDeltas: { [index: string]: number }
|
||||||
|
lane: 'MID' | 'MIDDLE' | 'TOP' | 'JUNGLE' | 'BOT' | 'BOTTOM'
|
||||||
|
creepsPerMinDeltas: { [index: string]: number }
|
||||||
|
goldPerMinDeltas: { [index: string]: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4MasteryDto {
|
||||||
|
rank: number
|
||||||
|
masteryId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MatchV4Endpoint {
|
||||||
|
private config: JaxConfig
|
||||||
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
|
this.config = config
|
||||||
|
this.limiter = limiter
|
||||||
|
|
||||||
|
this.get = this.get.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(matchID: number, region: string): Promise<V4MatchDto> {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`match/v4/matches/${matchID}`,
|
||||||
|
this.limiter,
|
||||||
|
1500
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,41 +1,52 @@
|
||||||
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
import { getRiotRegion } from 'App/helpers'
|
||||||
import RiotRateLimiter from 'riot-ratelimiter'
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
import { JaxConfig } from '../../JaxConfig'
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
import JaxRequest from '../JaxRequest'
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
export interface MatchlistDto {
|
// export interface MatchlistDto {
|
||||||
startIndex: number,
|
// startIndex: number,
|
||||||
totalGames: number,
|
// totalGames: number,
|
||||||
endIndex: number,
|
// endIndex: number,
|
||||||
matches: MatchReferenceDto[]
|
// matches: MatchReferenceDto[]
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface MatchReferenceDto {
|
// export interface MatchReferenceDto {
|
||||||
gameId: number,
|
// gameId: number,
|
||||||
role: string,
|
// role: string,
|
||||||
season: number,
|
// season: number,
|
||||||
platformId: string,
|
// platformId: string,
|
||||||
champion: number,
|
// champion: number,
|
||||||
queue: number,
|
// queue: number,
|
||||||
lane: string,
|
// lane: string,
|
||||||
timestamp: number,
|
// timestamp: number,
|
||||||
seasonMatch?: number
|
// seasonMatch?: number
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* ===============================================
|
||||||
|
* V5
|
||||||
|
* ===============================================
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type MatchlistDto = string[]
|
||||||
|
|
||||||
export default class MatchlistEndpoint {
|
export default class MatchlistEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
private limiter: RiotRateLimiter
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.limiter = limiter
|
this.limiter = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
public accountID (accountID: string, region: string, beginIndex = 0): Promise<MatchlistDto> {
|
public puuid(puuid: string, region: string, beginIndex = 0, count = 100): Promise<MatchlistDto> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
getRiotRegion(region),
|
||||||
this.config,
|
this.config,
|
||||||
`match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`,
|
`match/v5/matches/by-puuid/${puuid}/ids?start=${beginIndex}&count=${count}`,
|
||||||
this.limiter,
|
this.limiter,
|
||||||
0
|
0
|
||||||
).execute()
|
).execute()
|
||||||
|
|
|
||||||
43
server/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts
Normal file
43
server/app/Services/Jax/src/Endpoints/MatchlistV4Endpoint.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
|
export interface V4MatchlistDto {
|
||||||
|
startIndex: number
|
||||||
|
totalGames: number
|
||||||
|
endIndex: number
|
||||||
|
matches: V4MatchReferenceDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface V4MatchReferenceDto {
|
||||||
|
gameId: number
|
||||||
|
role: string
|
||||||
|
season: number
|
||||||
|
platformId: string
|
||||||
|
champion: number
|
||||||
|
queue: number
|
||||||
|
lane: string
|
||||||
|
timestamp: number
|
||||||
|
seasonMatch?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MatchlistV4Endpoint {
|
||||||
|
private config: JaxConfig
|
||||||
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
|
this.config = config
|
||||||
|
this.limiter = limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
public accountID(accountID: string, region: string, beginIndex = 0): Promise<V4MatchlistDto> {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`,
|
||||||
|
this.limiter,
|
||||||
|
0
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,59 +1,53 @@
|
||||||
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
import RiotRateLimiter from 'riot-ratelimiter'
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
|
||||||
import { JaxConfig } from '../../JaxConfig'
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
import JaxRequest from '../JaxRequest'
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
export interface CurrentGameInfo {
|
export interface CurrentGameInfoDTO {
|
||||||
gameId: number,
|
gameId: number
|
||||||
gameType: string
|
gameType: string
|
||||||
gameStartTime: number,
|
gameStartTime: number
|
||||||
mapId: number,
|
mapId: number
|
||||||
gameLength: number,
|
gameLength: number
|
||||||
platformId: string,
|
platformId: string
|
||||||
gameMode: string,
|
gameMode: string
|
||||||
bannedChampions: BannedChampion[],
|
bannedChampions: BannedChampionDTO[]
|
||||||
gameQueueConfigId: number,
|
gameQueueConfigId: number
|
||||||
observers: Observer,
|
observers: ObserverDTO
|
||||||
participants: CurrentGameParticipant[],
|
participants: CurrentGameParticipantDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BannedChampion {
|
export interface BannedChampionDTO {
|
||||||
pickTurn: number,
|
pickTurn: number
|
||||||
championId: number,
|
championId: number
|
||||||
teamId: number,
|
teamId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Observer {
|
export interface ObserverDTO {
|
||||||
encryptionKey: string,
|
encryptionKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentGameParticipant {
|
export interface CurrentGameParticipantDTO {
|
||||||
championId: number,
|
championId: number
|
||||||
perks: Perks,
|
perks: PerksDTO
|
||||||
profileIconId: number,
|
profileIconId: number
|
||||||
bot: boolean,
|
bot: boolean
|
||||||
teamId: number,
|
teamId: number
|
||||||
summonerName: string,
|
summonerName: string
|
||||||
summonerId: string,
|
summonerId: string
|
||||||
spell1Id: number,
|
spell1Id: number
|
||||||
spell2Id: number,
|
spell2Id: number
|
||||||
gameCustomizationObjects: GameCustomizationObject[],
|
gameCustomizationObjects: GameCustomizationObjectDTO[]
|
||||||
// Custom types from here
|
|
||||||
role?: string,
|
|
||||||
runes?: { primaryRune: string, secondaryRune: string } | {}
|
|
||||||
level?: number,
|
|
||||||
rank?: LeagueEntriesByQueue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Perks {
|
export interface PerksDTO {
|
||||||
perkIds: number[]
|
perkIds: number[]
|
||||||
perkStyle: number,
|
perkStyle: number
|
||||||
perkSubStyle: number
|
perkSubStyle: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GameCustomizationObject {
|
export interface GameCustomizationObjectDTO {
|
||||||
category: string,
|
category: string
|
||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,12 +55,12 @@ export default class SpectatorEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
private limiter: RiotRateLimiter
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.limiter = limiter
|
this.limiter = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
public summonerID (summonerID: string, region: string) {
|
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfoDTO | undefined> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
region,
|
||||||
this.config,
|
this.config,
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,35 @@ import { JaxConfig } from '../../JaxConfig'
|
||||||
import JaxRequest from '../JaxRequest'
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
export interface SummonerDTO {
|
export interface SummonerDTO {
|
||||||
accountId: string,
|
accountId: string
|
||||||
profileIconId: number,
|
profileIconId: number
|
||||||
revisionDate: number,
|
revisionDate: number
|
||||||
name: string,
|
name: string
|
||||||
id: string,
|
id: string
|
||||||
puuid: string,
|
puuid: string
|
||||||
summonerLevel: number,
|
summonerLevel: number
|
||||||
region?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SummonerEndpoint {
|
export default class SummonerEndpoint {
|
||||||
private config: JaxConfig
|
private config: JaxConfig
|
||||||
private limiter: RiotRateLimiter
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.limiter = limiter
|
this.limiter = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
public summonerId (summonerId: string, region: string): Promise<SummonerDTO> {
|
public accountId(accountId: string, region: string): Promise<SummonerDTO> {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`summoner/v4/summoners/by-account/${accountId}`,
|
||||||
|
this.limiter,
|
||||||
|
36000
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
public summonerId(summonerId: string, region: string): Promise<SummonerDTO> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
region,
|
||||||
this.config,
|
this.config,
|
||||||
|
|
@ -33,7 +42,7 @@ export default class SummonerEndpoint {
|
||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
public summonerName (summonerName: string, region: string): Promise<SummonerDTO> {
|
public summonerName(summonerName: string, region: string): Promise<SummonerDTO> {
|
||||||
return new JaxRequest(
|
return new JaxRequest(
|
||||||
region,
|
region,
|
||||||
this.config,
|
this.config,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import { JaxConfig } from '../JaxConfig'
|
||||||
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
import RiotRateLimiter from 'riot-ratelimiter'
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
import { STRATEGY } from 'riot-ratelimiter/dist/RateLimiter'
|
import { STRATEGY } from 'riot-ratelimiter/dist/RateLimiter'
|
||||||
|
import MatchV4Endpoint from './Endpoints/MatchV4Endpoint'
|
||||||
|
import MatchlistV4Endpoint from './Endpoints/MatchlistV4Endpoint'
|
||||||
|
|
||||||
export default class Jax {
|
export default class Jax {
|
||||||
public key: string
|
public key: string
|
||||||
|
|
@ -15,12 +17,14 @@ export default class Jax {
|
||||||
public config: JaxConfig
|
public config: JaxConfig
|
||||||
public League: LeagueEndpoint
|
public League: LeagueEndpoint
|
||||||
public Match: MatchEndpoint
|
public Match: MatchEndpoint
|
||||||
|
public MatchV4: MatchV4Endpoint
|
||||||
public Matchlist: MatchlistEndpoint
|
public Matchlist: MatchlistEndpoint
|
||||||
|
public MatchlistV4: MatchlistV4Endpoint
|
||||||
public Spectator: SpectatorEndpoint
|
public Spectator: SpectatorEndpoint
|
||||||
public Summoner: SummonerEndpoint
|
public Summoner: SummonerEndpoint
|
||||||
public CDragon: CDragonEndpoint
|
public CDragon: CDragonEndpoint
|
||||||
|
|
||||||
constructor (config:JaxConfig) {
|
constructor(config: JaxConfig) {
|
||||||
this.key = config.key
|
this.key = config.key
|
||||||
// this.limiter = new RiotRateLimiter({
|
// this.limiter = new RiotRateLimiter({
|
||||||
// debug: true,
|
// debug: true,
|
||||||
|
|
@ -34,7 +38,9 @@ export default class Jax {
|
||||||
|
|
||||||
this.League = new LeagueEndpoint(this.config, this.limiter)
|
this.League = new LeagueEndpoint(this.config, this.limiter)
|
||||||
this.Match = new MatchEndpoint(this.config, this.limiter)
|
this.Match = new MatchEndpoint(this.config, this.limiter)
|
||||||
|
this.MatchV4 = new MatchV4Endpoint(this.config, this.limiter)
|
||||||
this.Matchlist = new MatchlistEndpoint(this.config, this.limiter)
|
this.Matchlist = new MatchlistEndpoint(this.config, this.limiter)
|
||||||
|
this.MatchlistV4 = new MatchlistV4Endpoint(this.config, this.limiter)
|
||||||
this.Spectator = new SpectatorEndpoint(this.config, this.limiter)
|
this.Spectator = new SpectatorEndpoint(this.config, this.limiter)
|
||||||
this.Summoner = new SummonerEndpoint(this.config, this.limiter)
|
this.Summoner = new SummonerEndpoint(this.config, this.limiter)
|
||||||
this.CDragon = new CDragonEndpoint(this.config)
|
this.CDragon = new CDragonEndpoint(this.config)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,13 @@ export default class JaxRequest {
|
||||||
private retries: number
|
private retries: number
|
||||||
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
|
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
|
||||||
|
|
||||||
constructor (region: string, config: JaxConfig, endpoint: string, limiter: RiotRateLimiter, cacheTime: number) {
|
constructor(
|
||||||
|
region: string,
|
||||||
|
config: JaxConfig,
|
||||||
|
endpoint: string,
|
||||||
|
limiter: RiotRateLimiter,
|
||||||
|
cacheTime: number
|
||||||
|
) {
|
||||||
this.region = region
|
this.region = region
|
||||||
this.config = config
|
this.config = config
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
|
|
@ -25,7 +31,7 @@ export default class JaxRequest {
|
||||||
this.sleep = promisify(setTimeout)
|
this.sleep = promisify(setTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async execute () {
|
public async execute() {
|
||||||
const url = `https://${this.region}.api.riotgames.com/lol/${this.endpoint}`
|
const url = `https://${this.region}.api.riotgames.com/lol/${this.endpoint}`
|
||||||
|
|
||||||
// Redis cache
|
// Redis cache
|
||||||
|
|
@ -37,16 +43,7 @@ export default class JaxRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const resp = await this.limiter.execute({
|
const resp: any = await this.limiter.executing({
|
||||||
// url,
|
|
||||||
// options: {
|
|
||||||
// headers: {
|
|
||||||
// 'X-Riot-Token': this.config.key,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
|
|
||||||
const resp:any = await this.limiter.executing({
|
|
||||||
url,
|
url,
|
||||||
token: this.config.key,
|
token: this.config.key,
|
||||||
resolveWithFullResponse: false,
|
resolveWithFullResponse: false,
|
||||||
|
|
@ -56,17 +53,28 @@ export default class JaxRequest {
|
||||||
await Redis.setex(url, this.cacheTime, resp)
|
await Redis.setex(url, this.cacheTime, resp)
|
||||||
}
|
}
|
||||||
return JSON.parse(resp)
|
return JSON.parse(resp)
|
||||||
} catch ({ statusCode , ...rest }) {
|
} catch ({ statusCode, ...rest }) {
|
||||||
this.retries--
|
this.retries--
|
||||||
|
|
||||||
if (statusCode !== 500 && statusCode !== 503 && statusCode !== 504) { //
|
console.log('JAX ERROR')
|
||||||
|
console.log(rest?.cause?.code)
|
||||||
|
|
||||||
|
if (
|
||||||
|
statusCode !== 500 &&
|
||||||
|
statusCode !== 503 &&
|
||||||
|
statusCode !== 504 &&
|
||||||
|
rest?.cause?.code !== 'ETIMEDOUT'
|
||||||
|
) {
|
||||||
|
//
|
||||||
// Don't log 404 when summoner isn't playing or the summoner doesn't exist
|
// Don't log 404 when summoner isn't playing or the summoner doesn't exist
|
||||||
// Or if summoner has no MatchList
|
// Or if summoner has no MatchList
|
||||||
if (!this.endpoint.includes('spectator/v4/active-games/by-summoner') &&
|
if (
|
||||||
|
!this.endpoint.includes('spectator/v4/active-games/by-summoner') &&
|
||||||
!this.endpoint.includes('summoner/v4/summoners/by-name') &&
|
!this.endpoint.includes('summoner/v4/summoners/by-name') &&
|
||||||
!this.endpoint.includes('match/v4/matchlists/by-account')
|
!this.endpoint.includes('match/v4/matchlists/by-account')
|
||||||
) {
|
) {
|
||||||
Logger.error(`JaxRequest Error ${statusCode}: `, rest)
|
Logger.error(`URL ${url}: `)
|
||||||
|
// Logger.error(`JaxRequest Error ${statusCode}: `, rest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,142 +1,138 @@
|
||||||
import Jax from './Jax'
|
import Jax from './Jax'
|
||||||
import Logger from '@ioc:Adonis/Core/Logger'
|
import { MatchlistDto } from './Jax/src/Endpoints/MatchlistEndpoint'
|
||||||
import { getSeasonNumber } from 'App/helpers'
|
|
||||||
import { MatchReferenceDto } from './Jax/src/Endpoints/MatchlistEndpoint'
|
|
||||||
import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint'
|
import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint'
|
||||||
import { SummonerModel } from 'App/Models/Summoner'
|
import Summoner from 'App/Models/Summoner'
|
||||||
import Match, { MatchModel } from 'App/Models/Match'
|
import Database from '@ioc:Adonis/Lucid/Database'
|
||||||
import BasicMatchTransformer from 'App/Transformers/BasicMatchTransformer'
|
import MatchParser from 'App/Parsers/MatchParser'
|
||||||
|
import BasicMatchSerializer from 'App/Serializers/BasicMatchSerializer'
|
||||||
|
import { SerializedMatch } from 'App/Serializers/SerializedTypes'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
import { notEmpty, tutorialQueues } from 'App/helpers'
|
||||||
|
|
||||||
class MatchService {
|
class MatchService {
|
||||||
/**
|
/**
|
||||||
* Add 100 matches at a time to MatchList until the stopFetching condition is true
|
* Add 100 matches at a time to MatchList until the stopFetching condition is true
|
||||||
* @param account of the summoner
|
* @param account of the summoner
|
||||||
|
* @param region of the summoner
|
||||||
* @param stopFetching condition to stop fetching the MatchList
|
* @param stopFetching condition to stop fetching the MatchList
|
||||||
*/
|
*/
|
||||||
private async _fetchMatchListUntil (account: SummonerDTO, stopFetching: any) {
|
private async _fetchMatchListUntil(account: SummonerDTO, region: string, stopFetching: any) {
|
||||||
let matchList: MatchReferenceDto[] = []
|
let matchList: MatchlistDto = []
|
||||||
let alreadyIn = false
|
let alreadyIn = false
|
||||||
let index = 0
|
let index = 0
|
||||||
do {
|
do {
|
||||||
let newMatchList = await Jax.Matchlist.accountID(account.accountId, account.region as string, index)
|
const newMatchList = await Jax.Matchlist.puuid(account.puuid, region, index)
|
||||||
// Error while fetching Riot API
|
// Error while fetching Riot API
|
||||||
if (!newMatchList) {
|
if (!newMatchList) {
|
||||||
matchList = matchList.map(m => {
|
|
||||||
m.seasonMatch = getSeasonNumber(m.timestamp)
|
|
||||||
return m
|
|
||||||
})
|
|
||||||
return matchList
|
return matchList
|
||||||
}
|
}
|
||||||
matchList = [...matchList, ...newMatchList.matches]
|
matchList = [...matchList, ...newMatchList]
|
||||||
alreadyIn = newMatchList.matches.length === 0 || stopFetching(newMatchList.matches)
|
alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList)
|
||||||
// If the match is made in another region : we stop fetching
|
// If the match is made in another region : we stop fetching
|
||||||
if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) {
|
if (matchList[matchList.length - 1].split('_')[0].toLowerCase() !== region.toLowerCase()) {
|
||||||
alreadyIn = true
|
alreadyIn = true
|
||||||
}
|
}
|
||||||
index += 100
|
index += 100
|
||||||
} while (!alreadyIn)
|
} 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
|
return matchList
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Update the full MatchList of the summoner (min. 4 months)
|
* Update the full MatchList of the summoner
|
||||||
* @param account of the summoner
|
|
||||||
* @param summonerDB summoner in the database
|
|
||||||
*/
|
*/
|
||||||
public async updateMatchList (account: SummonerDTO, summonerDB: SummonerModel) {
|
public async updateMatchList(
|
||||||
|
account: SummonerDTO,
|
||||||
|
region: string,
|
||||||
|
summonerDB: Summoner
|
||||||
|
): Promise<MatchlistDto> {
|
||||||
console.time('matchList')
|
console.time('matchList')
|
||||||
|
|
||||||
// Summoner has already been searched : we already have a MatchList and we need to update it
|
const currentMatchList = await summonerDB.related('matchList').query().orderBy('matchId', 'asc')
|
||||||
if (summonerDB.matchList) {
|
const currentMatchListIds = currentMatchList.map((m) => m.matchId)
|
||||||
// Get MatchList
|
|
||||||
const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => {
|
const newMatchList = await this._fetchMatchListUntil(
|
||||||
return summonerDB.matchList!.some(m => m.gameId === newMatchList[newMatchList.length - 1].gameId)
|
account,
|
||||||
})
|
region,
|
||||||
// Update Summoner's MatchList
|
(newMatchList: MatchlistDto) => {
|
||||||
for (const match of matchList.reverse()) {
|
return currentMatchListIds.some((id) => id === newMatchList[newMatchList.length - 1])
|
||||||
if (!summonerDB.matchList.some(m => m.gameId === match.gameId)) {
|
}
|
||||||
summonerDB.matchList.unshift(match)
|
)
|
||||||
|
|
||||||
|
const matchListToSave: MatchlistDto = []
|
||||||
|
for (const matchId of newMatchList.reverse()) {
|
||||||
|
if (!currentMatchListIds.some((id) => id === matchId)) {
|
||||||
|
matchListToSave.push(matchId)
|
||||||
|
currentMatchListIds.push(matchId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // First search of the Summoner
|
|
||||||
const today = Date.now()
|
// If there is new matchIds to save in database
|
||||||
// Get MatchList
|
if (matchListToSave.length) {
|
||||||
const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => {
|
await Database.table('summoner_matchlist').multiInsert(
|
||||||
return (newMatchList.length !== 100 || today - newMatchList[newMatchList.length - 1].timestamp > 10368000000)
|
matchListToSave.map((id) => ({
|
||||||
})
|
match_id: id,
|
||||||
// Create Summoner's MatchList in Database
|
summoner_puuid: summonerDB.puuid,
|
||||||
summonerDB.matchList = matchList
|
}))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.timeEnd('matchList')
|
console.timeEnd('matchList')
|
||||||
|
return currentMatchListIds.reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch list of matches for a specific Summoner
|
* Fetch list of matches for a specific Summoner
|
||||||
* @param puuid
|
|
||||||
* @param accountId
|
|
||||||
* @param region
|
|
||||||
* @param gameIds
|
|
||||||
* @param summonerDB
|
|
||||||
*/
|
*/
|
||||||
public async getMatches (puuid: string, accountId: string, region: string, gameIds: number[], summonerDB: SummonerModel) {
|
public async getMatches(
|
||||||
|
region: string,
|
||||||
|
matchIds: string[],
|
||||||
|
puuid: string
|
||||||
|
): Promise<SerializedMatch[]> {
|
||||||
console.time('getMatches')
|
console.time('getMatches')
|
||||||
|
|
||||||
let matchesDetails: MatchModel[] = []
|
const matches: SerializedMatch[] = []
|
||||||
const matchesToGetFromRiot: number[] = []
|
const matchesToGetFromRiot: MatchlistDto = []
|
||||||
for (let i = 0; i < gameIds.length; ++i) {
|
for (let i = 0; i < matchIds.length; ++i) {
|
||||||
const matchSaved = await Match.findOne({
|
const matchSaved = await Match.query()
|
||||||
summoner_puuid: puuid,
|
.where('id', matchIds[i])
|
||||||
gameId: gameIds[i],
|
.preload('teams')
|
||||||
})
|
.preload('players')
|
||||||
|
.first()
|
||||||
|
|
||||||
if (matchSaved) {
|
if (matchSaved) {
|
||||||
matchesDetails.push(matchSaved)
|
// TODO: Serialize match from DB + put it in Redis + push it in "matches"
|
||||||
|
matches.push(BasicMatchSerializer.serializeOneMatch(matchSaved, puuid))
|
||||||
} else {
|
} else {
|
||||||
matchesToGetFromRiot.push(gameIds[i])
|
matchesToGetFromRiot.push(matchIds[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, region))
|
const requests = matchesToGetFromRiot.map((gameId) => Jax.Match.get(gameId, region))
|
||||||
let matchesFromApi = await Promise.all(requests)
|
const matchesFromApi = await Promise.all(requests)
|
||||||
|
|
||||||
/* If we have to store some matches in the db */
|
/* If we have to store some matches in the db */
|
||||||
if (matchesFromApi.length !== 0) {
|
if (matchesFromApi.length !== 0) {
|
||||||
// Try to see why matches are sometimes undefined
|
// Remove bugged matches from the Riot API + tutorial games
|
||||||
matchesFromApi.filter(m => {
|
const filteredMatches = matchesFromApi
|
||||||
if (m === undefined) {
|
.filter(notEmpty)
|
||||||
Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m)
|
.filter(
|
||||||
}
|
(m) =>
|
||||||
})
|
!tutorialQueues.includes(m.info.queueId) &&
|
||||||
|
m.info.teams.length > 0 &&
|
||||||
|
m.info.participants.length > 0
|
||||||
|
)
|
||||||
|
|
||||||
// Transform raw matches data
|
// Transform raw matches data
|
||||||
const transformedMatches = await BasicMatchTransformer.transform(matchesFromApi, { puuid, accountId })
|
const parsedMatches: any = await MatchParser.parse(filteredMatches)
|
||||||
|
|
||||||
/* Save all matches from Riot Api in db */
|
// TODO: Serialize match from DB + put it in Redis + push it in "matches"
|
||||||
for (const match of transformedMatches) {
|
const serializedMatches = BasicMatchSerializer.serialize(parsedMatches, puuid, true)
|
||||||
await Match.create(match)
|
matches.push(...serializedMatches)
|
||||||
match.newMatch = true
|
|
||||||
}
|
|
||||||
matchesDetails = [...matchesDetails, ...transformedMatches]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort matches */
|
// Todo: check if we need to sort here
|
||||||
matchesDetails.sort((a, b) => (a.date < b.date) ? 1 : -1)
|
matches.sort((a, b) => (a.date < b.date ? 1 : -1))
|
||||||
console.timeEnd('getMatches')
|
console.timeEnd('getMatches')
|
||||||
|
return matches
|
||||||
return matchesDetails
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
72
server/app/Services/MatchV4Service.ts
Normal file
72
server/app/Services/MatchV4Service.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import Jax from './Jax'
|
||||||
|
import { getSeasonNumber, notEmpty } from 'App/helpers'
|
||||||
|
import { V4MatchReferenceDto } from './Jax/src/Endpoints/MatchlistV4Endpoint'
|
||||||
|
import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint'
|
||||||
|
import MatchV4Parser from 'App/Parsers/MatchV4Parser'
|
||||||
|
import Match from 'App/Models/Match'
|
||||||
|
|
||||||
|
class MatchService {
|
||||||
|
private async _fetchMatchListUntil(account: SummonerDTO, region: string) {
|
||||||
|
let matchList: V4MatchReferenceDto[] = []
|
||||||
|
let alreadyIn = false
|
||||||
|
let index = 0
|
||||||
|
do {
|
||||||
|
let newMatchList = await Jax.MatchlistV4.accountID(account.accountId, region, index)
|
||||||
|
// Error while fetching Riot API
|
||||||
|
if (!newMatchList) {
|
||||||
|
matchList = matchList.map((m) => {
|
||||||
|
m.seasonMatch = getSeasonNumber(m.timestamp)
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
return matchList
|
||||||
|
}
|
||||||
|
matchList = [...matchList, ...newMatchList.matches]
|
||||||
|
alreadyIn = newMatchList.matches.length === 0
|
||||||
|
// If the match is made in another region : we stop fetching
|
||||||
|
if (matchList[matchList.length - 1].platformId.toLowerCase() !== region) {
|
||||||
|
alreadyIn = true
|
||||||
|
}
|
||||||
|
index += 100
|
||||||
|
} while (!alreadyIn)
|
||||||
|
|
||||||
|
// Remove matches from MatchList made in another region, tutorial games, 3v3 games, Coop vs IA games
|
||||||
|
const tutorialModes = [2000, 2010, 2020, 460, 470, 800, 810, 820, 830, 840, 850]
|
||||||
|
matchList = matchList
|
||||||
|
.filter((m) => {
|
||||||
|
const sameRegion = m.platformId.toLowerCase() === region
|
||||||
|
const notATutorialGame = !tutorialModes.includes(m.queue)
|
||||||
|
|
||||||
|
return sameRegion && notATutorialGame
|
||||||
|
})
|
||||||
|
.map((m) => {
|
||||||
|
m.seasonMatch = getSeasonNumber(m.timestamp)
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
|
return matchList
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateMatchList(account: SummonerDTO, region: string) {
|
||||||
|
return this._fetchMatchListUntil(account, region)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getMatches(region: string, matchlist: V4MatchReferenceDto[]) {
|
||||||
|
const matchesToGetFromRiot: number[] = []
|
||||||
|
for (const match of matchlist) {
|
||||||
|
const matchSaved = await Match.query()
|
||||||
|
.where('id', MatchV4Parser.createMatchId(match.gameId, region))
|
||||||
|
.first()
|
||||||
|
if (!matchSaved) {
|
||||||
|
matchesToGetFromRiot.push(match.gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const requests = matchesToGetFromRiot.map((gameId) => Jax.MatchV4.get(gameId, region))
|
||||||
|
const matchesFromApi = await Promise.all(requests)
|
||||||
|
const filteredMatches = matchesFromApi.filter(notEmpty)
|
||||||
|
|
||||||
|
return filteredMatches.length ? await MatchV4Parser.parse(filteredMatches) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MatchService()
|
||||||
|
|
@ -3,28 +3,20 @@ import got from 'got/dist/source'
|
||||||
|
|
||||||
export interface ChampionsPlayRate {
|
export interface ChampionsPlayRate {
|
||||||
[champion: string]: {
|
[champion: string]: {
|
||||||
TOP: number,
|
TOP: number
|
||||||
JUNGLE: number,
|
JUNGLE: number
|
||||||
MIDDLE: number,
|
MIDDLE: number
|
||||||
BOTTOM: number,
|
BOTTOM: number
|
||||||
UTILITY: number,
|
UTILITY: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FinalRoleComposition {
|
|
||||||
TOP?: number,
|
|
||||||
JUNGLE?: number,
|
|
||||||
MIDDLE?: number,
|
|
||||||
BOTTOM?: number,
|
|
||||||
SUPPORT?: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RoleComposition {
|
export interface RoleComposition {
|
||||||
TOP?: number,
|
TOP?: number
|
||||||
JUNGLE?: number,
|
JUNGLE?: number
|
||||||
MIDDLE?: number,
|
MIDDLE?: number
|
||||||
BOTTOM?: number,
|
BOTTOM?: number
|
||||||
UTILITY?: number,
|
UTILITY?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChampionComposition {
|
export interface ChampionComposition {
|
||||||
|
|
@ -32,22 +24,22 @@ export interface ChampionComposition {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiRoleResponse {
|
export interface ApiRoleResponse {
|
||||||
data: { [key: string]: ChampionInitialRates };
|
data: { [key: string]: ChampionInitialRates }
|
||||||
patch: string;
|
patch: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChampionInitialRates {
|
export interface ChampionInitialRates {
|
||||||
MIDDLE?: RoleRate;
|
MIDDLE?: RoleRate
|
||||||
UTILITY?: RoleRate;
|
UTILITY?: RoleRate
|
||||||
JUNGLE?: RoleRate;
|
JUNGLE?: RoleRate
|
||||||
TOP?: RoleRate;
|
TOP?: RoleRate
|
||||||
BOTTOM?: RoleRate;
|
BOTTOM?: RoleRate
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RoleRate {
|
export interface RoleRate {
|
||||||
playRate: number;
|
playRate: number
|
||||||
winRate: number;
|
winRate: number
|
||||||
banRate: number;
|
banRate: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChampionsRates {
|
export interface ChampionsRates {
|
||||||
|
|
@ -55,7 +47,7 @@ export interface ChampionsRates {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoleIdentificationService {
|
class RoleIdentificationService {
|
||||||
private _getPermutations (array: number[]) {
|
private _getPermutations(array: number[]) {
|
||||||
const result: number[][] = []
|
const result: number[][] = []
|
||||||
|
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
|
@ -72,13 +64,15 @@ class RoleIdentificationService {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private _calculateMetric (championPositions: ChampionsRates, bestPositions: RoleComposition) {
|
private _calculateMetric(championPositions: ChampionsRates, bestPositions: RoleComposition) {
|
||||||
return Object.entries(bestPositions).reduce((agg, [position, champion]) => {
|
return (
|
||||||
|
Object.entries(bestPositions).reduce((agg, [position, champion]) => {
|
||||||
return agg + (championPositions[champion][position] || 0)
|
return agg + (championPositions[champion][position] || 0)
|
||||||
}, 0) / Object.keys(bestPositions).length
|
}, 0) / Object.keys(bestPositions).length
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPositions (
|
private _getPositions(
|
||||||
championPositions: ChampionsRates,
|
championPositions: ChampionsRates,
|
||||||
composition: number[],
|
composition: number[],
|
||||||
top?: number,
|
top?: number,
|
||||||
|
|
@ -89,11 +83,11 @@ class RoleIdentificationService {
|
||||||
) {
|
) {
|
||||||
// Set the initial guess to be the champion in the composition, order doesn't matter
|
// Set the initial guess to be the champion in the composition, order doesn't matter
|
||||||
let bestPositions: RoleComposition = {
|
let bestPositions: RoleComposition = {
|
||||||
'TOP': composition[0],
|
TOP: composition[0],
|
||||||
'JUNGLE': composition[1],
|
JUNGLE: composition[1],
|
||||||
'MIDDLE': composition[2],
|
MIDDLE: composition[2],
|
||||||
'BOTTOM': composition[3],
|
BOTTOM: composition[3],
|
||||||
'UTILITY': composition[4],
|
UTILITY: composition[4],
|
||||||
}
|
}
|
||||||
|
|
||||||
let bestMetric = this._calculateMetric(championPositions, bestPositions)
|
let bestMetric = this._calculateMetric(championPositions, bestPositions)
|
||||||
|
|
@ -102,19 +96,23 @@ class RoleIdentificationService {
|
||||||
|
|
||||||
// Figure out which champions and positions we need to fill
|
// Figure out which champions and positions we need to fill
|
||||||
const knownChampions = [top, jungle, middle, adc, support].filter(Boolean)
|
const knownChampions = [top, jungle, middle, adc, support].filter(Boolean)
|
||||||
const unknownChampions = composition.filter(champ => !knownChampions.includes(champ))
|
const unknownChampions = composition.filter((champ) => !knownChampions.includes(champ))
|
||||||
const unknownPositions = Object.entries({
|
const unknownPositions = Object.entries({
|
||||||
'TOP': top, 'JUNGLE': jungle, 'MIDDLE': middle, 'BOTTOM': adc, 'UTILITY': support,
|
TOP: top,
|
||||||
|
JUNGLE: jungle,
|
||||||
|
MIDDLE: middle,
|
||||||
|
BOTTOM: adc,
|
||||||
|
UTILITY: support,
|
||||||
})
|
})
|
||||||
.filter(pos => !pos[1])
|
.filter((pos) => !pos[1])
|
||||||
.map(pos => pos[0])
|
.map((pos) => pos[0])
|
||||||
|
|
||||||
const testComposition: RoleComposition = {
|
const testComposition: RoleComposition = {
|
||||||
'TOP': top,
|
TOP: top,
|
||||||
'JUNGLE': jungle,
|
JUNGLE: jungle,
|
||||||
'MIDDLE': middle,
|
MIDDLE: middle,
|
||||||
'BOTTOM': adc,
|
BOTTOM: adc,
|
||||||
'UTILITY': support,
|
UTILITY: support,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over the positions we need to fill and record how well each composition "performs"
|
// Iterate over the positions we need to fill and record how well each composition "performs"
|
||||||
|
|
@ -163,7 +161,7 @@ class RoleIdentificationService {
|
||||||
/**
|
/**
|
||||||
* Get the CDN data of the champion playrates by role
|
* Get the CDN data of the champion playrates by role
|
||||||
*/
|
*/
|
||||||
public async pullData (): Promise<ChampionsPlayRate> {
|
public async pullData(): Promise<ChampionsPlayRate> {
|
||||||
const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json'
|
const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json'
|
||||||
|
|
||||||
// Check if cached
|
// Check if cached
|
||||||
|
|
@ -204,12 +202,12 @@ class RoleIdentificationService {
|
||||||
* @param jungle
|
* @param jungle
|
||||||
* @param support
|
* @param support
|
||||||
*/
|
*/
|
||||||
public getRoles (
|
public getRoles(
|
||||||
championPositions: ChampionsRates,
|
championPositions: ChampionsRates,
|
||||||
composition: number[],
|
composition: number[],
|
||||||
jungle?: number,
|
jungle?: number,
|
||||||
support?: number
|
support?: number
|
||||||
): FinalRoleComposition {
|
): RoleComposition {
|
||||||
// Set composition champion playrate to 0% if not present in the json data
|
// Set composition champion playrate to 0% if not present in the json data
|
||||||
for (const compChamp of composition) {
|
for (const compChamp of composition) {
|
||||||
if (championPositions[compChamp]) {
|
if (championPositions[compChamp]) {
|
||||||
|
|
@ -239,9 +237,18 @@ class RoleIdentificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (Object.keys(identified).length < composition.length - 1) {
|
while (Object.keys(identified).length < composition.length - 1) {
|
||||||
let { bestPositions, bestMetric: metric, secondBestPositions: sbp } =
|
let {
|
||||||
this._getPositions(championPositions, composition,
|
bestPositions,
|
||||||
identified.TOP, identified.JUNGLE, identified.MIDDLE, identified.BOTTOM, identified.UTILITY
|
bestMetric: metric,
|
||||||
|
secondBestPositions: sbp,
|
||||||
|
} = this._getPositions(
|
||||||
|
championPositions,
|
||||||
|
composition,
|
||||||
|
identified.TOP,
|
||||||
|
identified.JUNGLE,
|
||||||
|
identified.MIDDLE,
|
||||||
|
identified.BOTTOM,
|
||||||
|
identified.UTILITY
|
||||||
)
|
)
|
||||||
|
|
||||||
positions = bestPositions
|
positions = bestPositions
|
||||||
|
|
@ -261,7 +268,11 @@ class RoleIdentificationService {
|
||||||
// Done! Grab the results.
|
// Done! Grab the results.
|
||||||
const positionsWithMetric = {}
|
const positionsWithMetric = {}
|
||||||
for (const [position, champion] of Object.entries(positions)) {
|
for (const [position, champion] of Object.entries(positions)) {
|
||||||
if (Object.keys(identified).includes(position) || champion === jungle || champion === support) {
|
if (
|
||||||
|
Object.keys(identified).includes(position) ||
|
||||||
|
champion === jungle ||
|
||||||
|
champion === support
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
positionsWithMetric[position] = {
|
positionsWithMetric[position] = {
|
||||||
|
|
@ -285,13 +296,7 @@ class RoleIdentificationService {
|
||||||
identified[best[0]] = best[1]
|
identified[best[0]] = best[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename UTILITY to SUPPORT
|
return positions
|
||||||
const {
|
|
||||||
UTILITY: SUPPORT,
|
|
||||||
...rest
|
|
||||||
} = positions
|
|
||||||
|
|
||||||
return { ...rest, SUPPORT }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
import MatchRepository from 'App/Repositories/MatchRepository'
|
|
||||||
import { sortTeamByRole } from 'App/helpers'
|
import { sortTeamByRole } from 'App/helpers'
|
||||||
|
import { ChampionRoles, TeamPosition } from 'App/Parsers/ParsedType'
|
||||||
|
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||||
|
import BasicMatchSerializer from 'App/Serializers/BasicMatchSerializer'
|
||||||
|
|
||||||
class StatsService {
|
class StatsService {
|
||||||
public async getSummonerStats (puuid: string, season?: number) {
|
public async getRecentActivity(puuid: string) {
|
||||||
|
return MatchRepository.recentActivity(puuid)
|
||||||
|
}
|
||||||
|
public async getSummonerStats(puuid: string, season?: number) {
|
||||||
console.time('GLOBAL')
|
console.time('GLOBAL')
|
||||||
const globalStats = await MatchRepository.globalStats(puuid, season)
|
const globalStats = await MatchRepository.globalStats(puuid)
|
||||||
console.timeEnd('GLOBAL')
|
console.timeEnd('GLOBAL')
|
||||||
console.time('GAMEMODE')
|
console.time('GAMEMODE')
|
||||||
const gamemodeStats = await MatchRepository.gamemodeStats(puuid, season)
|
const gamemodeStats = await MatchRepository.gamemodeStats(puuid)
|
||||||
console.timeEnd('GAMEMODE')
|
console.timeEnd('GAMEMODE')
|
||||||
console.time('ROLE')
|
console.time('ROLE')
|
||||||
const roleStats = await MatchRepository.roleStats(puuid, season)
|
const roleStats = await MatchRepository.roleStats(puuid)
|
||||||
// Check if all roles are in the array
|
// Check if all roles are in the array
|
||||||
const roles = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
const roles = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
if (!roleStats.find(r => r.role === role)) {
|
const findedRole = roleStats.find((r) => TeamPosition[r.role] === role)
|
||||||
|
if (findedRole) {
|
||||||
|
findedRole.role = TeamPosition[findedRole.role]
|
||||||
|
} else {
|
||||||
roleStats.push({
|
roleStats.push({
|
||||||
count: 0,
|
count: 0,
|
||||||
losses: 0,
|
losses: 0,
|
||||||
|
|
@ -25,22 +33,33 @@ class StatsService {
|
||||||
}
|
}
|
||||||
console.timeEnd('ROLE')
|
console.timeEnd('ROLE')
|
||||||
console.time('CHAMPION')
|
console.time('CHAMPION')
|
||||||
const championStats = await MatchRepository.championStats(puuid, 5, season)
|
const championStats = await MatchRepository.championStats(puuid, 5)
|
||||||
|
for (const champ of championStats) {
|
||||||
|
champ.champion = BasicMatchSerializer.getChampion(champ.id)
|
||||||
|
}
|
||||||
console.timeEnd('CHAMPION')
|
console.timeEnd('CHAMPION')
|
||||||
console.time('CHAMPION-CLASS')
|
console.time('CHAMPION-CLASS')
|
||||||
const championClassStats = await MatchRepository.championClassStats(puuid, season)
|
const championClassStats = await MatchRepository.championClassStats(puuid)
|
||||||
|
for (const champ of championClassStats) {
|
||||||
|
champ.id = ChampionRoles[champ.id]
|
||||||
|
}
|
||||||
console.timeEnd('CHAMPION-CLASS')
|
console.timeEnd('CHAMPION-CLASS')
|
||||||
console.time('MATES')
|
console.time('MATES')
|
||||||
const mates = await MatchRepository.mates(puuid, season)
|
const mates = await MatchRepository.mates(puuid)
|
||||||
console.timeEnd('MATES')
|
console.timeEnd('MATES')
|
||||||
|
|
||||||
|
console.time('RECENT_ACTIVITY')
|
||||||
|
const recentActivity = await MatchRepository.recentActivity(puuid)
|
||||||
|
console.timeEnd('RECENT_ACTIVITY')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
global: globalStats[0],
|
global: globalStats,
|
||||||
league: gamemodeStats,
|
league: gamemodeStats,
|
||||||
role: roleStats.sort(sortTeamByRole),
|
role: roleStats.sort(sortTeamByRole),
|
||||||
|
champion: championStats,
|
||||||
class: championClassStats,
|
class: championClassStats,
|
||||||
mates,
|
mates,
|
||||||
champion: championStats,
|
recentActivity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,48 @@
|
||||||
import Jax from './Jax'
|
import Jax from './Jax'
|
||||||
import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint'
|
import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint'
|
||||||
import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint'
|
import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint'
|
||||||
import { SummonerModel } from 'App/Models/Summoner'
|
import Summoner from 'App/Models/Summoner'
|
||||||
|
import { PlayerRankParsed } from 'App/Parsers/ParsedType'
|
||||||
|
import MatchPlayerRank from 'App/Models/MatchPlayerRank'
|
||||||
|
|
||||||
export interface LeagueEntriesByQueue {
|
export interface LeagueEntriesByQueue {
|
||||||
soloQ?: LeagueEntryByQueue,
|
soloQ?: LeagueEntryByQueue
|
||||||
flex5v5?: LeagueEntryByQueue
|
flex5v5?: LeagueEntryByQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LeagueEntryByQueue extends LeagueEntryDTO {
|
export interface LeagueEntryByQueue extends LeagueEntryDTO {
|
||||||
fullRank: string,
|
fullRank: string
|
||||||
winrate: string,
|
winrate: string
|
||||||
shortName: string | number
|
shortName: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
class SummonerService {
|
class SummonerService {
|
||||||
private uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
|
private readonly uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
|
||||||
private leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
public readonly leaguesNumbers = { I: 1, II: 2, III: 3, IV: 4 }
|
||||||
|
|
||||||
|
public getRankedShortName(rank: PlayerRankParsed | MatchPlayerRank) {
|
||||||
|
return this.uniqueLeagues.includes(rank.tier) ? rank.lp : rank.tier[0] + rank.rank
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWinrate(wins: number, losses: number) {
|
||||||
|
return +((wins * 100) / (wins + losses)).toFixed(1) + '%'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to transform League Data from the Riot API
|
* Helper to transform League Data from the Riot API
|
||||||
* @param league raw data of the league from Riot API
|
* @param league raw data of the league from Riot API
|
||||||
*/
|
*/
|
||||||
private getleagueData (league?: LeagueEntryDTO): LeagueEntryByQueue | null {
|
private getleagueData(league?: LeagueEntryDTO): LeagueEntryByQueue | null {
|
||||||
if (!league) {
|
if (!league) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const fullRank = this.uniqueLeagues.includes(league.tier) ? league.tier : `${league.tier} ${league.rank}`
|
const fullRank = this.uniqueLeagues.includes(league.tier)
|
||||||
const winrate = +(league.wins * 100 / (league.wins + league.losses)).toFixed(1) + '%'
|
? league.tier
|
||||||
const shortName = this.uniqueLeagues.includes(league.tier) ?
|
: `${league.tier} ${league.rank}`
|
||||||
league.leaguePoints :
|
const winrate = this.getWinrate(league.wins, league.losses)
|
||||||
league.tier[0] + this.leaguesNumbers[league.rank]
|
const shortName = this.uniqueLeagues.includes(league.tier)
|
||||||
|
? league.leaguePoints
|
||||||
|
: league.tier[0] + this.leaguesNumbers[league.rank]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...league,
|
...league,
|
||||||
|
|
@ -45,7 +57,7 @@ class SummonerService {
|
||||||
* @param summonerName
|
* @param summonerName
|
||||||
* @param region
|
* @param region
|
||||||
*/
|
*/
|
||||||
public async getAccount (summonerName: string, region: string) {
|
public async getAccount(summonerName: string, region: string) {
|
||||||
const name = summonerName.toLowerCase()
|
const name = summonerName.toLowerCase()
|
||||||
const account = await Jax.Summoner.summonerName(name, region)
|
const account = await Jax.Summoner.summonerName(name, region)
|
||||||
return account
|
return account
|
||||||
|
|
@ -56,18 +68,11 @@ class SummonerService {
|
||||||
* @param account of the summoner
|
* @param account of the summoner
|
||||||
* @param summonerDB summoner in the database
|
* @param summonerDB summoner in the database
|
||||||
*/
|
*/
|
||||||
public getAllSummonerNames (account: SummonerDTO, summonerDB: SummonerModel) {
|
public async getAllSummonerNames(account: SummonerDTO, summonerDB: Summoner) {
|
||||||
const names = summonerDB.names ? summonerDB.names : []
|
await summonerDB.related('names').firstOrCreate({
|
||||||
|
|
||||||
if (!names.find(n => n.name === account.name)) {
|
|
||||||
names.push({
|
|
||||||
name: account.name,
|
name: account.name,
|
||||||
date: new Date(),
|
|
||||||
})
|
})
|
||||||
summonerDB.names = names
|
return summonerDB.related('names').query().select('name', 'created_at')
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,15 +80,16 @@ class SummonerService {
|
||||||
* @param account
|
* @param account
|
||||||
* @param region
|
* @param region
|
||||||
*/
|
*/
|
||||||
public async getRanked (account: SummonerDTO, region: string): Promise<LeagueEntriesByQueue> {
|
public async getRanked(summonerId: string, region: string): Promise<LeagueEntriesByQueue> {
|
||||||
const ranked = await Jax.League.summonerID(account.id, region)
|
const ranked = await Jax.League.summonerID(summonerId, region)
|
||||||
const result:LeagueEntriesByQueue = {}
|
const result: LeagueEntriesByQueue = {}
|
||||||
|
|
||||||
if (ranked && ranked.length) {
|
if (ranked && ranked.length) {
|
||||||
result.soloQ = this.getleagueData(ranked.find(e => e.queueType === 'RANKED_SOLO_5x5')) || undefined
|
result.soloQ =
|
||||||
result.flex5v5 = this.getleagueData(ranked.find(e => e.queueType === 'RANKED_FLEX_SR')) || undefined
|
this.getleagueData(ranked.find((e) => e.queueType === 'RANKED_SOLO_5x5')) || undefined
|
||||||
|
result.flex5v5 =
|
||||||
|
this.getleagueData(ranked.find((e) => e.queueType === 'RANKED_FLEX_SR')) || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import { MatchModel, ParticipantBasic } from 'App/Models/Match'
|
|
||||||
import { MatchDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
|
||||||
import MatchTransformer from 'App/Transformers/MatchTransformer'
|
|
||||||
|
|
||||||
class BasicMatchTransformer extends MatchTransformer {
|
|
||||||
/**
|
|
||||||
* Transform raw data for 1 match
|
|
||||||
* @param match
|
|
||||||
* @param puuid
|
|
||||||
* @param accountId
|
|
||||||
*/
|
|
||||||
private transformOneMatch (match: MatchDto, puuid: string, accountId: string): MatchModel {
|
|
||||||
// Global data about the match
|
|
||||||
const globalInfos = super.getGameInfos(match)
|
|
||||||
|
|
||||||
const identity = match.participantIdentities.find((p) => p.player.currentAccountId === accountId)
|
|
||||||
const player = match.participants[identity!.participantId - 1]
|
|
||||||
|
|
||||||
let win = match.teams.find((t) => t.teamId === player.teamId)!.win
|
|
||||||
|
|
||||||
// Match less than 5min
|
|
||||||
if (match.gameDuration < 300) {
|
|
||||||
win = 'Remake'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player data
|
|
||||||
const playerData = super.getPlayerData(match, player, false)
|
|
||||||
|
|
||||||
// Teams data
|
|
||||||
const allyTeam: ParticipantBasic[] = []
|
|
||||||
const enemyTeam: ParticipantBasic[] = []
|
|
||||||
for (let summoner of match.participantIdentities) {
|
|
||||||
const allData = match.participants[summoner.participantId - 1]
|
|
||||||
const playerInfos = {
|
|
||||||
account_id: summoner.player.currentAccountId,
|
|
||||||
name: summoner.player.summonerName,
|
|
||||||
role: super.getRoleName(allData.timeline, match.queueId),
|
|
||||||
champion: super.getChampion(allData.championId),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allData.teamId === player.teamId) {
|
|
||||||
allyTeam.push(playerInfos)
|
|
||||||
} else {
|
|
||||||
enemyTeam.push(playerInfos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
super.getMatchRoles(match, allyTeam, enemyTeam, player.teamId, playerData)
|
|
||||||
|
|
||||||
return {
|
|
||||||
account_id: identity!.player.currentAccountId,
|
|
||||||
summoner_puuid: puuid,
|
|
||||||
gameId: match.gameId,
|
|
||||||
result: win,
|
|
||||||
allyTeam,
|
|
||||||
enemyTeam,
|
|
||||||
...globalInfos,
|
|
||||||
...playerData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param matches data from Riot API, Array of matches
|
|
||||||
* @param ctx context
|
|
||||||
*/
|
|
||||||
public async transform (matches: MatchDto[], { puuid, accountId }: { puuid: string, accountId: string }) {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
const finalMatches: MatchModel[] = []
|
|
||||||
matches.forEach((match, index) => {
|
|
||||||
finalMatches[index] = this.transformOneMatch(match, puuid, accountId)
|
|
||||||
})
|
|
||||||
return finalMatches
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new BasicMatchTransformer()
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import { Ban, DetailedMatchModel } from 'App/Models/DetailedMatch'
|
|
||||||
import { MatchDto, TeamStatsDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
|
||||||
import MatchTransformer from './MatchTransformer'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DetailedMatchTransformer class
|
|
||||||
*
|
|
||||||
* @class DetailedMatchTransformer
|
|
||||||
*/
|
|
||||||
class DetailedMatchTransformer extends MatchTransformer {
|
|
||||||
/**
|
|
||||||
* Get all data of one team
|
|
||||||
* @param match raw match data from Riot API
|
|
||||||
* @param team raw team data from Riot API
|
|
||||||
*/
|
|
||||||
private getTeamData (match: MatchDto, team: TeamStatsDto) {
|
|
||||||
let win = team.win
|
|
||||||
if (match.gameDuration < 300) {
|
|
||||||
win = 'Remake'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global stats of the team
|
|
||||||
const teamPlayers = match.participants.filter(p => p.teamId === team.teamId)
|
|
||||||
const teamStats = teamPlayers.reduce((prev, cur) => {
|
|
||||||
prev.kills += cur.stats.kills
|
|
||||||
prev.deaths += cur.stats.deaths
|
|
||||||
prev.assists += cur.stats.assists
|
|
||||||
prev.gold += cur.stats.goldEarned
|
|
||||||
prev.dmgChamp += cur.stats.totalDamageDealtToChampions
|
|
||||||
prev.dmgObj += cur.stats.damageDealtToObjectives
|
|
||||||
prev.dmgTaken += cur.stats.totalDamageTaken
|
|
||||||
return prev
|
|
||||||
}, { kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 })
|
|
||||||
|
|
||||||
// Bans
|
|
||||||
const bans: Ban[] = []
|
|
||||||
if (team.bans) {
|
|
||||||
for (const ban of team.bans) {
|
|
||||||
const champion = (ban.championId === -1) ? { id: null, name: null } : super.getChampion(ban.championId)
|
|
||||||
|
|
||||||
bans.push({
|
|
||||||
...ban,
|
|
||||||
champion,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Players
|
|
||||||
let players = teamPlayers
|
|
||||||
.map(p => super.getPlayerData(match, p, true, teamStats))
|
|
||||||
.map(p => {
|
|
||||||
p.firstSum = super.getSummonerSpell(p.firstSum as number)
|
|
||||||
p.secondSum = super.getSummonerSpell(p.secondSum as number)
|
|
||||||
return p
|
|
||||||
})
|
|
||||||
.sort(this.sortTeamByRole)
|
|
||||||
|
|
||||||
return {
|
|
||||||
bans,
|
|
||||||
barons: team.baronKills,
|
|
||||||
color: team.teamId === 100 ? 'Blue' : 'Red',
|
|
||||||
dragons: team.dragonKills,
|
|
||||||
inhibitors: team.inhibitorKills,
|
|
||||||
players,
|
|
||||||
result: win,
|
|
||||||
riftHerald: team.riftHeraldKills,
|
|
||||||
teamStats,
|
|
||||||
towers: team.towerKills,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param match data from Riot API
|
|
||||||
*/
|
|
||||||
public async transform (match: MatchDto): Promise<DetailedMatchModel> {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
// Global data
|
|
||||||
const globalInfos = super.getGameInfos(match)
|
|
||||||
|
|
||||||
// Teams
|
|
||||||
const firstTeam = this.getTeamData(match, match.teams[0])
|
|
||||||
const secondTeam = this.getTeamData(match, match.teams[1])
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
super.getMatchRoles(match, firstTeam.players, secondTeam.players)
|
|
||||||
|
|
||||||
return {
|
|
||||||
gameId: match.gameId,
|
|
||||||
blueTeam: firstTeam.color === 'Blue' ? firstTeam : secondTeam,
|
|
||||||
redTeam: firstTeam.color === 'Blue' ? secondTeam : firstTeam,
|
|
||||||
...globalInfos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new DetailedMatchTransformer()
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import { queuesWithRole } from 'App/helpers'
|
|
||||||
import Jax from 'App/Services/Jax'
|
|
||||||
import { CurrentGameInfo, CurrentGameParticipant } from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
|
||||||
import { FinalRoleComposition } from 'App/Services/RoleIdentiticationService'
|
|
||||||
import SummonerService, { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
|
||||||
import MatchTransformer, { PlayerRole } from './MatchTransformer'
|
|
||||||
|
|
||||||
class LiveMatchTransformer extends MatchTransformer {
|
|
||||||
/**
|
|
||||||
* Get player soloQ and flex rank from his summonerName
|
|
||||||
* @param participant
|
|
||||||
* @param region
|
|
||||||
*/
|
|
||||||
private async getPlayerRank (participant: CurrentGameParticipant, region: string) {
|
|
||||||
const account = await Jax.Summoner.summonerId(participant.summonerId, region)
|
|
||||||
let ranked: LeagueEntriesByQueue
|
|
||||||
if (account) {
|
|
||||||
ranked = await SummonerService.getRanked(account, region)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...participant,
|
|
||||||
level: account ? account.summonerLevel : undefined,
|
|
||||||
rank: account ? ranked! : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param liveMatch
|
|
||||||
* @param ctx
|
|
||||||
*/
|
|
||||||
public async transform (liveMatch: CurrentGameInfo, { region }: { region: string }) {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
const blueTeam: PlayerRole[] = [] // 100
|
|
||||||
const redTeam: PlayerRole[] = [] // 200
|
|
||||||
let blueRoles: FinalRoleComposition = {}
|
|
||||||
let redRoles: FinalRoleComposition = {}
|
|
||||||
const needsRole = this.championRoles &&
|
|
||||||
(queuesWithRole.includes(liveMatch.gameQueueConfigId) ||
|
|
||||||
(liveMatch.gameType === 'CUSTOM_GAME' && liveMatch.participants.length === 10))
|
|
||||||
|
|
||||||
if (needsRole) {
|
|
||||||
liveMatch.participants.map(p => {
|
|
||||||
const playerRole = { champion: p.championId, jungle: p.spell1Id === 11 || p.spell2Id === 11 }
|
|
||||||
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
|
|
||||||
})
|
|
||||||
|
|
||||||
blueRoles = super.getTeamRoles(blueTeam)
|
|
||||||
redRoles = super.getTeamRoles(redTeam)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const participant of liveMatch.participants) {
|
|
||||||
// Perks
|
|
||||||
participant.runes = participant.perks ?
|
|
||||||
super.getPerksImages(participant.perks.perkIds[0], participant.perks.perkSubStyle)
|
|
||||||
: {}
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
if (needsRole) {
|
|
||||||
const roles = participant.teamId === 100 ? blueRoles : redRoles
|
|
||||||
participant.role = Object.entries(roles).find(([, champion]) => participant.championId === champion)![0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ranks
|
|
||||||
const requestsParticipants = liveMatch.participants.map(p => this.getPlayerRank(p, region))
|
|
||||||
liveMatch.participants = await Promise.all(requestsParticipants)
|
|
||||||
|
|
||||||
return liveMatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new LiveMatchTransformer()
|
|
||||||
|
|
@ -1,355 +0,0 @@
|
||||||
import { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } from 'App/helpers'
|
|
||||||
import Jax from 'App/Services/Jax'
|
|
||||||
import { Champion, Item, ParticipantBasic, ParticipantDetails, PercentStats, Perks, Stats, SummonerSpell } from 'App/Models/Match'
|
|
||||||
import RoleIdentificationService, { ChampionsPlayRate } from 'App/Services/RoleIdentiticationService'
|
|
||||||
import { ChampionDTO, ItemDTO, PerkDTO, PerkStyleDTO, SummonerSpellDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
|
||||||
import { TeamStats } from 'App/Models/DetailedMatch'
|
|
||||||
import {
|
|
||||||
MatchDto,
|
|
||||||
ParticipantDto,
|
|
||||||
ParticipantStatsDto,
|
|
||||||
ParticipantTimelineDto,
|
|
||||||
} from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
|
||||||
|
|
||||||
export interface PlayerRole {
|
|
||||||
champion: number,
|
|
||||||
jungle?: boolean,
|
|
||||||
support?: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default abstract class MatchTransformer {
|
|
||||||
protected champions: ChampionDTO[]
|
|
||||||
protected items: ItemDTO[]
|
|
||||||
protected perks: PerkDTO[]
|
|
||||||
protected perkstyles: PerkStyleDTO[]
|
|
||||||
protected summonerSpells: SummonerSpellDTO[]
|
|
||||||
protected championRoles: ChampionsPlayRate
|
|
||||||
protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number
|
|
||||||
/**
|
|
||||||
* Get global Context with CDragon Data
|
|
||||||
*/
|
|
||||||
public async getContext () {
|
|
||||||
const items = await Jax.CDragon.items()
|
|
||||||
const champions = await Jax.CDragon.champions()
|
|
||||||
const perks = await Jax.CDragon.perks()
|
|
||||||
const perkstyles = await Jax.CDragon.perkstyles()
|
|
||||||
const summonerSpells = await Jax.CDragon.summonerSpells()
|
|
||||||
const championRoles = await RoleIdentificationService.pullData().catch(() => { })
|
|
||||||
|
|
||||||
this.champions = champions
|
|
||||||
this.items = items
|
|
||||||
this.perks = perks
|
|
||||||
this.perkstyles = perkstyles.styles
|
|
||||||
this.summonerSpells = summonerSpells
|
|
||||||
this.championRoles = championRoles as ChampionsPlayRate
|
|
||||||
this.sortTeamByRole = sortTeamByRole
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get champion specific data
|
|
||||||
* @param id of the champion
|
|
||||||
*/
|
|
||||||
public getChampion (id: number): Champion {
|
|
||||||
const originalChampionData = this.champions.find(c => c.id === id)
|
|
||||||
const icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/'
|
|
||||||
+ originalChampionData!.squarePortraitPath.split('/assets/')[1].toLowerCase()
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon,
|
|
||||||
id: originalChampionData!.id,
|
|
||||||
name: originalChampionData!.name,
|
|
||||||
alias: originalChampionData!.alias,
|
|
||||||
roles: originalChampionData!.roles,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get global data about the match
|
|
||||||
*/
|
|
||||||
public getGameInfos (match: MatchDto) {
|
|
||||||
return {
|
|
||||||
map: match.mapId,
|
|
||||||
gamemode: match.queueId,
|
|
||||||
date: match.gameCreation,
|
|
||||||
region: match.platformId.toLowerCase(),
|
|
||||||
season: getSeasonNumber(match.gameCreation),
|
|
||||||
time: match.gameDuration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get player specific data during the match
|
|
||||||
* @param match
|
|
||||||
* @param player
|
|
||||||
* @param detailed : detailed or not stats
|
|
||||||
* @param teamStats : if detailed, the teamStats argument is mandatory
|
|
||||||
*/
|
|
||||||
public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats?: TeamStats) {
|
|
||||||
const identity = match.participantIdentities.find(p => p.participantId === player.participantId)
|
|
||||||
const name = identity!.player.summonerName
|
|
||||||
const champion = this.getChampion(player.championId)
|
|
||||||
const role = this.getRoleName(player.timeline, match.queueId)
|
|
||||||
const level = player.stats.champLevel
|
|
||||||
|
|
||||||
// Regular stats / Full match stats
|
|
||||||
const stats: Stats = {
|
|
||||||
kills: player.stats.kills,
|
|
||||||
deaths: player.stats.deaths,
|
|
||||||
assists: player.stats.assists,
|
|
||||||
minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled,
|
|
||||||
vision: player.stats.visionScore,
|
|
||||||
gold: player.stats.goldEarned,
|
|
||||||
dmgChamp: player.stats.totalDamageDealtToChampions,
|
|
||||||
dmgObj: player.stats.damageDealtToObjectives,
|
|
||||||
dmgTaken: player.stats.totalDamageTaken,
|
|
||||||
kp: 0,
|
|
||||||
kda: 0,
|
|
||||||
realKda: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats.kills + stats.assists !== 0 && stats.deaths === 0) {
|
|
||||||
stats.kda = '∞'
|
|
||||||
stats.realKda = stats.kills + stats.assists
|
|
||||||
} else {
|
|
||||||
stats.kda = +(stats.deaths === 0 ? 0 : ((stats.kills + stats.assists) / stats.deaths)).toFixed(2)
|
|
||||||
stats.realKda = stats.kda
|
|
||||||
}
|
|
||||||
|
|
||||||
// Percent stats / Per minute stats : only for detailed match
|
|
||||||
let percentStats: PercentStats
|
|
||||||
if (detailed) {
|
|
||||||
teamStats = teamStats!
|
|
||||||
percentStats = {
|
|
||||||
minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2),
|
|
||||||
vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2),
|
|
||||||
gold: +(player.stats.goldEarned * 100 / teamStats.gold).toFixed(1) + '%',
|
|
||||||
dmgChamp: +(player.stats.totalDamageDealtToChampions * 100 / teamStats.dmgChamp).toFixed(1) + '%',
|
|
||||||
dmgObj: +(teamStats.dmgObj ? player.stats.damageDealtToObjectives * 100 / teamStats.dmgObj : 0).toFixed(1) + '%',
|
|
||||||
dmgTaken: +(player.stats.totalDamageTaken * 100 / teamStats.dmgTaken).toFixed(1) + '%',
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.kp = teamStats.kills === 0 ? '0%' : +((stats.kills + stats.assists) * 100 / teamStats.kills).toFixed(1) + '%'
|
|
||||||
} else {
|
|
||||||
const totalKills = match.participants.reduce((prev, current) => {
|
|
||||||
if (current.teamId !== player.teamId) {
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
return prev + current.stats.kills
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
stats.criticalStrike = player.stats.largestCriticalStrike
|
|
||||||
stats.killingSpree = player.stats.largestKillingSpree
|
|
||||||
stats.doubleKills = player.stats.doubleKills
|
|
||||||
stats.tripleKills = player.stats.tripleKills
|
|
||||||
stats.quadraKills = player.stats.quadraKills
|
|
||||||
stats.pentaKills = player.stats.pentaKills
|
|
||||||
stats.heal = player.stats.totalHeal
|
|
||||||
stats.towers = player.stats.turretKills
|
|
||||||
stats.longestLiving = player.stats.longestTimeSpentLiving
|
|
||||||
stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let primaryRune: string | null = null
|
|
||||||
let secondaryRune: string | null = null
|
|
||||||
if (player.stats.perkPrimaryStyle) {
|
|
||||||
({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle))
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: (Item | null)[] = []
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
const id = player.stats['item' + i]
|
|
||||||
if (id === 0) {
|
|
||||||
items.push(null)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.items.find(i => i.id === id)
|
|
||||||
// TODO: get deleted item from old patch CDragon JSON instead of null
|
|
||||||
if (!item) {
|
|
||||||
items.push(null)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase()
|
|
||||||
items.push({
|
|
||||||
image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`,
|
|
||||||
name: item.name,
|
|
||||||
description: item.description,
|
|
||||||
price: item.priceTotal,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstSum = player.spell1Id
|
|
||||||
const secondSum = player.spell2Id
|
|
||||||
|
|
||||||
const playerData: ParticipantDetails = {
|
|
||||||
name,
|
|
||||||
summonerId: identity!.player.summonerId,
|
|
||||||
champion,
|
|
||||||
role,
|
|
||||||
primaryRune,
|
|
||||||
secondaryRune,
|
|
||||||
level,
|
|
||||||
items,
|
|
||||||
firstSum,
|
|
||||||
secondSum,
|
|
||||||
stats,
|
|
||||||
}
|
|
||||||
if (detailed) {
|
|
||||||
playerData.percentStats = percentStats!
|
|
||||||
}
|
|
||||||
|
|
||||||
playerData.perks = this.getFullPerks(player.stats)
|
|
||||||
|
|
||||||
return playerData
|
|
||||||
}
|
|
||||||
|
|
||||||
public getFullPerks (stats: ParticipantStatsDto) {
|
|
||||||
const perks: Perks = {
|
|
||||||
primaryStyle: stats.perkPrimaryStyle,
|
|
||||||
secondaryStyle: stats.perkSubStyle,
|
|
||||||
selected: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
perks.selected.push(stats[`perk${i}`])
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
perks.selected.push(stats[`statPerk${i}`])
|
|
||||||
}
|
|
||||||
|
|
||||||
return perks
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the icons of the primary rune and secondary category
|
|
||||||
* @param perk0 primary perks id
|
|
||||||
* @param perkSubStyle secondary perks category
|
|
||||||
*/
|
|
||||||
public getPerksImages (perk0: number, perkSubStyle: number) {
|
|
||||||
const firstRune = this.perks.find(p => p.id === perk0)
|
|
||||||
const firstRuneUrl = firstRune!.iconPath.split('/assets/')[1].toLowerCase()
|
|
||||||
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
|
|
||||||
|
|
||||||
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
|
|
||||||
|
|
||||||
const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null
|
|
||||||
const secondaryRune = secondRuneStyleUrl ?
|
|
||||||
`https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}`
|
|
||||||
: ''
|
|
||||||
|
|
||||||
return { primaryRune, secondaryRune }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the lane of the summoner according to timeline
|
|
||||||
* @param timeline from Riot Api
|
|
||||||
* @param gamemode of the match to check if a role is needed
|
|
||||||
*/
|
|
||||||
public getRoleName (timeline: ParticipantTimelineDto, gamemode: number) {
|
|
||||||
if (!queuesWithRole.includes(gamemode)) {
|
|
||||||
return 'NONE'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeline.lane === 'BOTTOM' && timeline.role.includes('SUPPORT')) {
|
|
||||||
return 'SUPPORT'
|
|
||||||
}
|
|
||||||
|
|
||||||
return timeline.lane
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the 5 roles of a team based on champions
|
|
||||||
* @param team 5 champions + smite from a team
|
|
||||||
*/
|
|
||||||
public getTeamRoles (team: PlayerRole[]) {
|
|
||||||
const teamJunglers = team.filter(p => p.jungle && !p.support)
|
|
||||||
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
|
||||||
const teamSupports = team.filter(p => p.support && !p.jungle)
|
|
||||||
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
|
||||||
|
|
||||||
return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update roles for a team if Riot's ones are badly identified
|
|
||||||
* @param team 5 players data of the team
|
|
||||||
* @param champs 5 champions + smite from the team
|
|
||||||
* @param playerData data of the searched player, only for basic matches
|
|
||||||
*/
|
|
||||||
public updateTeamRoles (
|
|
||||||
team: ParticipantBasic[] | ParticipantDetails[],
|
|
||||||
champs: PlayerRole[],
|
|
||||||
playerData?: ParticipantDetails
|
|
||||||
) {
|
|
||||||
// const actualRoles = [...new Set(team.map(p => p.role))]
|
|
||||||
// if (actualRoles.length === 5) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
const identifiedChamps = this.getTeamRoles(champs)
|
|
||||||
for (const summoner of team) {
|
|
||||||
summoner.role = Object.entries(identifiedChamps).find(([, champion]) => summoner.champion.id === champion)![0]
|
|
||||||
|
|
||||||
if (playerData && summoner.champion.id === playerData.champion.id) {
|
|
||||||
playerData.role = summoner.role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param match from Riot Api
|
|
||||||
* @param allyTeam 5 players of the first team
|
|
||||||
* @param enemyTeam 5 players of the second team
|
|
||||||
* @param allyTeamId team id of the searched player, only for basic matches
|
|
||||||
* @param playerData data of the searched player, only for basic matches
|
|
||||||
*/
|
|
||||||
public getMatchRoles (
|
|
||||||
match: MatchDto,
|
|
||||||
allyTeam: ParticipantBasic[] | ParticipantDetails[],
|
|
||||||
enemyTeam: ParticipantBasic[] | ParticipantDetails[],
|
|
||||||
allyTeamId = 100,
|
|
||||||
playerData?: ParticipantDetails
|
|
||||||
) {
|
|
||||||
if (!this.championRoles || !queuesWithRole.includes(match.queueId)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let allyChamps: PlayerRole[] = []
|
|
||||||
let enemyChamps: PlayerRole[] = []
|
|
||||||
match.participants.map(p => {
|
|
||||||
const items = [p.stats.item0, p.stats.item1, p.stats.item2, p.stats.item3, p.stats.item4, p.stats.item5]
|
|
||||||
const playerRole = {
|
|
||||||
champion: p.championId,
|
|
||||||
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
|
||||||
support: supportItems.some(suppItem => items.includes(suppItem)),
|
|
||||||
}
|
|
||||||
p.teamId === allyTeamId ? allyChamps.push(playerRole) : enemyChamps.push(playerRole)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.updateTeamRoles(allyTeam, allyChamps, playerData)
|
|
||||||
this.updateTeamRoles(enemyTeam, enemyChamps)
|
|
||||||
|
|
||||||
allyTeam.sort(this.sortTeamByRole)
|
|
||||||
enemyTeam.sort(this.sortTeamByRole)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Summoner Spell Data from CDragon
|
|
||||||
* @param id of the summonerSpell
|
|
||||||
*/
|
|
||||||
public getSummonerSpell (id: number): SummonerSpell | null {
|
|
||||||
if (id === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const spell = this.summonerSpells.find(s => s.id === id)
|
|
||||||
const spellName = spell!.iconPath.split('/assets/')[1].toLowerCase()
|
|
||||||
return {
|
|
||||||
name: spell!.name,
|
|
||||||
description: spell!.description,
|
|
||||||
icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class DetailedMatchValidator {
|
export default class DetailedMatchValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -25,20 +24,9 @@ export default class DetailedMatchValidator {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
gameId: schema.number(),
|
matchId: schema.string(),
|
||||||
region: schema.string(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -48,6 +36,7 @@ export default class DetailedMatchValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class MatchesIndexValidator {
|
export default class MatchesIndexValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -26,24 +25,11 @@ export default class MatchesIndexValidator {
|
||||||
*/
|
*/
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
puuid: schema.string(),
|
puuid: schema.string(),
|
||||||
accountId: schema.string(),
|
|
||||||
region: schema.string(),
|
region: schema.string(),
|
||||||
gameIds: schema.array().members(
|
matchIds: schema.array().members(schema.string()),
|
||||||
schema.number()
|
|
||||||
),
|
|
||||||
season: 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -53,6 +39,7 @@ export default class MatchesIndexValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { rules, schema } from '@ioc:Adonis/Core/Validator'
|
import { rules, schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class SummonerBasicValidator {
|
export default class SummonerBasicValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -25,22 +24,10 @@ export default class SummonerBasicValidator {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
summoner: schema.string({}, [
|
summoner: schema.string({}, [rules.regex(/^[0-9\p{L} _\.]+$/u)]),
|
||||||
rules.regex(/^[0-9\p{L} _\.]+$/u),
|
|
||||||
]),
|
|
||||||
region: schema.string(),
|
region: schema.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -50,6 +37,7 @@ export default class SummonerBasicValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class SummonerChampionValidator {
|
export default class SummonerChampionValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -30,16 +29,6 @@ export default class SummonerChampionValidator {
|
||||||
season: 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -49,6 +38,7 @@ export default class SummonerChampionValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class SummonerLiveValidator {
|
export default class SummonerLiveValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -29,16 +28,6 @@ export default class SummonerLiveValidator {
|
||||||
region: schema.string(),
|
region: schema.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -48,6 +37,7 @@ export default class SummonerLiveValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class SummonerOverviewValidator {
|
export default class SummonerOverviewValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -26,21 +25,10 @@ export default class SummonerOverviewValidator {
|
||||||
*/
|
*/
|
||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
puuid: schema.string(),
|
puuid: schema.string(),
|
||||||
accountId: schema.string(),
|
|
||||||
region: schema.string(),
|
region: schema.string(),
|
||||||
season: 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -50,6 +38,7 @@ export default class SummonerOverviewValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
|
||||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
export default class SummonerRecordValidator {
|
export default class SummonerRecordValidator {
|
||||||
constructor (private ctx: HttpContextContract) {
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Defining a schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
*
|
*
|
||||||
* For example:
|
* For example:
|
||||||
* 1. The username must be of data type string. But then also, it should
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
|
@ -29,16 +28,6 @@ export default class SummonerRecordValidator {
|
||||||
season: 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 `(.)`
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
* for targeting nested fields and array expressions `(*)` for targeting all
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
|
@ -48,6 +37,7 @@ export default class SummonerRecordValidator {
|
||||||
* 'profile.username.required': 'Username is required',
|
* 'profile.username.required': 'Username is required',
|
||||||
* 'scores.*.number': 'Define scores as valid numbers'
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public messages = {}
|
public messages = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,63 @@
|
||||||
import { ParticipantBasic, ParticipantDetails } from './Models/Match'
|
/**
|
||||||
|
* All League of Legends regions used in Riot API
|
||||||
|
*/
|
||||||
|
export enum LeagueRegion {
|
||||||
|
BRAZIL = 'br1',
|
||||||
|
EUROPE_NORTHEAST = 'eun1',
|
||||||
|
EUROPE_WEST = 'euw1',
|
||||||
|
KOREA = 'kr',
|
||||||
|
LATIN_AMERICA_NORTH = 'la1',
|
||||||
|
LATIN_AMERICA_SOUTH = 'la2',
|
||||||
|
NORTH_AMERICA = 'na1',
|
||||||
|
OCEANIA = 'oc1',
|
||||||
|
RUSSIA = 'ru',
|
||||||
|
TURKEY = 'tr1',
|
||||||
|
JAPAN = 'jp1',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New regions used in Riot API >= v5
|
||||||
|
*/
|
||||||
|
export enum RiotRegion {
|
||||||
|
AMERICAS = 'americas',
|
||||||
|
ASIA = 'asia',
|
||||||
|
EUROPE = 'europe',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map old Riot API regions to new ones
|
||||||
|
* @param region : old region
|
||||||
|
* @returns new region name
|
||||||
|
*/
|
||||||
|
export function getRiotRegion(region: string): RiotRegion {
|
||||||
|
switch (
|
||||||
|
region as LeagueRegion // TODO: remove cast when region is typed to "Region" everywhere instead of string
|
||||||
|
) {
|
||||||
|
case LeagueRegion.NORTH_AMERICA:
|
||||||
|
case LeagueRegion.BRAZIL:
|
||||||
|
case LeagueRegion.LATIN_AMERICA_NORTH:
|
||||||
|
case LeagueRegion.LATIN_AMERICA_SOUTH:
|
||||||
|
case LeagueRegion.OCEANIA:
|
||||||
|
return RiotRegion.AMERICAS
|
||||||
|
case LeagueRegion.KOREA:
|
||||||
|
case LeagueRegion.JAPAN:
|
||||||
|
return RiotRegion.ASIA
|
||||||
|
case LeagueRegion.EUROPE_NORTHEAST:
|
||||||
|
case LeagueRegion.EUROPE_WEST:
|
||||||
|
case LeagueRegion.TURKEY:
|
||||||
|
case LeagueRegion.RUSSIA:
|
||||||
|
return RiotRegion.EUROPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to help define a player's role
|
||||||
|
*/
|
||||||
|
export interface PlayerRole {
|
||||||
|
champion: number
|
||||||
|
jungle?: boolean
|
||||||
|
support?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* League of Legends queues with defined role for each summoner
|
* League of Legends queues with defined role for each summoner
|
||||||
|
|
@ -13,8 +72,13 @@ export const queuesWithRole = [
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* League of Legends seasons timestamps
|
* League of Legends tutorial queues
|
||||||
*/
|
*/
|
||||||
|
export const tutorialQueues = [2000, 2010, 2020]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* League of Legends seasons timestamps
|
||||||
|
*/
|
||||||
export const seasons = {
|
export const seasons = {
|
||||||
0: 9,
|
0: 9,
|
||||||
1578628800000: 10,
|
1578628800000: 10,
|
||||||
|
|
@ -23,28 +87,47 @@ export const seasons = {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* League of Legends all support item ids
|
* League of Legends all support item ids
|
||||||
*/
|
*/
|
||||||
export 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]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get season number for a match
|
* Get season number for a match
|
||||||
* @param timestamp
|
* @param timestamp
|
||||||
*/
|
*/
|
||||||
export function getSeasonNumber (timestamp: number): number {
|
export function getSeasonNumber(timestamp: number): number {
|
||||||
const arrSeasons = Object.keys(seasons).map(k => Number(k))
|
const arrSeasons = Object.keys(seasons).map((k) => Number(k))
|
||||||
arrSeasons.push(timestamp)
|
arrSeasons.push(timestamp)
|
||||||
arrSeasons.sort()
|
arrSeasons.sort()
|
||||||
const indexSeason = arrSeasons.indexOf(timestamp) - 1
|
const indexSeason = arrSeasons.indexOf(timestamp) - 1
|
||||||
return seasons[arrSeasons[indexSeason]]
|
return seasons[arrSeasons[indexSeason]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current League of Legends season number
|
||||||
|
*/
|
||||||
|
export function getCurrentSeason(): number {
|
||||||
|
const lastTimestamp = Object.keys(seasons).pop()!
|
||||||
|
return seasons[lastTimestamp]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SortableByRole {
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort array of Players by roles according to a specific order
|
* Sort array of Players by roles according to a specific order
|
||||||
* @param a first player
|
* @param a first player
|
||||||
* @param b second player
|
* @param b second player
|
||||||
*/
|
*/
|
||||||
export function sortTeamByRole (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) {
|
export function sortTeamByRole<T extends SortableByRole>(a: T, b: T) {
|
||||||
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
|
||||||
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/46700791/9188650
|
||||||
|
export function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
|
||||||
|
if (value === null || value === undefined) return false
|
||||||
|
const testDummy: TValue = value
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
||||||
66
server/commands/LoadV4Matches.ts
Normal file
66
server/commands/LoadV4Matches.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { BaseCommand, args } from '@adonisjs/core/build/standalone'
|
||||||
|
import MatchV4Service from 'App/Services/MatchV4Service'
|
||||||
|
import SummonerService from 'App/Services/SummonerService'
|
||||||
|
|
||||||
|
export default class LoadV4Matches extends BaseCommand {
|
||||||
|
/**
|
||||||
|
* Command name is used to run the command
|
||||||
|
*/
|
||||||
|
public static commandName = 'load:v4'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command description is displayed in the "help" output
|
||||||
|
*/
|
||||||
|
public static description = 'Load matches for a given Summoner from the old Match-V4 endpoint'
|
||||||
|
|
||||||
|
@args.string({ description: 'Summoner name to seach' })
|
||||||
|
public summoner: string
|
||||||
|
|
||||||
|
@args.string({ description: 'League region of the summoner' })
|
||||||
|
public region: string
|
||||||
|
|
||||||
|
public static settings = {
|
||||||
|
/**
|
||||||
|
* Set the following value to true, if you want to load the application
|
||||||
|
* before running the command
|
||||||
|
*/
|
||||||
|
loadApp: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the following value to true, if you want this command to keep running until
|
||||||
|
* you manually decide to exit the process
|
||||||
|
*/
|
||||||
|
stayAlive: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
this.logger.info(`Trying to find ${this.summoner} from ${this.region}`)
|
||||||
|
|
||||||
|
// ACCOUNT
|
||||||
|
const account = await SummonerService.getAccount(this.summoner, this.region)
|
||||||
|
if (account) {
|
||||||
|
this.logger.success('League account found.')
|
||||||
|
} else {
|
||||||
|
return this.logger.error('League account not found.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// MATCHLIST
|
||||||
|
const matchListIds = await MatchV4Service.updateMatchList(account, this.region)
|
||||||
|
if (matchListIds.length) {
|
||||||
|
this.logger.success(`${matchListIds.length} matches in the matchlist.`)
|
||||||
|
} else {
|
||||||
|
return this.logger.error('Matchlist empty.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// MATCHES
|
||||||
|
const chunkSize = 10
|
||||||
|
let savedMatches = 0
|
||||||
|
for (let i = 0; i < matchListIds.length; i += chunkSize) {
|
||||||
|
const chunk = matchListIds.slice(i, i + chunkSize)
|
||||||
|
savedMatches += await MatchV4Service.getMatches(this.region, chunk)
|
||||||
|
this.logger.info(`${savedMatches} matches saved.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.success(`${savedMatches} matches saved for summoner ${this.summoner}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -112,7 +112,7 @@ export const http: ServerConfig = {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Force content negotiation to JSON
|
| Force Content Negotiation
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| The internals of the framework relies on the content negotiation to
|
| The internals of the framework relies on the content negotiation to
|
||||||
|
|
@ -121,9 +121,9 @@ export const http: ServerConfig = {
|
||||||
| However, it is a very common these days that API servers always wants to
|
| However, it is a very common these days that API servers always wants to
|
||||||
| make response in JSON regardless of the existence of the `Accept` header.
|
| make response in JSON regardless of the existence of the `Accept` header.
|
||||||
|
|
|
|
||||||
| By setting `forceContentNegotiationToJSON = true`, you negotiate with the
|
| By setting `forceContentNegotiationTo = 'application/json'`, you negotiate
|
||||||
| server in advance to always return JSON without relying on the client
|
| with the server in advance to always return JSON without relying on the
|
||||||
| to set the header explicitly.
|
| client to set the header explicitly.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
forceContentNegotiationTo: 'application/json',
|
forceContentNegotiationTo: 'application/json',
|
||||||
|
|
|
||||||
60
server/config/database.ts
Normal file
60
server/config/database.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JesV9
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import pg from 'pg'
|
||||||
|
import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
import { DatabaseConfig } from '@ioc:Adonis/Lucid/Database'
|
||||||
|
|
||||||
|
const databaseConfig: DatabaseConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The primary connection for making database queries across the application
|
||||||
|
| You can use any key from the `connections` object defined in this same
|
||||||
|
| file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
connection: Env.get('DB_CONNECTION'),
|
||||||
|
|
||||||
|
connections: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PostgreSQL config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configuration for PostgreSQL database. Make sure to install the driver
|
||||||
|
| from npm when using this connection
|
||||||
|
|
|
||||||
|
| npm i pg
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
pg: {
|
||||||
|
client: 'pg',
|
||||||
|
connection: {
|
||||||
|
host: Env.get('PG_HOST'),
|
||||||
|
port: Env.get('PG_PORT'),
|
||||||
|
user: Env.get('PG_USER'),
|
||||||
|
password: Env.get('PG_PASSWORD', ''),
|
||||||
|
database: Env.get('PG_DB_NAME'),
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
naturalSort: true,
|
||||||
|
},
|
||||||
|
healthCheck: true,
|
||||||
|
debug: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bigint as number instead of string in postgres
|
||||||
|
pg.types.setTypeParser(20, (value) => {
|
||||||
|
return parseInt(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default databaseConfig
|
||||||
148
server/config/drive.ts
Normal file
148
server/config/drive.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* Config source: https://git.io/JBt3o
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this config
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
import { DriveConfig } from '@ioc:Adonis/Core/Drive'
|
||||||
|
import Application from '@ioc:Adonis/Core/Application'
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Drive Config
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The `DriveConfig` relies on the `DisksList` interface which is
|
||||||
|
| defined inside the `contracts` directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
const driveConfig: DriveConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default disk
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default disk to use for managing file uploads. The value is driven by
|
||||||
|
| the `DRIVE_DISK` environment variable.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
disk: Env.get('DRIVE_DISK'),
|
||||||
|
|
||||||
|
disks: {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Local
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the local file system to manage files. Make sure to turn off serving
|
||||||
|
| files when not using this disk.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
local: {
|
||||||
|
driver: 'local',
|
||||||
|
visibility: 'public',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Storage root - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define an absolute path to the storage directory from where to read the
|
||||||
|
| files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
root: Application.tmpPath('uploads'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Serve files - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When this is set to true, AdonisJS will configure a files server to serve
|
||||||
|
| files from the disk root. This is done to mimic the behavior of cloud
|
||||||
|
| storage services that has inbuilt capabilities to serve files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
serveFiles: true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Base path - Local driver only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Base path is always required when "serveFiles = true". Also make sure
|
||||||
|
| the `basePath` is unique across all the disks using "local" driver and
|
||||||
|
| you are not registering routes with this prefix.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
basePath: '/uploads',
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| S3 Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the S3 cloud storage to manage files. Make sure to install the s3
|
||||||
|
| drive separately when using it.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| npm i @adonisjs/drive-s3
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// s3: {
|
||||||
|
// driver: 's3',
|
||||||
|
// visibility: 'public',
|
||||||
|
// key: Env.get('S3_KEY'),
|
||||||
|
// secret: Env.get('S3_SECRET'),
|
||||||
|
// region: Env.get('S3_REGION'),
|
||||||
|
// bucket: Env.get('S3_BUCKET'),
|
||||||
|
// endpoint: Env.get('S3_ENDPOINT'),
|
||||||
|
// },
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| GCS Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Uses the Google cloud storage to manage files. Make sure to install the GCS
|
||||||
|
| drive separately when using it.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| npm i @adonisjs/drive-gcs
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// gcs: {
|
||||||
|
// driver: 'gcs',
|
||||||
|
// visibility: 'public',
|
||||||
|
// keyFilename: Env.get('GCS_KEY_FILENAME'),
|
||||||
|
// bucket: Env.get('GCS_BUCKET'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uniform ACL - Google cloud storage only
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using the Uniform ACL on the bucket, the "visibility" option is
|
||||||
|
| ignored. Since, the files ACL is managed by the google bucket policies
|
||||||
|
| directly.
|
||||||
|
|
|
||||||
|
|**************************************************************************
|
||||||
|
| Learn more: https://cloud.google.com/storage/docs/uniform-bucket-level-access
|
||||||
|
|**************************************************************************
|
||||||
|
|
|
||||||
|
| The following option just informs drive whether your bucket is using uniform
|
||||||
|
| ACL or not. The actual setting needs to be toggled within the Google cloud
|
||||||
|
| console.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
// usingUniformAcl: false
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default driveConfig
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { MongodbConfig } from '@ioc:Mongodb/Database'
|
|
||||||
import Env from '@ioc:Adonis/Core/Env'
|
|
||||||
|
|
||||||
const config: MongodbConfig = {
|
|
||||||
default: 'mongodb',
|
|
||||||
connections: {
|
|
||||||
mongodb: {
|
|
||||||
url: Env.get('MONGODB_URL') as string,
|
|
||||||
database: Env.get('MONGODB_DATABASE') as string,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
|
|
@ -41,6 +41,7 @@ const redisConfig: RedisConfig = {
|
||||||
password: Env.get('REDIS_PASSWORD', ''),
|
password: Env.get('REDIS_PASSWORD', ''),
|
||||||
db: 0,
|
db: 0,
|
||||||
keyPrefix: '',
|
keyPrefix: '',
|
||||||
|
healthCheck: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
server/contracts/drive.ts
Normal file
23
server/contracts/drive.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Contract source: https://git.io/JBt3I
|
||||||
|
*
|
||||||
|
* Feel free to let us know via PR, if you find something broken in this contract
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '@ioc:Adonis/Core/Drive' {
|
||||||
|
interface DisksList {
|
||||||
|
local: {
|
||||||
|
config: LocalDriverConfig
|
||||||
|
implementation: LocalDriverContract
|
||||||
|
}
|
||||||
|
// s3: {
|
||||||
|
// config: S3DriverConfig
|
||||||
|
// implementation: S3DriverContract
|
||||||
|
// }
|
||||||
|
// gcs: {
|
||||||
|
// config: GcsDriverConfig
|
||||||
|
// implementation: GcsDriverContract
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,5 @@ declare module '@ioc:Adonis/Core/Event' {
|
||||||
| an instance of the the UserModel only.
|
| an instance of the the UserModel only.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
interface EventsList {
|
interface EventsList {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare module '@ioc:Adonis/Core/Hash' {
|
declare module '@ioc:Adonis/Core/Hash' {
|
||||||
import { HashDrivers } from '@ioc:Adonis/Core/Hash'
|
|
||||||
|
|
||||||
interface HashersList {
|
interface HashersList {
|
||||||
bcrypt: {
|
bcrypt: {
|
||||||
config: BcryptConfig,
|
config: BcryptConfig
|
||||||
implementation: BcryptContract,
|
implementation: BcryptContract
|
||||||
},
|
}
|
||||||
argon: {
|
argon: {
|
||||||
config: ArgonConfig,
|
config: ArgonConfig
|
||||||
implementation: ArgonContract,
|
implementation: ArgonContract
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@
|
||||||
|
|
||||||
declare module '@ioc:Adonis/Addons/Redis' {
|
declare module '@ioc:Adonis/Addons/Redis' {
|
||||||
interface RedisConnectionsList {
|
interface RedisConnectionsList {
|
||||||
local: RedisConnectionConfig,
|
local: RedisConnectionConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
server/database/factories/index.ts
Normal file
1
server/database/factories/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// import Factory from '@ioc:Adonis/Lucid/Factory'
|
||||||
26
server/database/migrations/1631392754960_matches.ts
Normal file
26
server/database/migrations/1631392754960_matches.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class Matches extends BaseSchema {
|
||||||
|
protected tableName = 'matches'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.string('id', 15).primary()
|
||||||
|
table.bigInteger('game_id').notNullable()
|
||||||
|
table.specificType('map', 'smallint').notNullable()
|
||||||
|
table.specificType('gamemode', 'smallint').notNullable().index()
|
||||||
|
table.bigInteger('date').notNullable()
|
||||||
|
table.string('region', 4).notNullable()
|
||||||
|
table.specificType('result', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.float('season').notNullable().index()
|
||||||
|
table.specificType('game_duration', 'smallint').unsigned().notNullable()
|
||||||
|
})
|
||||||
|
|
||||||
|
// this.schema.alterTable
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
server/database/migrations/1631392766690_match_players.ts
Normal file
74
server/database/migrations/1631392766690_match_players.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class MatchPlayers extends BaseSchema {
|
||||||
|
protected tableName = 'match_players'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('match_id', 15).notNullable().index()
|
||||||
|
|
||||||
|
table.specificType('participant_id', 'smallint').notNullable()
|
||||||
|
table.string('summoner_id', 63).notNullable()
|
||||||
|
table.string('summoner_puuid', 78).notNullable().index()
|
||||||
|
table.string('summoner_name', 16).notNullable()
|
||||||
|
|
||||||
|
table.specificType('win', 'smallint').notNullable()
|
||||||
|
table.specificType('loss', 'smallint').notNullable()
|
||||||
|
table.specificType('remake', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.specificType('team', 'smallint').notNullable().index()
|
||||||
|
table.specificType('team_position', 'smallint').notNullable().index()
|
||||||
|
|
||||||
|
table.specificType('kills', 'smallint').unsigned().notNullable()
|
||||||
|
table.specificType('deaths', 'smallint').unsigned().notNullable()
|
||||||
|
table.specificType('assists', 'smallint').unsigned().notNullable()
|
||||||
|
table.float('kda').notNullable()
|
||||||
|
table.float('kp').notNullable()
|
||||||
|
|
||||||
|
table.specificType('champ_level', 'smallint').notNullable()
|
||||||
|
table.specificType('champion_id', 'smallint').notNullable().index()
|
||||||
|
table.specificType('champion_role', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.specificType('double_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('triple_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('quadra_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('penta_kills', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.specificType('baron_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('dragon_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('turret_kills', 'smallint').notNullable()
|
||||||
|
table.specificType('vision_score', 'smallint').notNullable()
|
||||||
|
table.integer('gold').notNullable()
|
||||||
|
|
||||||
|
table.integer('summoner1_id').notNullable()
|
||||||
|
table.integer('summoner2_id').notNullable()
|
||||||
|
|
||||||
|
table.integer('item0').notNullable()
|
||||||
|
table.integer('item1').notNullable()
|
||||||
|
table.integer('item2').notNullable()
|
||||||
|
table.integer('item3').notNullable()
|
||||||
|
table.integer('item4').notNullable()
|
||||||
|
table.integer('item5').notNullable()
|
||||||
|
table.integer('item6').notNullable()
|
||||||
|
|
||||||
|
table.integer('damage_dealt_objectives').notNullable()
|
||||||
|
table.integer('damage_dealt_champions').notNullable()
|
||||||
|
table.integer('damage_taken').notNullable()
|
||||||
|
table.integer('heal').notNullable()
|
||||||
|
table.specificType('minions', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.specificType('critical_strike', 'smallint').unsigned().notNullable()
|
||||||
|
table.specificType('killing_spree', 'smallint').unsigned().notNullable()
|
||||||
|
table.specificType('time_spent_living', 'smallint').unsigned().notNullable()
|
||||||
|
|
||||||
|
table.integer('perks_primary_style').notNullable()
|
||||||
|
table.integer('perks_secondary_style').notNullable()
|
||||||
|
table.specificType('perks_selected', 'INT[]').notNullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
21
server/database/migrations/1631392773430_summoners.ts
Normal file
21
server/database/migrations/1631392773430_summoners.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class Summoners extends BaseSchema {
|
||||||
|
protected tableName = 'summoners'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.string('puuid', 78).primary()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true })
|
||||||
|
table.timestamp('updated_at', { useTz: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
server/database/migrations/1631392778620_summoner_names.ts
Normal file
26
server/database/migrations/1631392778620_summoner_names.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class SummonerNames extends BaseSchema {
|
||||||
|
protected tableName = 'summoner_names'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('summoner_puuid', 78).notNullable().index()
|
||||||
|
table.string('name', 16).notNullable().index()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
|
||||||
|
*/
|
||||||
|
table.timestamp('created_at', { useTz: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
this.schema.alterTable(this.tableName, (table) => {
|
||||||
|
table.unique(['summoner_puuid', 'name'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class SummonerMatchLists extends BaseSchema {
|
||||||
|
protected tableName = 'summoner_matchlist'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('summoner_puuid', 78).notNullable().index()
|
||||||
|
table.string('match_id', 15).notNullable().index()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.schema.alterTable(this.tableName, (table) => {
|
||||||
|
table.unique(['summoner_puuid', 'match_id'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
server/database/migrations/1631397498477_match_teams.ts
Normal file
28
server/database/migrations/1631397498477_match_teams.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
|
||||||
|
|
||||||
|
export default class MatchTeams extends BaseSchema {
|
||||||
|
protected tableName = 'match_teams'
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
|
table.increments('id')
|
||||||
|
table.string('match_id', 15).index()
|
||||||
|
|
||||||
|
table.specificType('color', 'smallint').notNullable().index() // 100 ou 200
|
||||||
|
table.string('result', 6) // Win - Remake - Fail
|
||||||
|
|
||||||
|
table.specificType('barons', 'smallint').notNullable()
|
||||||
|
table.specificType('dragons', 'smallint').notNullable()
|
||||||
|
table.specificType('inhibitors', 'smallint').notNullable()
|
||||||
|
table.specificType('rift_heralds', 'smallint').notNullable()
|
||||||
|
table.specificType('towers', 'smallint').notNullable()
|
||||||
|
|
||||||
|
table.specificType('bans', 'smallint[]').nullable()
|
||||||
|
table.specificType('ban_orders', 'smallint[]').nullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue