feat: delete old server folder + rename new one

This commit is contained in:
Kalane 2021-09-20 19:20:51 +02:00
parent 5e33d0b0ba
commit 93f23a0d33
153 changed files with 4828 additions and 23836 deletions

View file

@ -1,37 +0,0 @@
{
"typescript": true,
"commands": [
"./commands",
"@adonisjs/core/build/commands/index.js",
"@adonisjs/repl/build/commands",
"@adonisjs/lucid/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Config": "config",
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel",
{
"file": "./start/events",
"environment": [
"console",
"repl",
"web"
]
}
],
"providers": [
"./providers/AppProvider",
"@adonisjs/core",
"@adonisjs/lucid",
"@adonisjs/redis"
],
"aceProviders": [
"@adonisjs/repl"
]
}

View file

@ -1,14 +0,0 @@
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false

View file

@ -1,19 +0,0 @@
PORT=3333
HOST=0.0.0.0
NODE_ENV=development
APP_KEY=
DRIVE_DISK=local
DB_CONNECTION=pg
PG_HOST=localhost
PG_PORT=5432
PG_USER=
PG_PASSWORD=
PG_DB_NAME=
REDIS_CONNECTION=local
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
RIOT_API_KEY=

View file

@ -1 +0,0 @@
build

View file

@ -1,7 +0,0 @@
{
"extends": ["plugin:adonis/typescriptApp", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"]
}
}

View file

@ -1,7 +0,0 @@
node_modules
build
coverage
.vscode
.DS_STORE
.env
tmp

View file

@ -1,16 +0,0 @@
/*
|--------------------------------------------------------------------------
| Ace Commands
|--------------------------------------------------------------------------
|
| This file is the entry point for running ace commands.
|
*/
require('reflect-metadata')
require('source-map-support').install({ handleUncaughtExceptions: false })
const { Ignitor } = require('@adonisjs/core/build/standalone')
new Ignitor(__dirname)
.ace()
.handle(process.argv.slice(2))

View file

@ -1,294 +0,0 @@
{
"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": {}
}

View file

@ -1,27 +0,0 @@
import Redis from '@ioc:Adonis/Addons/Redis'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import RuneSerializer from 'App/Serializers/RuneSerializer'
import Jax from 'App/Services/Jax'
export default class CDragonController {
public async runes({ response }: HttpContextContract) {
const cacheUrl = 'cdragon-runes'
const requestCached = await Redis.get(cacheUrl)
if (requestCached) {
return response.json(requestCached)
}
const perks = await Jax.CDragon.perks()
const perkstyles = await Jax.CDragon.perkstyles()
const runesData = {
perks: RuneSerializer.serializePerks(perks),
perkstyles: RuneSerializer.serializeStyles(perkstyles.styles),
}
await Redis.set(cacheUrl, JSON.stringify(runesData), 'EX', 36000)
return response.json(runesData)
}
}

View file

@ -1,67 +0,0 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Match from 'App/Models/Match'
import MatchPlayerRankParser from 'App/Parsers/MatchPlayerRankParser'
import DetailedMatchSerializer from 'App/Serializers/DetailedMatchSerializer'
import MatchPlayerRankSerializer from 'App/Serializers/MatchPlayerRankSerializer'
import MatchService from 'App/Services/MatchService'
import StatsService from 'App/Services/StatsService'
import DetailedMatchValidator from 'App/Validators/DetailedMatchValidator'
import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
export default class MatchesController {
/**
* POST - Return data from matches searched by matchIds
* @param ctx
*/
public async index({ request, response }: HttpContextContract) {
const { puuid, region, matchIds, season } = await request.validate(MatchesIndexValidator)
const matches = await MatchService.getMatches(region, matchIds, puuid)
const stats = await StatsService.getSummonerStats(puuid, season)
return response.json({
matches,
stats,
})
}
/**
* POST - Return details data for one specific match
* @param ctx
*/
public async show({ request, response }: HttpContextContract) {
console.time('MatchDetails')
const { matchId } = await request.validate(DetailedMatchValidator)
const match = await Match.query()
.where('id', matchId)
.preload('teams')
.preload('players', (playersQuery) => {
playersQuery.preload('ranks')
})
.firstOrFail()
const { match: matchDetails, ranksLoaded } = DetailedMatchSerializer.serializeOneMatch(match)
console.timeEnd('MatchDetails')
return response.json({
matchDetails,
ranksLoaded,
})
}
/**
* POST - Return ranks of players for a specific game
* @param ctx
*/
public async showRanks({ request, response }: HttpContextContract) {
console.time('Ranks')
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)
console.timeEnd('Ranks')
return response.json(serializedRanks)
}
}

View file

@ -1,148 +0,0 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { getCurrentSeason } from 'App/helpers'
import Summoner from 'App/Models/Summoner'
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 MatchService from 'App/Services/MatchService'
import StatsService from 'App/Services/StatsService'
import SummonerService from 'App/Services/SummonerService'
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator'
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
export default class SummonersController {
public async basic({ request, response }: HttpContextContract) {
console.time('BASIC_REQUEST')
const { summoner, region } = await request.validate(SummonerBasicValidator)
const finalJSON: any = {}
try {
const account = await SummonerService.getAccount(summoner, region)
// Check if the summoner is found
if (!account) {
return response.json(null)
}
finalJSON.account = account
// Summoner in DB
const summonerDB = await Summoner.firstOrCreate({ puuid: account.puuid })
// Summoner names
finalJSON.account.names = await SummonerService.getAllSummonerNames(account, summonerDB)
// MATCH LIST
finalJSON.matchList = await MatchService.updateMatchList(account, region, summonerDB)
// All seasons the summoner has played
// TODO: check if there is a way to do that with V5...
finalJSON.seasons = [getCurrentSeason()]
// CURRENT GAME
const currentGame = await Jax.Spectator.summonerID(account.id, region)
finalJSON.playing = !!currentGame
finalJSON.current = currentGame
// RANKED STATS
finalJSON.ranked = await SummonerService.getRanked(account.id, region)
// RECENT ACTIVITY
finalJSON.recentActivity = await StatsService.getRecentActivity(account.puuid)
} catch (e) {
console.log(e)
console.timeEnd('BASIC_REQUEST')
return response.json(null)
}
console.timeEnd('BASIC_REQUEST')
return response.json(finalJSON)
}
public async overview({ request, response }: HttpContextContract) {
console.time('OVERVIEW_REQUEST')
const { puuid, region, season } = await request.validate(SummonerOverviewValidator)
const finalJSON: any = {}
// Summoner in DB
const summonerDB = await Summoner.firstOrCreate({ puuid: puuid })
// MATCHES BASIC
const matchlist = await summonerDB
.related('matchList')
.query()
.select('matchId')
.orderBy('matchId', 'desc')
.limit(10)
const matchIds = matchlist.map((m) => m.matchId)
finalJSON.matchesDetails = await MatchService.getMatches(region, matchIds, puuid)
console.time('STATS')
finalJSON.stats = await StatsService.getSummonerStats(puuid, season)
console.timeEnd('STATS')
console.timeEnd('OVERVIEW_REQUEST')
return response.json(finalJSON)
}
/**
* POST: get champions view summoner data
* @param ctx
*/
public async champions({ request, response }: HttpContextContract) {
console.time('championsRequest')
const { puuid, queue, season } = await request.validate(SummonerChampionValidator)
const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
const championStatsSerialized = championStats.map((champion) => {
return {
...champion,
champion: BasicMatchSerializer.getChampion(champion.id),
}
})
console.timeEnd('championsRequest')
return response.json(championStatsSerialized)
}
/**
* 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)
const recordsSerialized = records.map((record) => {
return {
...record,
what: record.what.split('.')[1],
champion: BasicMatchSerializer.getChampion(record.champion_id),
}
})
console.timeEnd('recordsRequest')
return response.json(recordsSerialized)
}
/**
* POST - Return live match detail
* @param ctx
*/
public async liveMatchDetails({ request, response }: HttpContextContract) {
console.time('liveMatchDetails')
const { id, region } = await request.validate(SummonerLiveValidator)
// CURRENT GAME
const currentGame = await Jax.Spectator.summonerID(id, region)
if (!currentGame) {
return response.json(null)
}
const currentGameSerialized = await LiveMatchSerializer.serializeOneMatch(currentGame, region)
console.timeEnd('liveMatchDetails')
return response.json(currentGameSerialized)
}
}

View file

@ -1,23 +0,0 @@
/*
|--------------------------------------------------------------------------
| Http Exception Handler
|--------------------------------------------------------------------------
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
| the following class. You can learn more about exception handling by
| reading docs.
|
| The exception handler extends a base `HttpExceptionHandler` which is not
| mandatory, however it can do lot of heavy lifting to handle the errors
| properly.
|
*/
import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
constructor() {
super(Logger)
}
}

View file

@ -1,40 +0,0 @@
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'
import MatchPlayer from './MatchPlayer'
import MatchTeam from './MatchTeam'
export default class Match extends BaseModel {
public static selfAssignPrimaryKey = true
@column({ isPrimary: true })
public id: string
@column()
public gameId: number
@column()
public map: number
@column()
public gamemode: number
@column()
public date: number
@column()
public region: string
@column()
public result: number
@column()
public season: number
@column()
public gameDuration: number
@hasMany(() => MatchTeam)
public teams: HasMany<typeof MatchTeam>
@hasMany(() => MatchPlayer)
public players: HasMany<typeof MatchPlayer>
}

View file

@ -1,36 +0,0 @@
import { DateTime } from 'luxon'
import { BaseModel, column, HasMany, hasMany } from '@ioc:Adonis/Lucid/Orm'
import SummonerMatchlist from './SummonerMatchlist'
import SummonerName from './SummonerName'
import MatchPlayer from './MatchPlayer'
export default class Summoner extends BaseModel {
public static selfAssignPrimaryKey = true
@column({ isPrimary: true })
public puuid: string
@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>
}

View file

@ -1,258 +0,0 @@
import Database from '@ioc:Adonis/Lucid/Database'
class MatchRepository {
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}`
private readonly GLOBAL_FILTERS = `
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) {
const query = `
SELECT
SUM(match_players.kills) as kills,
SUM(match_players.deaths) as deaths,
SUM(match_players.assists) as assists,
SUM(match_players.minions) as minions,
SUM(matches.game_duration) as time,
SUM(match_players.vision_score) as vision,
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) {
const query = `
SELECT
matches.gamemode 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
matches.gamemode
ORDER BY
count DESC
`
const { rows } = await Database.rawQuery(query, { puuid })
return rows
}
public async roleStats(puuid: string) {
const query = `
SELECT
match_players.team_position as role,
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}
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) {
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
FROM
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
}
public async championClassStats(puuid: string) {
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',
]
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()

View file

@ -1,21 +0,0 @@
import Env from '@ioc:Adonis/Core/Env'
export interface JaxConfig {
key: string
region: string
requestOptions: JaxConfigRequestOptions
}
export interface JaxConfigRequestOptions {
retriesBeforeAbort: number
delayBeforeRetry: number
}
export const JAX_CONFIG: JaxConfig = {
key: Env.get('RIOT_API_KEY') as string,
region: 'euw1',
requestOptions: {
retriesBeforeAbort: 3,
delayBeforeRetry: 1000,
},
}

View file

@ -1,4 +0,0 @@
import Jax from './src/Jax'
import { JAX_CONFIG } from './JaxConfig'
export = new Jax(JAX_CONFIG)

View file

@ -1,53 +0,0 @@
import { promisify } from 'util'
import got from 'got'
import Logger from '@ioc:Adonis/Core/Logger'
import Redis from '@ioc:Adonis/Addons/Redis'
import { JaxConfig } from '../JaxConfig'
export default class CDragonRequest {
private config: JaxConfig
private endpoint: string
private cacheTime: number
private retries: number
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
constructor(config: JaxConfig, endpoint: string, cacheTime: number) {
this.config = config
this.endpoint = endpoint
this.cacheTime = cacheTime
this.retries = config.requestOptions.retriesBeforeAbort
this.sleep = promisify(setTimeout)
}
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/items.json
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perks.json
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.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
public async execute() {
const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}`
const requestCached = await Redis.get(url)
if (requestCached) {
return JSON.parse(requestCached)
}
try {
const response = await got(url)
await Redis.set(url, response.body, 'EX', this.cacheTime)
return JSON.parse(response.body)
} catch (error) {
this.retries--
Logger.error('CDragon Error : ', error)
if (this.retries > 0) {
await this.sleep(this.config.requestOptions.delayBeforeRetry)
return this.execute()
}
}
}
}

View file

@ -1,104 +0,0 @@
import { JaxConfig } from '../../JaxConfig'
import CDragonRequest from '../CDragonRequest'
export interface ChampionDTO {
id: number
name: string
alias: string
squarePortraitPath: string
roles: string[]
}
export interface ItemDTO {
id: number
name: string
description: string
active: boolean
inStore: boolean
from: number[]
to: number[]
categories: string[]
mapStringIdInclusions: string[]
maxStacks: number
modeNameInclusions: string[]
requiredChampion: string
requiredAlly: string
requiredBuffCurrencyName: string
requiredBuffCurrencyCost: number
specialRecipe: number
isEnchantment: boolean
price: number
priceTotal: number
iconPath: string
}
export interface PerkDTO {
id: number
name: string
majorChangePatchVersion: string
tooltip: string
shortDesc: string
longDesc: string
iconPath: string
endOfGameStatDescs: string[]
}
export interface PerkStyleResponse {
schemaVersion: string
styles: PerkStyleDTO[]
}
export interface PerkStyleDTO {
id: number
name: string
tooltip: string
iconPath: string
assetMap: { [key: string]: string }
isAdvanced: boolean
allowedSubStyles: number[]
subStyleBonus: { styleId: number; perkId: number }[]
slots: { type: string; slotLabel: string; perks: number[] }[]
defaultPageName: string
defaultSubStyle: number
defaultPerks: number[]
defaultPerksWhenSplashed: number[]
defaultStatModsPerSubStyle: { id: string; perks: number[] }[]
}
export interface SummonerSpellDTO {
id: number
name: string
description: string
summonerLevel: number
cooldown: number
gameModes: string[]
iconPath: string
}
export default class CDragonEndpoint {
private config: JaxConfig
constructor(config: JaxConfig) {
this.config = config
}
public async champions(): Promise<ChampionDTO[]> {
return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute()
}
public async items(): Promise<ItemDTO[]> {
return new CDragonRequest(this.config, 'items.json', 36000).execute()
}
public async perks(): Promise<PerkDTO[]> {
return new CDragonRequest(this.config, 'perks.json', 36000).execute()
}
public async perkstyles(): Promise<PerkStyleResponse> {
return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute()
}
public async summonerSpells(): Promise<SummonerSpellDTO[]> {
return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute()
}
}

View file

@ -1,48 +0,0 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface LeagueEntryDTO {
leagueId: string
queueType: string
tier: string
rank: string
summonerId: string
summonerName: string
leaguePoints: number
wins: number
losses: number
veteran: boolean
inactive: boolean
freshBlood: boolean
hotStreak: boolean
miniSeries?: MiniSeriesDTO
}
interface MiniSeriesDTO {
losses: number
progress: string
target: number
wins: number
}
export default class LeagueEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public summonerID(summonerID: string, region: string): Promise<LeagueEntryDTO[]> {
return new JaxRequest(
region,
this.config,
`league/v4/entries/by-summoner/${summonerID}`,
this.limiter,
300
).execute()
}
}

View file

@ -1,218 +0,0 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import { getRiotRegion } from 'App/helpers'
import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface MatchDto {
metadata: MetadataDto
info: InfoDto
}
export interface MetadataDto {
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
queueId: number
teams: TeamDto[]
tournamentCode?: string
}
export interface ParticipantDto {
assists: number
baronKills: number
bountyLevel: number
champExperience: number
champLevel: number
championId: number
championName: string
championTransform: ChampionTransformDto
consumablesPurchased: number
damageDealtToObjectives: number
damageDealtToTurrets: number
damageSelfMitigated: number
deaths: number
detectorWardsPlaced: number
doubleKills: number
dragonKills: number
firstBloodAssist: boolean
firstBloodKill: boolean
firstTowerAssist: boolean
firstTowerKill: boolean
gameEndedInEarlySurrender: boolean
gameEndedInSurrender: boolean
goldEarned: number
goldSpent: number
individualPosition: 'Invalid' | TeamPositionDto // TODO
inhibitorKills: number
item0: number
item1: number
item2: number
item3: number
item4: number
item5: number
item6: number
itemsPurchased: number
killingSprees: number
kills: number
lane: LaneDto // TODO
largestCriticalStrike: number
largestKillingSpree: number
largestMultiKill: number
longestTimeSpentLiving: number
magicDamageDealt: number
magicDamageDealtToChampions: number
magicDamageTaken: number
neutralMinionsKilled: number
nexusKills: number
objectivesStolen: number
objectivesStolenAssists: number
participantId: number
pentaKills: number
perks: PerksDto
physicalDamageDealt: number
physicalDamageDealtToChampions: number
physicalDamageTaken: number
profileIcon: number
puuid: string
quadraKills: number
riotIdName: string
riotIdTagline: string
role: RoleDto // TODO
sightWardsBoughtInGame: number
spell1Casts: number
spell2Casts: number
spell3Casts: number
spell4Casts: number
summoner1Casts: number
summoner1Id: number
summoner2Casts: number
summoner2Id: number
summonerId: string
summonerLevel: number
summonerName: string
teamEarlySurrendered: boolean
teamId: number
teamPosition: TeamPositionDto // TODO
timeCCingOthers: number
timePlayed: number
totalDamageDealt: number
totalDamageDealtToChampions: number
totalDamageShieldedOnTeammates: 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 enum ChampionTransformDto {
None,
Slayer,
Assasin,
}
export type LaneDto = 'TOP' | 'JUNGLE' | 'MIDDLE' | 'BOTTOM'
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 {
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: string, region: string): Promise<MatchDto> {
return new JaxRequest(
getRiotRegion(region),
this.config,
`match/v5/matches/${matchID}`,
this.limiter,
1500
).execute()
}
}

View file

@ -1,54 +0,0 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import { getRiotRegion } from 'App/helpers'
import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
// export interface MatchlistDto {
// startIndex: number,
// totalGames: number,
// endIndex: number,
// matches: MatchReferenceDto[]
// }
// export interface MatchReferenceDto {
// gameId: number,
// role: string,
// season: number,
// platformId: string,
// champion: number,
// queue: number,
// lane: string,
// timestamp: number,
// seasonMatch?: number
// }
/**
*
* ===============================================
* V5
* ===============================================
*
*/
export type MatchlistDto = string[]
export default class MatchlistEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public puuid(puuid: string, region: string, beginIndex = 0, count = 100): Promise<MatchlistDto> {
return new JaxRequest(
getRiotRegion(region),
this.config,
`match/v5/matches/by-puuid/${puuid}/ids?start=${beginIndex}&count=${count}`,
this.limiter,
0
).execute()
}
}

View file

@ -1,72 +0,0 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface CurrentGameInfoDTO {
gameId: number
gameType: string
gameStartTime: number
mapId: number
gameLength: number
platformId: string
gameMode: string
bannedChampions: BannedChampionDTO[]
gameQueueConfigId: number
observers: ObserverDTO
participants: CurrentGameParticipantDTO[]
}
export interface BannedChampionDTO {
pickTurn: number
championId: number
teamId: number
}
export interface ObserverDTO {
encryptionKey: string
}
export interface CurrentGameParticipantDTO {
championId: number
perks: PerksDTO
profileIconId: number
bot: boolean
teamId: number
summonerName: string
summonerId: string
spell1Id: number
spell2Id: number
gameCustomizationObjects: GameCustomizationObjectDTO[]
}
export interface PerksDTO {
perkIds: number[]
perkStyle: number
perkSubStyle: number
}
export interface GameCustomizationObjectDTO {
category: string
content: string
}
export default class SpectatorEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfoDTO | undefined> {
return new JaxRequest(
region,
this.config,
`spectator/v4/active-games/by-summoner/${summonerID}`,
this.limiter,
0
).execute()
}
}

View file

@ -1,54 +0,0 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface SummonerDTO {
accountId: string
profileIconId: number
revisionDate: number
name: string
id: string
puuid: string
summonerLevel: number
}
export default class SummonerEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
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(
region,
this.config,
`summoner/v4/summoners/${summonerId}`,
this.limiter,
36000
).execute()
}
public summonerName(summonerName: string, region: string): Promise<SummonerDTO> {
return new JaxRequest(
region,
this.config,
`summoner/v4/summoners/by-name/${encodeURI(summonerName)}`,
this.limiter,
36000
).execute()
}
}

View file

@ -1,48 +0,0 @@
import LeagueEndpoint from './Endpoints/LeagueEndpoint'
import MatchEndpoint from './Endpoints/MatchEndpoint'
import MatchlistEndpoint from './Endpoints/MatchlistEndpoint'
import SummonerEndpoint from './Endpoints/SummonerEndpoint'
import SpectatorEndpoint from './Endpoints/SpectatorEndpoint'
import CDragonEndpoint from './Endpoints/CDragonEndpoint'
import { JaxConfig } from '../JaxConfig'
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { STRATEGY } from 'riot-ratelimiter/dist/RateLimiter'
import MatchV4Endpoint from './Endpoints/MatchV4Endpoint'
import MatchlistV4Endpoint from './Endpoints/MatchlistV4Endpoint'
export default class Jax {
public key: string
public limiter: RiotRateLimiter
public config: JaxConfig
public League: LeagueEndpoint
public Match: MatchEndpoint
public MatchV4: MatchV4Endpoint
public Matchlist: MatchlistEndpoint
public MatchlistV4: MatchlistV4Endpoint
public Spectator: SpectatorEndpoint
public Summoner: SummonerEndpoint
public CDragon: CDragonEndpoint
constructor(config: JaxConfig) {
this.key = config.key
// this.limiter = new RiotRateLimiter({
// debug: true,
// retryCount: 0,
// })
this.limiter = new RiotRateLimiter({
debug: false,
strategy: STRATEGY.SPREAD,
})
this.config = config
this.League = new LeagueEndpoint(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.MatchlistV4 = new MatchlistV4Endpoint(this.config, this.limiter)
this.Spectator = new SpectatorEndpoint(this.config, this.limiter)
this.Summoner = new SummonerEndpoint(this.config, this.limiter)
this.CDragon = new CDragonEndpoint(this.config)
}
}

View file

@ -1,93 +0,0 @@
import { promisify } from 'util'
import { JaxConfig } from '../JaxConfig'
import Logger from '@ioc:Adonis/Core/Logger'
import Redis from '@ioc:Adonis/Addons/Redis'
import RiotRateLimiter from 'riot-ratelimiter'
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
export default class JaxRequest {
private region: string
private config: JaxConfig
private endpoint: string
private limiter: RiotRateLimiter
private cacheTime: number
private retries: number
private sleep: { (ms: number): Promise<void>; <T>(ms: number, value: T): Promise<T> }
constructor(
region: string,
config: JaxConfig,
endpoint: string,
limiter: RiotRateLimiter,
cacheTime: number
) {
this.region = region
this.config = config
this.endpoint = endpoint
this.limiter = limiter
this.cacheTime = cacheTime
this.retries = config.requestOptions.retriesBeforeAbort
this.sleep = promisify(setTimeout)
}
public async execute() {
const url = `https://${this.region}.api.riotgames.com/lol/${this.endpoint}`
// Redis cache
if (this.cacheTime > 0) {
const requestCached = await Redis.get(url)
if (requestCached) {
return JSON.parse(requestCached)
}
}
try {
const resp: any = await this.limiter.executing({
url,
token: this.config.key,
resolveWithFullResponse: false,
})
if (this.cacheTime > 0) {
await Redis.setex(url, this.cacheTime, resp)
}
return JSON.parse(resp)
} catch ({ statusCode, ...rest }) {
this.retries--
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
// Or if summoner has no MatchList
if (
!this.endpoint.includes('spectator/v4/active-games/by-summoner') &&
!this.endpoint.includes('summoner/v4/summoners/by-name') &&
!this.endpoint.includes('match/v4/matchlists/by-account')
) {
Logger.error(`URL ${url}: `)
// Logger.error(`JaxRequest Error ${statusCode}: `, rest)
}
return
}
console.log('====================================')
console.log(statusCode)
console.log('====================================')
if (this.retries > 0) {
await this.sleep(this.config.requestOptions.delayBeforeRetry)
return this.execute()
}
}
}
}

View file

@ -1,139 +0,0 @@
import Jax from './Jax'
import { MatchlistDto } from './Jax/src/Endpoints/MatchlistEndpoint'
import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint'
import Summoner from 'App/Models/Summoner'
import Database from '@ioc:Adonis/Lucid/Database'
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 {
/**
* Add 100 matches at a time to MatchList until the stopFetching condition is true
* @param account of the summoner
* @param region of the summoner
* @param stopFetching condition to stop fetching the MatchList
*/
private async _fetchMatchListUntil(account: SummonerDTO, region: string, stopFetching: any) {
let matchList: MatchlistDto = []
let alreadyIn = false
let index = 0
do {
const newMatchList = await Jax.Matchlist.puuid(account.puuid, region, index)
// Error while fetching Riot API
if (!newMatchList) {
return matchList
}
matchList = [...matchList, ...newMatchList]
alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList)
// If the match is made in another region : we stop fetching
if (matchList[matchList.length - 1].split('_')[0].toLowerCase() !== region.toLowerCase()) {
alreadyIn = true
}
index += 100
} while (!alreadyIn)
return matchList
}
/**
* Update the full MatchList of the summoner
*/
public async updateMatchList(
account: SummonerDTO,
region: string,
summonerDB: Summoner
): Promise<MatchlistDto> {
console.time('matchList')
const currentMatchList = await summonerDB.related('matchList').query().orderBy('matchId', 'asc')
const currentMatchListIds = currentMatchList.map((m) => m.matchId)
const newMatchList = await this._fetchMatchListUntil(
account,
region,
(newMatchList: MatchlistDto) => {
return currentMatchListIds.some((id) => id === newMatchList[newMatchList.length - 1])
}
)
const matchListToSave: MatchlistDto = []
for (const matchId of newMatchList.reverse()) {
if (!currentMatchListIds.some((id) => id === matchId)) {
matchListToSave.push(matchId)
currentMatchListIds.push(matchId)
}
}
// If there is new matchIds to save in database
if (matchListToSave.length) {
await Database.table('summoner_matchlist').multiInsert(
matchListToSave.map((id) => ({
match_id: id,
summoner_puuid: summonerDB.puuid,
}))
)
}
console.timeEnd('matchList')
return currentMatchListIds.reverse()
}
/**
* Fetch list of matches for a specific Summoner
*/
public async getMatches(
region: string,
matchIds: string[],
puuid: string
): Promise<SerializedMatch[]> {
console.time('getMatches')
const matches: SerializedMatch[] = []
const matchesToGetFromRiot: MatchlistDto = []
for (let i = 0; i < matchIds.length; ++i) {
const matchSaved = await Match.query()
.where('id', matchIds[i])
.preload('teams')
.preload('players')
.first()
if (matchSaved) {
// TODO: Serialize match from DB + put it in Redis + push it in "matches"
matches.push(BasicMatchSerializer.serializeOneMatch(matchSaved, puuid))
} else {
matchesToGetFromRiot.push(matchIds[i])
}
}
const requests = matchesToGetFromRiot.map((gameId) => Jax.Match.get(gameId, region))
const matchesFromApi = await Promise.all(requests)
/* If we have to store some matches in the db */
if (matchesFromApi.length !== 0) {
// Remove bugged matches from the Riot API + tutorial games
const filteredMatches = matchesFromApi
.filter(notEmpty)
.filter(
(m) =>
!tutorialQueues.includes(m.info.queueId) &&
m.info.teams.length > 0 &&
m.info.participants.length > 0
)
// Transform raw matches data
const parsedMatches: any = await MatchParser.parse(filteredMatches)
// TODO: Serialize match from DB + put it in Redis + push it in "matches"
const serializedMatches = BasicMatchSerializer.serialize(parsedMatches, puuid, true)
matches.push(...serializedMatches)
}
// Todo: check if we need to sort here
matches.sort((a, b) => (a.date < b.date ? 1 : -1))
console.timeEnd('getMatches')
return matches
}
}
export default new MatchService()

View file

@ -1,67 +0,0 @@
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 {
public async getRecentActivity(puuid: string) {
return MatchRepository.recentActivity(puuid)
}
public async getSummonerStats(puuid: string, season?: number) {
console.time('GLOBAL')
const globalStats = await MatchRepository.globalStats(puuid)
console.timeEnd('GLOBAL')
console.time('GAMEMODE')
const gamemodeStats = await MatchRepository.gamemodeStats(puuid)
console.timeEnd('GAMEMODE')
console.time('ROLE')
const roleStats = await MatchRepository.roleStats(puuid)
// Check if all roles are in the array
const roles = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
for (const role of roles) {
const findedRole = roleStats.find((r) => TeamPosition[r.role] === role)
if (findedRole) {
findedRole.role = TeamPosition[findedRole.role]
} else {
roleStats.push({
count: 0,
losses: 0,
role,
wins: 0,
})
}
}
console.timeEnd('ROLE')
console.time('CHAMPION')
const championStats = await MatchRepository.championStats(puuid, 5)
for (const champ of championStats) {
champ.champion = BasicMatchSerializer.getChampion(champ.id)
}
console.timeEnd('CHAMPION')
console.time('CHAMPION-CLASS')
const championClassStats = await MatchRepository.championClassStats(puuid)
for (const champ of championClassStats) {
champ.id = ChampionRoles[champ.id]
}
console.timeEnd('CHAMPION-CLASS')
console.time('MATES')
const mates = await MatchRepository.mates(puuid)
console.timeEnd('MATES')
console.time('RECENT_ACTIVITY')
const recentActivity = await MatchRepository.recentActivity(puuid)
console.timeEnd('RECENT_ACTIVITY')
return {
global: globalStats,
league: gamemodeStats,
role: roleStats.sort(sortTeamByRole),
champion: championStats,
class: championClassStats,
mates,
recentActivity,
}
}
}
export default new StatsService()

View file

@ -1,97 +0,0 @@
import Jax from './Jax'
import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint'
import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint'
import Summoner from 'App/Models/Summoner'
import { PlayerRankParsed } from 'App/Parsers/ParsedType'
import MatchPlayerRank from 'App/Models/MatchPlayerRank'
export interface LeagueEntriesByQueue {
soloQ?: LeagueEntryByQueue
flex5v5?: LeagueEntryByQueue
}
export interface LeagueEntryByQueue extends LeagueEntryDTO {
fullRank: string
winrate: string
shortName: string | number
}
class SummonerService {
private readonly uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
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
* @param league raw data of the league from Riot API
*/
private getleagueData(league?: LeagueEntryDTO): LeagueEntryByQueue | null {
if (!league) {
return null
}
const fullRank = this.uniqueLeagues.includes(league.tier)
? league.tier
: `${league.tier} ${league.rank}`
const winrate = this.getWinrate(league.wins, league.losses)
const shortName = this.uniqueLeagues.includes(league.tier)
? league.leaguePoints
: league.tier[0] + this.leaguesNumbers[league.rank]
return {
...league,
fullRank,
winrate,
shortName,
}
}
/**
* Get account infos for a searched summoner name
* @param summonerName
* @param region
*/
public async getAccount(summonerName: string, region: string) {
const name = summonerName.toLowerCase()
const account = await Jax.Summoner.summonerName(name, region)
return account
}
/**
* Return the full list of old and actual summoner names
* @param account of the summoner
* @param summonerDB summoner in the database
*/
public async getAllSummonerNames(account: SummonerDTO, summonerDB: Summoner) {
await summonerDB.related('names').firstOrCreate({
name: account.name,
})
return summonerDB.related('names').query().select('name', 'created_at')
}
/**
* Get ranked data for a specific Summoner
* @param account
* @param region
*/
public async getRanked(summonerId: string, region: string): Promise<LeagueEntriesByQueue> {
const ranked = await Jax.League.summonerID(summonerId, region)
const result: LeagueEntriesByQueue = {}
if (ranked && ranked.length) {
result.soloQ =
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
}
}
export default new SummonerService()

View file

@ -1,42 +0,0 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class DetailedMatchValidator {
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({
matchId: schema.string(),
})
/**
* 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 = {}
}

View file

@ -1,45 +0,0 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class MatchesIndexValidator {
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(),
region: schema.string(),
matchIds: schema.array().members(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 = {}
}

View file

@ -1,43 +0,0 @@
import { rules, schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class SummonerBasicValidator {
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({
summoner: schema.string({}, [rules.regex(/^[0-9\p{L} _\.]+$/u)]),
region: schema.string(),
})
/**
* 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 = {}
}

View file

@ -1,44 +0,0 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class SummonerChampionValidator {
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(),
queue: schema.number.optional(),
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 = {}
}

View file

@ -1,43 +0,0 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class SummonerLiveValidator {
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({
id: schema.string(),
region: schema.string(),
})
/**
* 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 = {}
}

View file

@ -1,44 +0,0 @@
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class SummonerOverviewValidator {
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(),
region: 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 = {}
}

View file

@ -1,43 +0,0 @@
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 = {}
}

View file

@ -1,133 +0,0 @@
/**
* 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
*/
export const queuesWithRole = [
0, // Custom
400, // Draft
420, // Solo/Duo
430, // Blind,
440, // Flex
700, // Clash
]
/**
* League of Legends tutorial queues
*/
export const tutorialQueues = [2000, 2010, 2020]
/**
* League of Legends seasons timestamps
*/
export const seasons = {
0: 9,
1578628800000: 10,
1604970061000: 10.5, // Preseason 11
1610078400000: 11,
}
/**
* League of Legends all support item ids
*/
export const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864]
/**
* Get season number for a match
* @param timestamp
*/
export function getSeasonNumber(timestamp: number): number {
const arrSeasons = Object.keys(seasons).map((k) => Number(k))
arrSeasons.push(timestamp)
arrSeasons.sort()
const indexSeason = arrSeasons.indexOf(timestamp) - 1
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
* @param a first player
* @param b second player
*/
export function sortTeamByRole<T extends SortableByRole>(a: T, b: T) {
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']
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
}

View file

@ -1,19 +0,0 @@
import { listDirectoryFiles } from '@adonisjs/core/build/standalone'
import Application from '@ioc:Adonis/Core/Application'
/*
|--------------------------------------------------------------------------
| Exporting an array of commands
|--------------------------------------------------------------------------
|
| Instead of manually exporting each file from this directory, we use the
| helper `listDirectoryFiles` to recursively collect and export an array
| of filenames.
|
| Couple of things to note:
|
| 1. The file path must be relative from the project root and not this directory.
| 2. We must ignore this file to avoid getting into an infinite loop
|
*/
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index'])

View file

@ -1,234 +0,0 @@
/**
* Config source: https://git.io/JfefZ
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import proxyAddr from 'proxy-addr'
import Env from '@ioc:Adonis/Core/Env'
import { ServerConfig } from '@ioc:Adonis/Core/Server'
import { LoggerConfig } from '@ioc:Adonis/Core/Logger'
import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler'
import { ValidatorConfig } from '@ioc:Adonis/Core/Validator'
/*
|--------------------------------------------------------------------------
| Application secret key
|--------------------------------------------------------------------------
|
| The secret to encrypt and sign different values in your application.
| Make sure to keep the `APP_KEY` as an environment variable and secure.
|
| Note: Changing the application key for an existing app will make all
| the cookies invalid and also the existing encrypted data will not
| be decrypted.
|
*/
export const appKey: string = Env.get('APP_KEY')
/*
|--------------------------------------------------------------------------
| Http server configuration
|--------------------------------------------------------------------------
|
| The configuration for the HTTP(s) server. Make sure to go through all
| the config properties to make keep server secure.
|
*/
export const http: ServerConfig = {
/*
|--------------------------------------------------------------------------
| Allow method spoofing
|--------------------------------------------------------------------------
|
| Method spoofing enables defining custom HTTP methods using a query string
| `_method`. This is usually required when you are making traditional
| form requests and wants to use HTTP verbs like `PUT`, `DELETE` and
| so on.
|
*/
allowMethodSpoofing: false,
/*
|--------------------------------------------------------------------------
| Subdomain offset
|--------------------------------------------------------------------------
*/
subdomainOffset: 2,
/*
|--------------------------------------------------------------------------
| Request Ids
|--------------------------------------------------------------------------
|
| Setting this value to `true` will generate a unique request id for each
| HTTP request and set it as `x-request-id` header.
|
*/
generateRequestId: false,
/*
|--------------------------------------------------------------------------
| Trusting proxy servers
|--------------------------------------------------------------------------
|
| Define the proxy servers that AdonisJs must trust for reading `X-Forwarded`
| headers.
|
*/
trustProxy: proxyAddr.compile('loopback'),
/*
|--------------------------------------------------------------------------
| Generating Etag
|--------------------------------------------------------------------------
|
| Whether or not to generate an etag for every response.
|
*/
etag: false,
/*
|--------------------------------------------------------------------------
| JSONP Callback
|--------------------------------------------------------------------------
*/
jsonpCallbackName: 'callback',
/*
|--------------------------------------------------------------------------
| Cookie settings
|--------------------------------------------------------------------------
*/
cookie: {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true,
secure: false,
sameSite: false,
},
/*
|--------------------------------------------------------------------------
| Force Content Negotiation
|--------------------------------------------------------------------------
|
| The internals of the framework relies on the content negotiation to
| detect the best possible response type for a given HTTP request.
|
| 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.
|
| By setting `forceContentNegotiationTo = 'application/json'`, you negotiate
| with the server in advance to always return JSON without relying on the
| client to set the header explicitly.
|
*/
forceContentNegotiationTo: 'application/json',
}
/*
|--------------------------------------------------------------------------
| Logger
|--------------------------------------------------------------------------
*/
export const logger: LoggerConfig = {
/*
|--------------------------------------------------------------------------
| Application name
|--------------------------------------------------------------------------
|
| The name of the application you want to add to the log. It is recommended
| to always have app name in every log line.
|
| The `APP_NAME` environment variable is automatically set by AdonisJS by
| reading the `name` property from the `package.json` file.
|
*/
name: Env.get('APP_NAME'),
/*
|--------------------------------------------------------------------------
| Toggle logger
|--------------------------------------------------------------------------
|
| Enable or disable logger application wide
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Logging level
|--------------------------------------------------------------------------
|
| The level from which you want the logger to flush logs. It is recommended
| to make use of the environment variable, so that you can define log levels
| at deployment level and not code level.
|
*/
level: Env.get('LOG_LEVEL', 'info'),
/*
|--------------------------------------------------------------------------
| Pretty print
|--------------------------------------------------------------------------
|
| It is highly advised NOT to use `prettyPrint` in production, since it
| can have huge impact on performance.
|
*/
prettyPrint: Env.get('NODE_ENV') === 'development',
}
/*
|--------------------------------------------------------------------------
| Profiler
|--------------------------------------------------------------------------
*/
export const profiler: ProfilerConfig = {
/*
|--------------------------------------------------------------------------
| Toggle profiler
|--------------------------------------------------------------------------
|
| Enable or disable profiler
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Blacklist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to disable from
| getting profiled.
|
*/
blacklist: [],
/*
|--------------------------------------------------------------------------
| Whitelist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to whitelist for
| the profiler. When whitelist is defined, then `blacklist` is ignored.
|
*/
whitelist: [],
}
/*
|--------------------------------------------------------------------------
| Validator
|--------------------------------------------------------------------------
|
| Configure the global configuration for the validator. Here's the reference
| to the default config https://git.io/JT0WE
|
*/
export const validator: ValidatorConfig = {}

View file

@ -1,205 +0,0 @@
/**
* Config source: https://git.io/Jfefn
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser'
const bodyParserConfig: BodyParserConfig = {
/*
|--------------------------------------------------------------------------
| White listed methods
|--------------------------------------------------------------------------
|
| HTTP methods for which body parsing must be performed. It is a good practice
| to avoid body parsing for `GET` requests.
|
*/
whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
/*
|--------------------------------------------------------------------------
| JSON parser settings
|--------------------------------------------------------------------------
|
| The settings for the JSON parser. The types defines the request content
| types which gets processed by the JSON parser.
|
*/
json: {
encoding: 'utf-8',
limit: '1mb',
strict: true,
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report',
],
},
/*
|--------------------------------------------------------------------------
| Form parser settings
|--------------------------------------------------------------------------
|
| The settings for the `application/x-www-form-urlencoded` parser. The types
| defines the request content types which gets processed by the form parser.
|
*/
form: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
/*
|--------------------------------------------------------------------------
| Convert empty strings to null
|--------------------------------------------------------------------------
|
| Convert empty form fields to null. HTML forms results in field string
| value when the field is left blank. This option normalizes all the blank
| field values to "null"
|
*/
convertEmptyStringsToNull: true,
types: ['application/x-www-form-urlencoded'],
},
/*
|--------------------------------------------------------------------------
| Raw body parser settings
|--------------------------------------------------------------------------
|
| Raw body just reads the request body stream as a plain text, which you
| can process by hand. This must be used when request body type is not
| supported by the body parser.
|
*/
raw: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
types: ['text/*'],
},
/*
|--------------------------------------------------------------------------
| Multipart parser settings
|--------------------------------------------------------------------------
|
| The settings for the `multipart/form-data` parser. The types defines the
| request content types which gets processed by the form parser.
|
*/
multipart: {
/*
|--------------------------------------------------------------------------
| Auto process
|--------------------------------------------------------------------------
|
| The auto process option will process uploaded files and writes them to
| the `tmp` folder. You can turn it off and then manually use the stream
| to pipe stream to a different destination.
|
| It is recommended to keep `autoProcess=true`. Unless you are processing bigger
| file sizes.
|
*/
autoProcess: true,
/*
|--------------------------------------------------------------------------
| Files to be processed manually
|--------------------------------------------------------------------------
|
| You can turn off `autoProcess` for certain routes by defining
| routes inside the following array.
|
| NOTE: Make sure the route pattern starts with a leading slash.
|
| Correct
| ```js
| /projects/:id/file
| ```
|
| Incorrect
| ```js
| projects/:id/file
| ```
*/
processManually: [],
/*
|--------------------------------------------------------------------------
| Temporary file name
|--------------------------------------------------------------------------
|
| When auto processing is on. We will use this method to compute the temporary
| file name. AdonisJs will compute a unique `tmpPath` for you automatically,
| However, you can also define your own custom method.
|
*/
// tmpFileName () {
// },
/*
|--------------------------------------------------------------------------
| Encoding
|--------------------------------------------------------------------------
|
| Request body encoding
|
*/
encoding: 'utf-8',
/*
|--------------------------------------------------------------------------
| Convert empty strings to null
|--------------------------------------------------------------------------
|
| Convert empty form fields to null. HTML forms results in field string
| value when the field is left blank. This option normalizes all the blank
| field values to "null"
|
*/
convertEmptyStringsToNull: true,
/*
|--------------------------------------------------------------------------
| Max Fields
|--------------------------------------------------------------------------
|
| The maximum number of fields allowed in the request body. The field includes
| text inputs and files both.
|
*/
maxFields: 1000,
/*
|--------------------------------------------------------------------------
| Request body limit
|--------------------------------------------------------------------------
|
| The total limit to the multipart body. This includes all request files
| and fields data.
|
*/
limit: '20mb',
/*
|--------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------
|
| The types that will be considered and parsed as multipart body.
|
*/
types: ['multipart/form-data'],
},
}
export default bodyParserConfig

View file

@ -1,144 +0,0 @@
/**
* Config source: https://git.io/JfefC
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { CorsConfig } from '@ioc:Adonis/Core/Cors'
const corsConfig: CorsConfig = {
/*
|--------------------------------------------------------------------------
| Enabled
|--------------------------------------------------------------------------
|
| A boolean to enable or disable CORS integration from your AdonisJs
| application.
|
| Setting the value to `true` will enable the CORS for all HTTP request. However,
| you can define a function to enable/disable it on per request basis as well.
|
*/
enabled: true,
// You can also use a function that return true or false.
// enabled: (request) => request.url().startsWith('/api')
/*
|--------------------------------------------------------------------------
| Origin
|--------------------------------------------------------------------------
|
| Set a list of origins to be allowed for `Access-Control-Allow-Origin`.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
| Boolean (true) - Allow current request origin.
| Boolean (false) - Disallow all.
| String - Comma separated list of allowed origins.
| Array - An array of allowed origins.
| String (*) - A wildcard (*) to allow all request origins.
| Function - Receives the current origin string and should return
| one of the above values.
|
*/
origin: (origin) => {
if (process.env.NODE_ENV === 'development') {
return true
}
if (origin.includes('leaguestats.gg')) {
return true
}
return false
},
/*
|--------------------------------------------------------------------------
| Methods
|--------------------------------------------------------------------------
|
| An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method`
| is checked against the following list.
|
| Following is the list of default methods. Feel free to add more.
*/
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
/*
|--------------------------------------------------------------------------
| Headers
|--------------------------------------------------------------------------
|
| List of headers to be allowed for `Access-Control-Allow-Headers` header.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
|
| Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`.
| Boolean(false) - Disallow all headers.
| String - Comma separated list of allowed headers.
| Array - An array of allowed headers.
| Function - Receives the current header and should return one of the above values.
|
*/
headers: true,
/*
|--------------------------------------------------------------------------
| Expose Headers
|--------------------------------------------------------------------------
|
| A list of headers to be exposed by setting `Access-Control-Expose-Headers`.
| header. By default following 6 simple response headers are exposed.
|
| Cache-Control
| Content-Language
| Content-Type
| Expires
| Last-Modified
| Pragma
|
| In order to add more headers, simply define them inside the following array.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
*/
exposeHeaders: [
'cache-control',
'content-language',
'content-type',
'expires',
'last-modified',
'pragma',
],
/*
|--------------------------------------------------------------------------
| Credentials
|--------------------------------------------------------------------------
|
| Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`,
| then header will be set, otherwise not.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
*/
credentials: true,
/*
|--------------------------------------------------------------------------
| MaxAge
|--------------------------------------------------------------------------
|
| Define `Access-Control-Max-Age` header in seconds.
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
*/
maxAge: 90,
}
export default corsConfig

View file

@ -1,75 +0,0 @@
/**
* Config source: https://git.io/JfefW
*
* 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 { HashConfig } from '@ioc:Adonis/Core/Hash'
/*
|--------------------------------------------------------------------------
| Hash Config
|--------------------------------------------------------------------------
|
| The `HashConfig` relies on the `HashList` interface which is
| defined inside `contracts` directory.
|
*/
const hashConfig: HashConfig = {
/*
|--------------------------------------------------------------------------
| Default hasher
|--------------------------------------------------------------------------
|
| By default we make use of the bcrypt hasher to hash values. However, feel
| free to change the default value
|
*/
default: Env.get('HASH_DRIVER', 'argon'),
list: {
/*
|--------------------------------------------------------------------------
| Argon
|--------------------------------------------------------------------------
|
| Argon mapping uses the `argon2` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-argon2.
|
| npm install phc-argon2
|
*/
argon: {
driver: 'argon2',
variant: 'id',
iterations: 3,
memory: 4096,
parallelism: 1,
saltSize: 16,
},
/*
|--------------------------------------------------------------------------
| Bcrypt
|--------------------------------------------------------------------------
|
| Bcrypt mapping uses the `bcrypt` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-bcrypt.
|
| npm install phc-bcrypt
|
*/
bcrypt: {
driver: 'bcrypt',
rounds: 10,
},
},
}
export default hashConfig

View file

@ -1,49 +0,0 @@
/**
* Config source: https://git.io/JemcF
*
* 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 { RedisConfig } from '@ioc:Adonis/Addons/Redis'
/*
|--------------------------------------------------------------------------
| Redis configuration
|--------------------------------------------------------------------------
|
| Following is the configuration used by the Redis provider to connect to
| the redis server and execute redis commands.
|
| Do make sure to pre-define the connections type inside `contracts/redis.ts`
| file for AdonisJs to recognize connections.
|
| Make sure to check `contracts/redis.ts` file for defining extra connections
*/
const redisConfig: RedisConfig = {
connection: Env.get('REDIS_CONNECTION'),
connections: {
/*
|--------------------------------------------------------------------------
| The default connection
|--------------------------------------------------------------------------
|
| The main connection you want to use to execute redis commands. The same
| connection will be used by the session provider, if you rely on the
| redis driver.
|
*/
local: {
host: Env.get('REDIS_HOST'),
port: Env.get('REDIS_PORT'),
password: Env.get('REDIS_PASSWORD', ''),
db: 0,
keyPrefix: '',
healthCheck: true,
},
},
}
export default redisConfig

View file

@ -1,23 +0,0 @@
/**
* Contract source: https://git.io/JTm6U
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Env' {
/*
|--------------------------------------------------------------------------
| Getting types for validated environment variables
|--------------------------------------------------------------------------
|
| The `default` export from the "../env.ts" file exports types for the
| validated environment variables. Here we merge them with the `EnvTypes`
| interface so that you can enjoy intellisense when using the "Env"
| module.
|
*/
type CustomTypes = typeof import('../env').default
interface EnvTypes extends CustomTypes {}
}

View file

@ -1,29 +0,0 @@
/**
* Contract source: https://git.io/JfefG
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Event' {
/*
|--------------------------------------------------------------------------
| Define typed events
|--------------------------------------------------------------------------
|
| You can define types for events inside the following interface and
| AdonisJS will make sure that all listeners and emit calls adheres
| to the defined types.
|
| For example:
|
| interface EventsList {
| 'new:user': UserModel
| }
|
| Now calling `Event.emit('new:user')` will statically ensure that passed value is
| an instance of the the UserModel only.
|
*/
interface EventsList {}
}

View file

@ -1,19 +0,0 @@
/**
* Contract source: https://git.io/Jfefs
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Hash' {
interface HashersList {
bcrypt: {
config: BcryptConfig
implementation: BcryptContract
}
argon: {
config: ArgonConfig
implementation: ArgonContract
}
}
}

View file

@ -1,12 +0,0 @@
/**
* Contract source: https://git.io/JemcN
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
declare module '@ioc:Adonis/Addons/Redis' {
interface RedisConnectionsList {
local: RedisConnectionConfig
}
}

View file

@ -1,38 +0,0 @@
/*
|--------------------------------------------------------------------------
| Validating Environment Variables
|--------------------------------------------------------------------------
|
| In this file we define the rules for validating environment variables.
| By performing validation we ensure that your application is running in
| a stable environment with correct configuration values.
|
| This file is read automatically by the framework during the boot lifecycle
| and hence do not rename or move this file to a different location.
|
*/
import Env from '@ioc:Adonis/Core/Env'
export default Env.rules({
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
DRIVE_DISK: Env.schema.enum(['local'] as const),
NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const),
DB_CONNECTION: Env.schema.string(),
PG_HOST: Env.schema.string({ format: 'host' }),
PG_PORT: Env.schema.number(),
PG_USER: Env.schema.string(),
PG_PASSWORD: Env.schema.string.optional(),
PG_DB_NAME: Env.schema.string(),
REDIS_CONNECTION: Env.schema.enum(['local'] as const),
REDIS_HOST: Env.schema.string({ format: 'host' }),
REDIS_PORT: Env.schema.number(),
REDIS_PASSWORD: Env.schema.string.optional(),
RIOT_API_KEY: Env.schema.string(),
})

14567
server-v2/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,40 +0,0 @@
{
"name": "leaguestats",
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "node ace serve --watch",
"build": "node ace build --production",
"start": "node server.js",
"lint": "eslint . --ext=.ts",
"format": "prettier --write ."
},
"devDependencies": {
"@adonisjs/assembler": "^5.3.7",
"@types/pg": "^8.6.1",
"adonis-preset-ts": "^2.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-adonis": "^1.3.3",
"eslint-plugin-prettier": "^4.0.0",
"pino-pretty": "^7.0.0",
"prettier": "^2.4.0",
"typescript": "~4.2",
"youch": "^2.2.2",
"youch-terminal": "^1.1.1"
},
"dependencies": {
"@adonisjs/core": "^5.3.4",
"@adonisjs/lucid": "^16.0.2",
"@adonisjs/redis": "^7.0.9",
"@adonisjs/repl": "^3.1.6",
"got": "^11.8.2",
"luxon": "^2.0.2",
"pg": "^8.7.1",
"proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"riot-ratelimiter": "^0.1.5",
"source-map-support": "^0.5.20"
}
}

View file

@ -1,25 +0,0 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
constructor(protected app: ApplicationContract) {}
public register() {
// Register your own bindings
}
public async boot() {
// IoC container is ready
// Load CDragon Service
const CDragon = await import('App/Services/CDragonService')
await CDragon.default.getContext()
}
public async ready() {
// App is ready
}
public async shutdown() {
// Cleanup, since app is going down
}
}

View file

@ -1,19 +0,0 @@
/*
|--------------------------------------------------------------------------
| AdonisJs Server
|--------------------------------------------------------------------------
|
| The contents in this file is meant to bootstrap the AdonisJs application
| and start the HTTP server to accept incoming connections. You must avoid
| making this file dirty and instead make use of `lifecycle hooks` provided
| by AdonisJs service providers for custom code.
|
*/
import 'reflect-metadata'
import sourceMapSupport from 'source-map-support'
import { Ignitor } from '@adonisjs/core/build/standalone'
sourceMapSupport.install({ handleUncaughtExceptions: false })
new Ignitor(__dirname).httpServer().start()

View file

@ -1,41 +0,0 @@
/*
|--------------------------------------------------------------------------
| Application middleware
|--------------------------------------------------------------------------
|
| This file is used to define middleware for HTTP requests. You can register
| middleware as a `closure` or an IoC container binding. The bindings are
| preferred, since they keep this file clean.
|
*/
import Server from '@ioc:Adonis/Core/Server'
/*
|--------------------------------------------------------------------------
| Global middleware
|--------------------------------------------------------------------------
|
| An array of global middleware, that will be executed in the order they
| are defined for every HTTP requests.
|
*/
Server.middleware.register([() => import('@ioc:Adonis/Core/BodyParser')])
/*
|--------------------------------------------------------------------------
| Named middleware
|--------------------------------------------------------------------------
|
| Named middleware are defined as key-value pair. The value is the namespace
| or middleware function and key is the alias. Later you can use these
| alias on individual routes. For example:
|
| { auth: () => import('App/Middleware/Auth') }
|
| and then use it as follows
|
| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
|
*/
Server.middleware.registerNamed({})

View file

@ -1,42 +0,0 @@
/*
|--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
| This file is dedicated for defining HTTP routes. A single file is enough
| for majority of projects, however you can define routes in different
| files and just make sure to import them inside this file. For example
|
| Define routes in following two files
| start/routes/cart.ts
| start/routes/customer.ts
|
| and then import them inside `start/routes.ts` as follows
|
| import './routes/cart'
| import './routes/customer'
|
*/
import HealthCheck from '@ioc:Adonis/Core/HealthCheck'
import Route from '@ioc:Adonis/Core/Route'
Route.get('/', async () => ({
hi: 'Hello World from LeagueStats V2 API',
uptime: process.uptime(),
}))
Route.get('/health', async () => ({ report: await HealthCheck.getReport() }))
Route.post('/summoner/basic', 'SummonersController.basic')
Route.post('/summoner/overview', 'SummonersController.overview')
Route.post('/summoner/champions', 'SummonersController.champions')
Route.post('/summoner/records', 'SummonersController.records')
Route.post('/summoner/live', 'SummonersController.liveMatchDetails')
Route.post('/match', 'MatchesController.index')
Route.post('/match/details', 'MatchesController.show')
Route.post('/match/details/ranks', 'MatchesController.showRanks')
Route.get('/cdragon/runes', 'CDragonController.runes')

View file

@ -1,35 +0,0 @@
{
"extends": "./node_modules/adonis-preset-ts/tsconfig",
"include": [
"**/*"
],
"exclude": [
"node_modules",
"build"
],
"compilerOptions": {
"outDir": "build",
"rootDir": "./",
"sourceMap": true,
"paths": {
"App/*": [
"./app/*"
],
"Config/*": [
"./config/*"
],
"Contracts/*": [
"./contracts/*"
],
"Database/*": [
"./database/*"
]
},
"types": [
"@adonisjs/core",
"@adonisjs/repl",
"@adonisjs/lucid",
"@adonisjs/redis"
]
}
}

View file

@ -2,28 +2,36 @@
"typescript": true,
"commands": [
"./commands",
"@adonisjs/core/build/commands",
"@zakodium/adonis-mongodb/lib/commands"
"@adonisjs/core/build/commands/index.js",
"@adonisjs/repl/build/commands",
"@adonisjs/lucid/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Contracts": "contracts",
"Config": "config",
"Database": "database"
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel"
"./start/kernel",
{
"file": "./start/events",
"environment": [
"console",
"repl",
"web"
]
}
],
"providers": [
"./providers/AppProvider",
"@adonisjs/core",
"@zakodium/adonis-mongodb",
"@adonisjs/lucid",
"@adonisjs/redis"
],
"metaFiles": [
".env",
".adonisrc.json"
"aceProviders": [
"@adonisjs/repl"
]
}

View file

@ -2,13 +2,18 @@ PORT=3333
HOST=0.0.0.0
NODE_ENV=development
APP_KEY=
DRIVE_DISK=local
MONGODB_URL=mongodb://localhost:27017
MONGODB_DATABASE=leaguestats
DB_CONNECTION=pg
PG_HOST=localhost
PG_PORT=5432
PG_USER=
PG_PASSWORD=
PG_DB_NAME=
REDIS_CONNECTION=local
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
RIOT_API_KEY=

View file

@ -1,13 +1,7 @@
{
"extends": [
"plugin:adonis/typescriptApp"
],
"extends": ["plugin:adonis/typescriptApp", "prettier"],
"plugins": ["prettier"],
"rules": {
"max-len": [
"error",
{
"code": 130
}
]
"prettier/prettier": ["error"]
}
}
}

1
server/.gitignore vendored
View file

@ -5,4 +5,3 @@ coverage
.DS_STORE
.env
tmp
*.rdb

File diff suppressed because one or more lines are too long

View file

@ -3,17 +3,14 @@
| Ace Commands
|--------------------------------------------------------------------------
|
| This file is the entry point for running ace commands. For typescript
| projects, the ace commands will fallback to the compiled code and
| hence this file has to be executable by node directly.
| This file is the entry point for running ace commands.
|
*/
require('reflect-metadata')
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)
.ace()
.handle(process.argv.slice(2))
.catch(console.error)

View file

@ -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": {}
}

View file

@ -1,10 +1,10 @@
import Redis from '@ioc:Adonis/Addons/Redis'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import RuneSerializer from 'App/Serializers/RuneSerializer'
import Jax from 'App/Services/Jax'
import RuneTransformer from 'App/Transformers/RuneTransformer'
export default class CDragonController {
public async runes ({ response }: HttpContextContract) {
public async runes({ response }: HttpContextContract) {
const cacheUrl = 'cdragon-runes'
const requestCached = await Redis.get(cacheUrl)
@ -16,8 +16,8 @@ export default class CDragonController {
const perkstyles = await Jax.CDragon.perkstyles()
const runesData = {
perks: RuneTransformer.transformPerks(perks),
perkstyles: RuneTransformer.transformStyles(perkstyles.styles),
perks: RuneSerializer.serializePerks(perks),
perkstyles: RuneSerializer.serializeStyles(perkstyles.styles),
}
await Redis.set(cacheUrl, JSON.stringify(runesData), 'EX', 36000)

View file

@ -1,53 +1,23 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import DetailedMatch, { DetailedMatchModel } from 'App/Models/DetailedMatch'
import { ParticipantDetails } from 'App/Models/Match'
import Summoner from 'App/Models/Summoner'
import Jax from 'App/Services/Jax'
import Match from 'App/Models/Match'
import MatchPlayerRankParser from 'App/Parsers/MatchPlayerRankParser'
import DetailedMatchSerializer from 'App/Serializers/DetailedMatchSerializer'
import MatchPlayerRankSerializer from 'App/Serializers/MatchPlayerRankSerializer'
import MatchService from 'App/Services/MatchService'
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 MatchesIndexValidator from 'App/Validators/MatchesIndexValidator'
export default class MatchesController {
/**
* Get the soloQ rank of all the players of the team
* @param summoner all the data of the summoner
* @param region of the match
* POST - Return data from matches searched by matchIds
* @param ctx
*/
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
*/
public async index ({ request, response }: HttpContextContract) {
console.log('More Matches Request')
const { puuid, accountId, region, gameIds, season } = await request.validate(MatchesIndexValidator)
const summonerDB = await Summoner.findOne({ puuid })
if (!summonerDB) {
return response.json(null)
}
const matchesId = /^\d/.test(gameIds[0]) ? gameIds.map(id => `${region.toUpperCase()}_${id}`) : gameIds
const matches = await MatchService.getMatches(puuid, accountId, region, matchesId, summonerDB)
await summonerDB.save()
public async index({ request, response }: HttpContextContract) {
const { puuid, region, matchIds, season } = await request.validate(MatchesIndexValidator)
const matches = await MatchService.getMatches(region, matchIds, puuid)
const stats = await StatsService.getSummonerStats(puuid, season)
return response.json({
matches,
stats,
@ -56,58 +26,42 @@ export default class MatchesController {
/**
* 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')
const { gameId, region } = await request.validate(DetailedMatchValidator)
const { matchId } = await request.validate(DetailedMatchValidator)
let matchDetails: DetailedMatchModel
const alreadySaved = await DetailedMatch.findOne({ gameId, region })
const match = await Match.query()
.where('id', matchId)
.preload('teams')
.preload('players', (playersQuery) => {
playersQuery.preload('ranks')
})
.firstOrFail()
if (alreadySaved) {
console.log('MATCH DETAILS ALREADY SAVED')
matchDetails = alreadySaved
} else {
const matchId = `${region.toUpperCase()}_${gameId}`
const match = await Jax.Match.get(matchId, region)
matchDetails = await DetailedMatchTransformer.transform(match)
await DetailedMatch.create(matchDetails)
}
const { match: matchDetails, ranksLoaded } = DetailedMatchSerializer.serializeOneMatch(match)
console.timeEnd('MatchDetails')
return response.json({
matchDetails,
ranksLoaded,
})
}
/**
* 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')
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')
return response.json({
blueTeam: matchDetails.blueTeam.players,
redTeam: matchDetails.redTeam.players,
})
return response.json(serializedRanks)
}
}

View file

@ -2,11 +2,12 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { getCurrentSeason } from 'App/helpers'
import Summoner from 'App/Models/Summoner'
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 MatchService from 'App/Services/MatchService'
import StatsService from 'App/Services/StatsService'
import SummonerService from 'App/Services/SummonerService'
import LiveMatchTransformer from 'App/Transformers/LiveMatchTransformer'
import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator'
import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator'
import SummonerLiveValidator from 'App/Validators/SummonerLiveValidator'
@ -14,21 +15,8 @@ import SummonerOverviewValidator from 'App/Validators/SummonerOverviewValidator'
import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator'
export default class SummonersController {
/**
* Get all played seasons for a summoner
* @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) : [getCurrentSeason()]
}
/**
* POST: get basic summoner data
* @param ctx
*/
public async basic ({ request, response }: HttpContextContract) {
console.time('all')
public async basic({ request, response }: HttpContextContract) {
console.time('BASIC_REQUEST')
const { summoner, region } = await request.validate(SummonerBasicValidator)
const finalJSON: any = {}
@ -38,24 +26,20 @@ export default class SummonersController {
if (!account) {
return response.json(null)
}
account.region = region
finalJSON.account = account
// Summoner in DB
let summonerDB = await Summoner.findOne({ puuid: account.puuid })
if (!summonerDB) {
summonerDB = await Summoner.create({ puuid: account.puuid })
}
const summonerDB = await Summoner.firstOrCreate({ puuid: account.puuid })
// Summoner names
finalJSON.account.names = SummonerService.getAllSummonerNames(account, summonerDB)
finalJSON.account.names = await SummonerService.getAllSummonerNames(account, summonerDB)
// MATCH LIST
await MatchService.updateMatchList(account, summonerDB)
finalJSON.matchList = summonerDB.matchList
finalJSON.matchList = await MatchService.updateMatchList(account, region, summonerDB)
// 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
const currentGame = await Jax.Spectator.summonerID(account.id, region)
@ -63,97 +47,102 @@ export default class SummonersController {
finalJSON.current = currentGame
// RANKED STATS
finalJSON.ranked = await SummonerService.getRanked(account, region)
finalJSON.ranked = await SummonerService.getRanked(account.id, region)
// SAVE IN DB
await summonerDB.save()
} catch (error) {
console.log('username not found')
console.log(error)
// RECENT ACTIVITY
finalJSON.recentActivity = await StatsService.getRecentActivity(account.puuid)
} catch (e) {
console.log(e)
console.timeEnd('BASIC_REQUEST')
return response.json(null)
}
console.timeEnd('all')
console.timeEnd('BASIC_REQUEST')
return response.json(finalJSON)
}
/**
* POST: get overview view summoner data
* @param ctx
*/
public async overview ({ request, response }: HttpContextContract) {
console.time('overview')
const { puuid, accountId, region, season } = await request.validate(SummonerOverviewValidator)
public async overview({ request, response }: HttpContextContract) {
console.time('OVERVIEW_REQUEST')
const { puuid, region, season } = await request.validate(SummonerOverviewValidator)
const finalJSON: any = {}
// Summoner in DB
let summonerDB = await Summoner.findOne({ puuid: puuid })
if (!summonerDB) {
summonerDB = await Summoner.create({ puuid: puuid })
}
const summonerDB = await Summoner.firstOrCreate({ puuid: puuid })
// MATCHES BASIC
const gameIds = summonerDB.matchList!.slice(0)
// .filter(m => {
// return season ? m.seasonMatch === season : true // TODO: filter by season
// })
.slice(0, 10)
finalJSON.matchesDetails = await MatchService.getMatches(puuid, accountId, region, gameIds, summonerDB)
const matchlist = await summonerDB
.related('matchList')
.query()
.select('matchId')
.orderBy('matchId', 'desc')
.limit(10)
const matchIds = matchlist.map((m) => m.matchId)
finalJSON.matchesDetails = await MatchService.getMatches(region, matchIds, puuid)
// STATS
console.time('STATS')
finalJSON.stats = await StatsService.getSummonerStats(puuid, season)
console.timeEnd('STATS')
// SAVE IN DB
await summonerDB.save()
console.timeEnd('overview')
console.timeEnd('OVERVIEW_REQUEST')
return response.json(finalJSON)
}
/**
* 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')
const { puuid, queue, season } = await request.validate(SummonerChampionValidator)
const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
const championStatsSerialized = championStats.map((champion) => {
return {
...champion,
champion: BasicMatchSerializer.getChampion(champion.id),
}
})
console.timeEnd('championsRequest')
return response.json(championStats)
return response.json(championStatsSerialized)
}
/**
* 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')
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')
return response.json(records)
return response.json(recordsSerialized)
}
/**
* POST - Return live match detail
* @param ctx
* @param ctx
*/
public async liveMatchDetails ({ request, response }: HttpContextContract) {
public async liveMatchDetails({ request, response }: HttpContextContract) {
console.time('liveMatchDetails')
const { id, region } = await request.validate(SummonerLiveValidator)
// CURRENT GAME
let currentGame = await Jax.Spectator.summonerID(id, region)
const currentGame = await Jax.Spectator.summonerID(id, region)
if (!currentGame) {
return response.json(null)
}
currentGame = await LiveMatchTransformer.transform(currentGame, { region })
const currentGameSerialized = await LiveMatchSerializer.serializeOneMatch(currentGame, region)
console.timeEnd('liveMatchDetails')
return response.json(currentGame)
return response.json(currentGameSerialized)
}
}

View file

@ -17,7 +17,7 @@ import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
constructor () {
constructor() {
super(Logger)
}
}

View file

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

View file

@ -1,140 +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 {
account_id: string,
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 default class Match extends BaseModel {
public static selfAssignPrimaryKey = true
// V5
matchId: string,
}
@column({ isPrimary: true })
public id: string
export interface ParticipantDetails {
name: 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> {
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
@column()
public gameId: number
public result: string
public allyTeam: ParticipantBasic[]
public enemyTeam: ParticipantBasic[]
public map: number
public gamemode: number
public date: number
public region: string
public season: number
public time: number
public name: string
public summonerId: string
public champion: Champion
public role: string
public primaryRune: string
public secondaryRune: string
public level: number
public items: Item[]
public firstSum: number
public secondSum: number
public stats: Stats
// V5
public matchId: string
@column()
public map: number
@column()
public gamemode: number
@column()
public date: number
@column()
public region: string
@column()
public result: number
@column()
public season: number
@column()
public gameDuration: number
@hasMany(() => MatchTeam)
public teams: HasMany<typeof MatchTeam>
@hasMany(() => MatchPlayer)
public players: HasMany<typeof MatchPlayer>
}

View file

@ -1,21 +1,36 @@
import { Model } from '@ioc:Mongodb/Model'
import { MatchlistDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint'
import { DateTime } from 'luxon'
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 {
puuid: string,
matchList?: MatchlistDto,
names?: SummonerNames[]
}
interface SummonerNames {
name: string,
date: Date
}
export default class Summoner extends Model implements SummonerModel {
public static collectionName = 'summoners'
export default class Summoner extends BaseModel {
public static selfAssignPrimaryKey = true
@column({ isPrimary: true })
public puuid: string
public matchList?: MatchlistDto
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>
}

View file

@ -1,337 +1,257 @@
import mongodb from '@ioc:Mongodb/Database'
import { Collection } from 'mongodb'
import Database from '@ioc:Adonis/Lucid/Database'
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 () {
this.getCollection()
private readonly GLOBAL_FILTERS = `
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
}
/**
* Basic matchParams used in a lot of requests
* @param puuid of the summoner
*/
private matchParams (puuid: string, season?: number) {
return {
summoner_puuid: puuid,
result: { $not: { $eq: 'Remake' } },
gamemode: { $nin: [800, 810, 820, 830, 840, 850, 2000, 2010, 2020] },
season: season ? season : { $exists: true },
}
public async globalStats(puuid: string) {
const query = `
SELECT
SUM(match_players.kills) as kills,
SUM(match_players.deaths) as deaths,
SUM(match_players.assists) as assists,
SUM(match_players.minions) as minions,
SUM(matches.game_duration) as time,
SUM(match_players.vision_score) as vision,
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]
}
/**
* Build the aggregate mongo query
* @param puuid
* @param matchParams
* @param intermediateSteps
* @param groupId
* @param groupParams
* @param finalSteps
*/
private async aggregate (
puuid: string,
matchParams: object,
intermediateSteps: any[],
groupId: any,
groupParams: object,
finalSteps: any[],
season?: number,
) {
return this.collection.aggregate([
{
$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 gamemodeStats(puuid: string) {
const query = `
SELECT
matches.gamemode 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
matches.gamemode
ORDER BY
count DESC
`
const { rows } = await Database.rawQuery(query, { puuid })
return rows
}
/**
* Get MongoDB matches collection
*/
public async getCollection () {
if (!this.collection) {
this.collection = await mongodb.connection().collection('matches')
}
public async roleStats(puuid: string) {
const query = `
SELECT
match_players.team_position as role,
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}
AND match_players.team_position != 0
GROUP BY
role
`
const { rows } = await Database.rawQuery(query, { puuid })
return rows
}
/**
* Get Summoner's statistics for the N most played champions
* @param puuid of the summoner
* @param limit number of champions to fetch
* @param season
*/
public async championStats (puuid: string, limit = 5, season?: number) {
const groupParams = {
champion: { $first: '$champion' },
kills: { $sum: '$stats.kills' },
deaths: { $sum: '$stats.deaths' },
assists: { $sum: '$stats.assists' },
}
const finalSteps = [
{ $sort: { 'count': -1, 'champion.name': 1 } },
{ $limit: limit },
public async championStats(puuid: string, limit: 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
FROM
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
}
public async championClassStats(puuid: string) {
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)
}
/**
* Get Summoner's statistics for all played champion classes
* @param puuid of the summoner
* @param season
*/
public async championClassStats (puuid: string, season?: number) {
const groupId = { '$arrayElemAt': ['$champion.roles', 0] }
return this.aggregate(puuid, {}, [], groupId, {}, [], season)
}
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 ')
/**
* Get Summoner's complete statistics for the all played champs
* @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)
const { rows } = await Database.rawQuery(query, { puuid })
return rows
}
}

View file

@ -1,18 +1,18 @@
import Env from '@ioc:Adonis/Core/Env'
export interface JaxConfig {
key: string,
region: string,
key: string
region: string
requestOptions: JaxConfigRequestOptions
}
export interface JaxConfigRequestOptions {
retriesBeforeAbort: number,
delayBeforeRetry: number,
retriesBeforeAbort: number
delayBeforeRetry: number
}
export const JAX_CONFIG: JaxConfig = {
key: Env.get('API_KEY') as string,
key: Env.get('RIOT_API_KEY') as string,
region: 'euw1',
requestOptions: {
retriesBeforeAbort: 3,

View file

@ -11,7 +11,7 @@ export default class CDragonRequest {
private retries: number
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.endpoint = endpoint
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/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 requestCached = await Redis.get(url)

View file

@ -2,103 +2,103 @@ import { JaxConfig } from '../../JaxConfig'
import CDragonRequest from '../CDragonRequest'
export interface ChampionDTO {
id: number,
name: string,
alias: string,
squarePortraitPath: string,
id: number
name: string
alias: string
squarePortraitPath: string
roles: string[]
}
export interface ItemDTO {
id: number,
name: string,
description: string,
active: boolean,
inStore: boolean,
from: number[],
to: number[],
categories: string[],
mapStringIdInclusions: string[],
maxStacks: number,
modeNameInclusions: string[],
requiredChampion: string,
requiredAlly: string,
requiredBuffCurrencyName: string,
requiredBuffCurrencyCost: number,
specialRecipe: number,
isEnchantment: boolean,
price: number,
priceTotal: number,
id: number
name: string
description: string
active: boolean
inStore: boolean
from: number[]
to: number[]
categories: string[]
mapStringIdInclusions: string[]
maxStacks: number
modeNameInclusions: string[]
requiredChampion: string
requiredAlly: string
requiredBuffCurrencyName: string
requiredBuffCurrencyCost: number
specialRecipe: number
isEnchantment: boolean
price: number
priceTotal: number
iconPath: string
}
export interface PerkDTO {
id: number,
name: string,
majorChangePatchVersion: string,
tooltip: string,
shortDesc: string,
longDesc: string,
iconPath: string,
id: number
name: string
majorChangePatchVersion: string
tooltip: string
shortDesc: string
longDesc: string
iconPath: string
endOfGameStatDescs: string[]
}
export interface PerkStyleResponse {
schemaVersion: string,
schemaVersion: string
styles: PerkStyleDTO[]
}
export interface PerkStyleDTO {
id: number,
name: string,
tooltip: string,
iconPath: string,
assetMap: { [key: string]: string },
isAdvanced: boolean,
allowedSubStyles: number[],
subStyleBonus: { styleId: number, perkId: number }[],
slots: { type: string, slotLabel: string, perks: number[] }[],
defaultPageName: string,
defaultSubStyle: number,
defaultPerks: number[],
defaultPerksWhenSplashed: number[],
defaultStatModsPerSubStyle: { id: string, perks: number[] }[]
id: number
name: string
tooltip: string
iconPath: string
assetMap: { [key: string]: string }
isAdvanced: boolean
allowedSubStyles: number[]
subStyleBonus: { styleId: number; perkId: number }[]
slots: { type: string; slotLabel: string; perks: number[] }[]
defaultPageName: string
defaultSubStyle: number
defaultPerks: number[]
defaultPerksWhenSplashed: number[]
defaultStatModsPerSubStyle: { id: string; perks: number[] }[]
}
export interface SummonerSpellDTO {
id: number,
name: string,
description: string,
summonerLevel: number,
cooldown: number,
gameModes: string[],
id: number
name: string
description: string
summonerLevel: number
cooldown: number
gameModes: string[]
iconPath: string
}
export default class CDragonEndpoint {
private config: JaxConfig
constructor (config: JaxConfig) {
constructor(config: JaxConfig) {
this.config = config
}
public async champions (): Promise<ChampionDTO[]> {
public async champions(): Promise<ChampionDTO[]> {
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()
}
public async perks (): Promise<PerkDTO[]> {
public async perks(): Promise<PerkDTO[]> {
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()
}
public async summonerSpells (): Promise<SummonerSpellDTO[]> {
public async summonerSpells(): Promise<SummonerSpellDTO[]> {
return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute()
}
}

View file

@ -4,26 +4,26 @@ import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface LeagueEntryDTO {
leagueId: string;
queueType: string;
tier: string;
rank: string;
summonerId: string;
summonerName: string;
leaguePoints: number;
wins: number;
losses: number;
veteran: boolean;
inactive: boolean;
freshBlood: boolean;
hotStreak: boolean;
leagueId: string
queueType: string
tier: string
rank: string
summonerId: string
summonerName: string
leaguePoints: number
wins: number
losses: number
veteran: boolean
inactive: boolean
freshBlood: boolean
hotStreak: boolean
miniSeries?: MiniSeriesDTO
}
interface MiniSeriesDTO {
losses: number,
progress: string,
target: number,
losses: number
progress: string
target: number
wins: number
}
@ -31,12 +31,12 @@ export default class LeagueEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public summonerID (summonerID: string, region: string): Promise<LeagueEntryDTO[]> {
public summonerID(summonerID: string, region: string): Promise<LeagueEntryDTO[]> {
return new JaxRequest(
region,
this.config,

View file

@ -4,424 +4,209 @@ import RiotRateLimiter from 'riot-ratelimiter'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
// export interface MatchDto {
// gameId: number,
// participantIdentities: ParticipantIdentityDto[],
// queueId: number,
// gameType: string,
// gameDuration: number,
// teams: TeamStatsDto[],
// platformId: string
// gameCreation: number,
// seasonId: number,
// gameVersion: 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 {
// participantId: number,
// championId: number,
// runes: RuneDto[],
// stats: ParticipantStatsDto,
// teamId: number,
// timeline: ParticipantTimelineDto,
// spell1Id: number,
// spell2Id: number,
// highestAchievedSeasonTier?:
// 'CHALLENGER' | 'MASTER' | 'DIAMOND' | 'PLATINUM' | 'GOLD' | 'SILVER' | 'BRONZE' | 'UNRANKED',
// masteries: MasteryDto[]
// }
// export interface RuneDto {
// runeId: number,
// rank: 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
// 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 ParticipantTimelineDto {
// 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 MasteryDto {
// rank: number,
// masteryId: number,
// }
/**
*
* ===============================================
* V5
* ===============================================
*
*/
export interface MatchDto {
metadata: MetadataDto;
info: InfoDto;
metadata: MetadataDto
info: InfoDto
}
export interface MetadataDto {
dataVersion: string;
matchId: string;
participants: string[];
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;
queueId: number;
teams: TeamDto[];
tournamentCode?: string;
gameCreation: number
gameDuration: number
gameId: number
gameMode: string
gameName: string
gameStartTimestamp: number
gameType: string
gameVersion: string
mapId: number
participants: ParticipantDto[]
platformId: string
queueId: number
teams: TeamDto[]
tournamentCode?: string
}
export interface ParticipantDto {
assists: number;
baronKills: number;
bountyLevel: number;
champExperience: number;
champLevel: number;
championId: number;
championName: string;
championTransform: ChampionTransformDto;
consumablesPurchased: number;
damageDealtToObjectives: number;
damageDealtToTurrets: number;
damageSelfMitigated: number;
deaths: number;
detectorWardsPlaced: number;
doubleKills: number;
dragonKills: number;
firstBloodAssist: boolean;
firstBloodKill: boolean;
firstTowerAssist: boolean;
firstTowerKill: boolean;
gameEndedInEarlySurrender: boolean;
gameEndedInSurrender: boolean;
goldEarned: number;
goldSpent: number;
individualPosition: 'Invalid' | TeamPositionDto; // TODO
inhibitorKills: number;
item0: number;
item1: number;
item2: number;
item3: number;
item4: number;
item5: number;
item6: number;
itemsPurchased: number;
killingSprees: number;
kills: number;
lane: LaneDto; // TODO
largestCriticalStrike: number;
largestKillingSpree: number;
largestMultiKill: number;
longestTimeSpentLiving: number;
magicDamageDealt: number;
magicDamageDealtToChampions: number;
magicDamageTaken: number;
neutralMinionsKilled: number;
nexusKills: number;
objectivesStolen: number;
objectivesStolenAssists: number;
participantId: number;
pentaKills: number;
perks: PerksDto;
physicalDamageDealt: number;
physicalDamageDealtToChampions: number;
physicalDamageTaken: number;
profileIcon: number;
puuid: string;
quadraKills: number;
riotIdName: string;
riotIdTagline: string;
role: RoleDto; // TODO
sightWardsBoughtInGame: number;
spell1Casts: number;
spell2Casts: number;
spell3Casts: number;
spell4Casts: number;
summoner1Casts: number;
summoner1Id: number;
summoner2Casts: number;
summoner2Id: number;
summonerId: string;
summonerLevel: number;
summonerName: string;
teamEarlySurrendered: boolean;
teamId: number;
teamPosition: TeamPositionDto; // TODO
timeCCingOthers: number;
timePlayed: number;
totalDamageDealt: number;
totalDamageDealtToChampions: number;
totalDamageShieldedOnTeammates: 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;
assists: number
baronKills: number
bountyLevel: number
champExperience: number
champLevel: number
championId: number
championName: string
championTransform: ChampionTransformDto
consumablesPurchased: number
damageDealtToObjectives: number
damageDealtToTurrets: number
damageSelfMitigated: number
deaths: number
detectorWardsPlaced: number
doubleKills: number
dragonKills: number
firstBloodAssist: boolean
firstBloodKill: boolean
firstTowerAssist: boolean
firstTowerKill: boolean
gameEndedInEarlySurrender: boolean
gameEndedInSurrender: boolean
goldEarned: number
goldSpent: number
individualPosition: 'Invalid' | TeamPositionDto // TODO
inhibitorKills: number
item0: number
item1: number
item2: number
item3: number
item4: number
item5: number
item6: number
itemsPurchased: number
killingSprees: number
kills: number
lane: LaneDto // TODO
largestCriticalStrike: number
largestKillingSpree: number
largestMultiKill: number
longestTimeSpentLiving: number
magicDamageDealt: number
magicDamageDealtToChampions: number
magicDamageTaken: number
neutralMinionsKilled: number
nexusKills: number
objectivesStolen: number
objectivesStolenAssists: number
participantId: number
pentaKills: number
perks: PerksDto
physicalDamageDealt: number
physicalDamageDealtToChampions: number
physicalDamageTaken: number
profileIcon: number
puuid: string
quadraKills: number
riotIdName: string
riotIdTagline: string
role: RoleDto // TODO
sightWardsBoughtInGame: number
spell1Casts: number
spell2Casts: number
spell3Casts: number
spell4Casts: number
summoner1Casts: number
summoner1Id: number
summoner2Casts: number
summoner2Id: number
summonerId: string
summonerLevel: number
summonerName: string
teamEarlySurrendered: boolean
teamId: number
teamPosition: TeamPositionDto // TODO
timeCCingOthers: number
timePlayed: number
totalDamageDealt: number
totalDamageDealtToChampions: number
totalDamageShieldedOnTeammates: 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 enum ChampionTransformDto {
None,
Slayer,
Assasin
Assasin,
}
export type LaneDto = 'TOP' | 'JUNGLE' |'MIDDLE' | 'BOTTOM'
export type LaneDto = 'TOP' | 'JUNGLE' | 'MIDDLE' | 'BOTTOM'
export interface PerksDto {
statPerks: PerkStatsDto;
styles: PerkStyleDto[];
statPerks: PerkStatsDto
styles: PerkStyleDto[]
}
export interface PerkStatsDto {
defense: number;
flex: number;
offense: number;
defense: number
flex: number
offense: number
}
export interface PerkStyleDto {
description: 'primaryStyle' | 'subStyle';
selections: PerkStyleSelectionDto[];
style: number;
description: 'primaryStyle' | 'subStyle'
selections: PerkStyleSelectionDto[]
style: number
}
export interface PerkStyleSelectionDto {
perk: number;
var1: number;
var2: number;
var3: number;
perk: number
var1: number
var2: number
var3: number
}
export type RoleDto = 'NONE' | 'DUO' |'SOLO' | 'CARRY' | 'SUPPORT'
export type RoleDto = 'NONE' | 'DUO' | 'SOLO' | 'CARRY' | 'SUPPORT'
export type TeamPositionDto = 'TOP' | 'JUNGLE' |'MIDDLE' | 'BOTTOM' | 'UTILITY'
export type TeamPositionDto = 'TOP' | 'JUNGLE' | 'MIDDLE' | 'BOTTOM' | 'UTILITY'
export interface TeamDto {
bans: BanDto[];
objectives: ObjectivesDto;
teamId: number;
win: boolean;
bans: BanDto[]
objectives: ObjectivesDto
teamId: number
win: boolean
}
export interface BanDto {
championId: number;
pickTurn: number;
championId: number
pickTurn: number
}
export interface ObjectivesDto {
baron: ObjectiveDto;
champion: ObjectiveDto;
dragon: ObjectiveDto;
inhibitor: ObjectiveDto;
riftHerald: ObjectiveDto;
tower: ObjectiveDto;
baron: ObjectiveDto
champion: ObjectiveDto
dragon: ObjectiveDto
inhibitor: ObjectiveDto
riftHerald: ObjectiveDto
tower: ObjectiveDto
}
export interface ObjectiveDto {
first: boolean;
kills: number;
first: boolean
kills: number
}
export default class MatchEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
this.get = this.get.bind(this)
}
public get (matchID: string, region: string): Promise<MatchDto> {
public get(matchID: string, region: string): Promise<MatchDto> {
return new JaxRequest(
getRiotRegion(region),
this.config,

View file

@ -24,11 +24,11 @@ import JaxRequest from '../JaxRequest'
// }
/**
*
*
* ===============================================
* V5
* ===============================================
*
*
*/
export type MatchlistDto = string[]
@ -37,12 +37,12 @@ export default class MatchlistEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public puuid (puuid: string, region: string, beginIndex = 0, count = 100): Promise<MatchlistDto> {
public puuid(puuid: string, region: string, beginIndex = 0, count = 100): Promise<MatchlistDto> {
return new JaxRequest(
getRiotRegion(region),
this.config,

View file

@ -1,59 +1,53 @@
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
import RiotRateLimiter from 'riot-ratelimiter'
import { LeagueEntriesByQueue } from 'App/Services/SummonerService'
import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface CurrentGameInfo {
gameId: number,
export interface CurrentGameInfoDTO {
gameId: number
gameType: string
gameStartTime: number,
mapId: number,
gameLength: number,
platformId: string,
gameMode: string,
bannedChampions: BannedChampion[],
gameQueueConfigId: number,
observers: Observer,
participants: CurrentGameParticipant[],
gameStartTime: number
mapId: number
gameLength: number
platformId: string
gameMode: string
bannedChampions: BannedChampionDTO[]
gameQueueConfigId: number
observers: ObserverDTO
participants: CurrentGameParticipantDTO[]
}
export interface BannedChampion {
pickTurn: number,
championId: number,
teamId: number,
export interface BannedChampionDTO {
pickTurn: number
championId: number
teamId: number
}
export interface Observer {
encryptionKey: string,
export interface ObserverDTO {
encryptionKey: string
}
export interface CurrentGameParticipant {
championId: number,
perks: Perks,
profileIconId: number,
bot: boolean,
teamId: number,
summonerName: string,
summonerId: string,
spell1Id: number,
spell2Id: number,
gameCustomizationObjects: GameCustomizationObject[],
// Custom types from here
role?: string,
runes?: { primaryRune: string, secondaryRune: string } | {}
level?: number,
rank?: LeagueEntriesByQueue
export interface CurrentGameParticipantDTO {
championId: number
perks: PerksDTO
profileIconId: number
bot: boolean
teamId: number
summonerName: string
summonerId: string
spell1Id: number
spell2Id: number
gameCustomizationObjects: GameCustomizationObjectDTO[]
}
export interface Perks {
export interface PerksDTO {
perkIds: number[]
perkStyle: number,
perkStyle: number
perkSubStyle: number
}
export interface GameCustomizationObject {
category: string,
export interface GameCustomizationObjectDTO {
category: string
content: string
}
@ -61,12 +55,12 @@ export default class SpectatorEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
this.limiter = limiter
}
public summonerID (summonerID: string, region: string) {
public summonerID(summonerID: string, region: string): Promise<CurrentGameInfoDTO | undefined> {
return new JaxRequest(
region,
this.config,

View file

@ -4,26 +4,35 @@ import { JaxConfig } from '../../JaxConfig'
import JaxRequest from '../JaxRequest'
export interface SummonerDTO {
accountId: string,
profileIconId: number,
revisionDate: number,
name: string,
id: string,
puuid: string,
summonerLevel: number,
region?: string
accountId: string
profileIconId: number
revisionDate: number
name: string
id: string
puuid: string
summonerLevel: number
}
export default class SummonerEndpoint {
private config: JaxConfig
private limiter: RiotRateLimiter
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
constructor(config: JaxConfig, limiter: RiotRateLimiter) {
this.config = config
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(
region,
this.config,
@ -33,7 +42,7 @@ export default class SummonerEndpoint {
).execute()
}
public summonerName (summonerName: string, region: string): Promise<SummonerDTO> {
public summonerName(summonerName: string, region: string): Promise<SummonerDTO> {
return new JaxRequest(
region,
this.config,

Some files were not shown because too many files have changed in this diff Show more