mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 12:57:28 +00:00
feat: refactor queries + add records query
This commit is contained in:
parent
2a2fb61c58
commit
75b1007ec0
7 changed files with 160 additions and 55 deletions
|
|
@ -1,12 +1,14 @@
|
||||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
import { getCurrentSeason } from 'App/helpers'
|
import { getCurrentSeason } from 'App/helpers'
|
||||||
import Summoner from 'App/Models/Summoner'
|
import Summoner from 'App/Models/Summoner'
|
||||||
|
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||||
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 SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
|
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
|
||||||
import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
|
import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
|
||||||
|
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
|
||||||
|
|
||||||
export default class SummonersController {
|
export default class SummonersController {
|
||||||
public async basic({ request, response }: HttpContextContract) {
|
public async basic({ request, response }: HttpContextContract) {
|
||||||
|
|
@ -83,4 +85,16 @@ export default class SummonersController {
|
||||||
console.timeEnd('OVERVIEW_REQUEST')
|
console.timeEnd('OVERVIEW_REQUEST')
|
||||||
return response.json(finalJSON)
|
return response.json(finalJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST: get records view summoner data
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public async records({ request, response }: HttpContextContract) {
|
||||||
|
console.time('recordsRequest')
|
||||||
|
const { puuid, season } = await request.validate(SummonerRecordValidator)
|
||||||
|
const records = await MatchRepository.records(puuid)
|
||||||
|
console.timeEnd('recordsRequest')
|
||||||
|
return response.json(records)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,15 @@ export default class MatchPlayer extends BaseModel {
|
||||||
@column()
|
@column()
|
||||||
public teamPosition: number
|
public teamPosition: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public win: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public loss: number
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public remake: number
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public kills: number
|
public kills: number
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,12 @@ class MatchParser {
|
||||||
gameDuration: Math.round(match.info.gameDuration / 1000),
|
gameDuration: Math.round(match.info.gameDuration / 1000),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isRemake = match.info.gameDuration < 300
|
||||||
|
|
||||||
// - 2x MatchTeam : Red and Blue
|
// - 2x MatchTeam : Red and Blue
|
||||||
for (const team of match.info.teams) {
|
for (const team of match.info.teams) {
|
||||||
let result = team.win ? 'Win' : 'Fail'
|
let result = team.win ? 'Win' : 'Fail'
|
||||||
if (match.info.gameDuration < 300) {
|
if (isRemake) {
|
||||||
result = 'Remake'
|
result = 'Remake'
|
||||||
}
|
}
|
||||||
await parsedMatch.related('teams').create({
|
await parsedMatch.related('teams').create({
|
||||||
|
|
@ -47,10 +49,9 @@ class MatchParser {
|
||||||
? player.kills + player.assists
|
? player.kills + player.assists
|
||||||
: +(player.deaths === 0 ? 0 : (player.kills + player.assists) / player.deaths).toFixed(2)
|
: +(player.deaths === 0 ? 0 : (player.kills + player.assists) / player.deaths).toFixed(2)
|
||||||
|
|
||||||
const teamKills =
|
const team =
|
||||||
match.info.teams[0].teamId === player.teamId
|
match.info.teams[0].teamId === player.teamId ? match.info.teams[0] : match.info.teams[1]
|
||||||
? match.info.teams[0].objectives.champion.kills
|
const teamKills = team.objectives.champion.kills
|
||||||
: match.info.teams[1].objectives.champion.kills
|
|
||||||
|
|
||||||
const kp =
|
const kp =
|
||||||
teamKills === 0 ? 0 : +(((player.kills + player.assists) * 100) / teamKills).toFixed(1)
|
teamKills === 0 ? 0 : +(((player.kills + player.assists) * 100) / teamKills).toFixed(1)
|
||||||
|
|
@ -87,6 +88,9 @@ class MatchParser {
|
||||||
summoner_id: player.summonerId,
|
summoner_id: player.summonerId,
|
||||||
summoner_puuid: player.puuid,
|
summoner_puuid: player.puuid,
|
||||||
summoner_name: player.summonerName,
|
summoner_name: player.summonerName,
|
||||||
|
win: team.win ? 1 : 0,
|
||||||
|
loss: team.win ? 0 : 1,
|
||||||
|
remake: isRemake ? 1 : 0,
|
||||||
team: player.teamId,
|
team: player.teamId,
|
||||||
team_position:
|
team_position:
|
||||||
player.teamPosition.length && queuesWithRole.includes(match.info.queueId)
|
player.teamPosition.length && queuesWithRole.includes(match.info.queueId)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ class MatchRepository {
|
||||||
|
|
||||||
private readonly GLOBAL_FILTERS = `
|
private readonly GLOBAL_FILTERS = `
|
||||||
match_players.summoner_puuid = :puuid
|
match_players.summoner_puuid = :puuid
|
||||||
AND match_teams.result != 'Remake'
|
AND match_players.remake = 0
|
||||||
AND matches.gamemode NOT IN (800, 810, 820, 830, 840, 850, 2000, 2010, 2020)
|
AND matches.gamemode NOT IN (800, 810, 820, 830, 840, 850, 2000, 2010, 2020)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -34,19 +34,19 @@ class MatchRepository {
|
||||||
public async globalStats(puuid: string) {
|
public async globalStats(puuid: string) {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
SUM(assists) as assists,
|
SUM(match_players.assists) as assists,
|
||||||
SUM(deaths) as deaths,
|
SUM(match_players.deaths) as deaths,
|
||||||
SUM(kills) as kills,
|
SUM(match_players.kills) as kills,
|
||||||
SUM(minions) as minions,
|
SUM(match_players.minions) as minions,
|
||||||
SUM(matches.game_duration) as time,
|
SUM(matches.game_duration) as time,
|
||||||
SUM(vision_score) as vision,
|
SUM(match_players.vision_score) as vision,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
AVG(kp) as kp,
|
AVG(match_players.kp) as kp,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_MATCHES}
|
||||||
WHERE
|
WHERE
|
||||||
${this.GLOBAL_FILTERS}
|
${this.GLOBAL_FILTERS}
|
||||||
LIMIT
|
LIMIT
|
||||||
|
|
@ -83,11 +83,11 @@ class MatchRepository {
|
||||||
SELECT
|
SELECT
|
||||||
matches.gamemode as id,
|
matches.gamemode as id,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_MATCHES}
|
||||||
WHERE
|
WHERE
|
||||||
${this.GLOBAL_FILTERS}
|
${this.GLOBAL_FILTERS}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
|
|
@ -104,11 +104,11 @@ class MatchRepository {
|
||||||
SELECT
|
SELECT
|
||||||
match_players.team_position as role,
|
match_players.team_position as role,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_MATCHES}
|
||||||
WHERE
|
WHERE
|
||||||
${this.GLOBAL_FILTERS}
|
${this.GLOBAL_FILTERS}
|
||||||
AND match_players.team_position != 0
|
AND match_players.team_position != 0
|
||||||
|
|
@ -123,15 +123,15 @@ class MatchRepository {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
match_players.champion_id as id,
|
match_players.champion_id as id,
|
||||||
SUM(assists) as assists,
|
SUM(match_players.assists) as assists,
|
||||||
SUM(deaths) as deaths,
|
SUM(match_players.deaths) as deaths,
|
||||||
SUM(kills) as kills,
|
SUM(match_players.kills) as kills,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_MATCHES}
|
||||||
WHERE
|
WHERE
|
||||||
${this.GLOBAL_FILTERS}
|
${this.GLOBAL_FILTERS}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
|
|
@ -150,11 +150,11 @@ class MatchRepository {
|
||||||
SELECT
|
SELECT
|
||||||
match_players.champion_role as id,
|
match_players.champion_role as id,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_MATCHES}
|
||||||
WHERE
|
WHERE
|
||||||
${this.GLOBAL_FILTERS}
|
${this.GLOBAL_FILTERS}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
|
|
@ -171,8 +171,8 @@ class MatchRepository {
|
||||||
SELECT
|
SELECT
|
||||||
(array_agg(mates.summoner_name ORDER BY mates.match_id DESC))[1] as name,
|
(array_agg(mates.summoner_name ORDER BY mates.match_id DESC))[1] as name,
|
||||||
COUNT(match_players.id) as count,
|
COUNT(match_players.id) as count,
|
||||||
COUNT(case when match_teams.result = 'Win' then 1 else null end) as wins,
|
SUM(match_players.win) as wins,
|
||||||
COUNT(case when match_teams.result = 'Fail' then 1 else null end) as losses
|
SUM(match_players.loss) as losses
|
||||||
FROM
|
FROM
|
||||||
match_players
|
match_players
|
||||||
${this.JOIN_ALL}
|
${this.JOIN_ALL}
|
||||||
|
|
@ -191,6 +191,59 @@ class MatchRepository {
|
||||||
// Remove the Summoner himself + unique game mates
|
// Remove the Summoner himself + unique game mates
|
||||||
return rows.splice(1).filter((row) => row.count > 1)
|
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',
|
||||||
|
]
|
||||||
|
|
||||||
|
const query = fields
|
||||||
|
.map((field) => {
|
||||||
|
return `
|
||||||
|
(SELECT
|
||||||
|
'${field}' AS what,
|
||||||
|
${field} AS amount,
|
||||||
|
match_players.win as result,
|
||||||
|
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 })
|
||||||
|
return rows
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MatchRepository()
|
export default new MatchRepository()
|
||||||
|
|
|
||||||
43
server-v2/app/Validators/SummonerRecordValidator.ts
Normal file
43
server-v2/app/Validators/SummonerRecordValidator.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
|
||||||
|
export default class SummonerRecordValidator {
|
||||||
|
constructor(protected ctx: HttpContextContract) {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define schema to validate the "shape", "type", "formatting" and "integrity" of data.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
* 1. The username must be of data type string. But then also, it should
|
||||||
|
* not contain special characters or numbers.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [ rules.alpha() ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. The email must be of data type string, formatted as a valid
|
||||||
|
* email. But also, not used by any other user.
|
||||||
|
* ```
|
||||||
|
* schema.string({}, [
|
||||||
|
* rules.email(),
|
||||||
|
* rules.unique({ table: 'users', column: 'email' }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public schema = schema.create({
|
||||||
|
puuid: schema.string(),
|
||||||
|
season: schema.number.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* for targeting nested fields and array expressions `(*)` for targeting all
|
||||||
|
* children of an array. For example:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* 'profile.username.required': 'Username is required',
|
||||||
|
* 'scores.*.number': 'Define scores as valid numbers'
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public messages = {}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,10 @@ export default class MatchPlayers extends BaseSchema {
|
||||||
table.string('summoner_puuid', 78).notNullable()
|
table.string('summoner_puuid', 78).notNullable()
|
||||||
table.string('summoner_name', 16).notNullable()
|
table.string('summoner_name', 16).notNullable()
|
||||||
|
|
||||||
|
table.specificType('win', 'smallint').notNullable()
|
||||||
|
table.specificType('loss', 'smallint').notNullable()
|
||||||
|
table.specificType('remake', 'smallint').notNullable()
|
||||||
|
|
||||||
table.integer('team').notNullable()
|
table.integer('team').notNullable()
|
||||||
table.integer('team_position').notNullable()
|
table.integer('team_position').notNullable()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@
|
||||||
|
|
||||||
import HealthCheck from '@ioc:Adonis/Core/HealthCheck'
|
import HealthCheck from '@ioc:Adonis/Core/HealthCheck'
|
||||||
import Route from '@ioc:Adonis/Core/Route'
|
import Route from '@ioc:Adonis/Core/Route'
|
||||||
import MatchParser from 'App/Parsers/MatchParser'
|
|
||||||
import Jax from 'App/Services/Jax'
|
|
||||||
|
|
||||||
Route.get('/', async () => ({
|
Route.get('/', async () => ({
|
||||||
hi: 'Hello World from LeagueStats V2 API',
|
hi: 'Hello World from LeagueStats V2 API',
|
||||||
|
|
@ -34,7 +32,7 @@ Route.post('/summoner/basic', 'SummonersController.basic')
|
||||||
Route.post('/summoner/overview', 'SummonersController.overview')
|
Route.post('/summoner/overview', 'SummonersController.overview')
|
||||||
|
|
||||||
// Route.post('/summoner/champions', 'SummonersController.champions')
|
// Route.post('/summoner/champions', 'SummonersController.champions')
|
||||||
// Route.post('/summoner/records', 'SummonersController.records')
|
Route.post('/summoner/records', 'SummonersController.records')
|
||||||
// Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
// Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
|
||||||
|
|
||||||
Route.post('/match', 'MatchesController.index')
|
Route.post('/match', 'MatchesController.index')
|
||||||
|
|
@ -42,23 +40,3 @@ Route.post('/match', 'MatchesController.index')
|
||||||
// Route.post('/match/details/ranks', 'MatchesController.showRanks')
|
// Route.post('/match/details/ranks', 'MatchesController.showRanks')
|
||||||
|
|
||||||
Route.get('/cdragon/runes', 'CDragonController.runes')
|
Route.get('/cdragon/runes', 'CDragonController.runes')
|
||||||
|
|
||||||
Route.get('/test', async () => {
|
|
||||||
const ids = [
|
|
||||||
'EUW1_5221171940',
|
|
||||||
'EUW1_5220845489',
|
|
||||||
'EUW1_5220852134',
|
|
||||||
'EUW1_5220728352',
|
|
||||||
'EUW1_5220656980',
|
|
||||||
'EUW1_5215357679',
|
|
||||||
'EUW1_5215311330',
|
|
||||||
'EUW1_5215244329',
|
|
||||||
'EUW1_5214301786',
|
|
||||||
'EUW1_5212337578',
|
|
||||||
'EUW1_5212353922',
|
|
||||||
'EUW1_5212371343',
|
|
||||||
]
|
|
||||||
const region = 'euw1'
|
|
||||||
const matches = await Promise.all(ids.map((id) => Jax.Match.get(id, region)))
|
|
||||||
await MatchParser.parse(matches)
|
|
||||||
})
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue