mirror of
https://github.com/vkaelin/LeagueStats.git
synced 2026-03-25 21:07:27 +00:00
Merge branch 'adonis-5'
This commit is contained in:
commit
d5bb727219
111 changed files with 6733 additions and 5846 deletions
21
README.md
21
README.md
|
|
@ -18,10 +18,9 @@ Here is an [example](https://leaguestats.gg/summoner/euw/Alderiate) of stats for
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Development environment requirements :
|
Development environment requirements :
|
||||||
- [Node.js](https://nodejs.org/en/download/)
|
- [Node.js](https://nodejs.org/en/download/) >= 12.0.0
|
||||||
- [MongoDB](https://www.mongodb.com/download-center/community)
|
- [MongoDB](https://www.mongodb.com/download-center/community) >= 4.4
|
||||||
- [Redis](https://redis.io/download)
|
- [Redis](https://redis.io/download)
|
||||||
- [Adonis CLI](https://github.com/adonisjs/adonis-cli)
|
|
||||||
|
|
||||||
Setting up your development environment on your local machine :
|
Setting up your development environment on your local machine :
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -33,26 +32,32 @@ Setting up your development environment on your local machine :
|
||||||
|
|
||||||
> cd leaguestats/server
|
> cd leaguestats/server
|
||||||
> npm install
|
> npm install
|
||||||
> cp .env.example .env
|
> cp .env.example .env # edit the values
|
||||||
> adonis migration:run
|
> node ace mongodb:migration:run # your MongoDB installation needs to by a Replica Set and not a Standalone
|
||||||
```
|
```
|
||||||
|
|
||||||
## Useful commands
|
## Useful commands
|
||||||
Running the app :
|
Running the app :
|
||||||
```bash
|
```bash
|
||||||
> cd client
|
> cd client
|
||||||
> npm run serve
|
> npm run dev
|
||||||
|
|
||||||
# And
|
# And
|
||||||
|
|
||||||
> cd server
|
> cd server
|
||||||
> adonis serve --dev
|
> npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Building the app :
|
Deploying the app :
|
||||||
```bash
|
```bash
|
||||||
> cd client
|
> cd client
|
||||||
> npm run build
|
> npm run build
|
||||||
|
|
||||||
|
# And
|
||||||
|
|
||||||
|
> cd server
|
||||||
|
> npm run build
|
||||||
|
> node build/server.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@
|
||||||
<span>V</span>
|
<span>V</span>
|
||||||
<span class="ml-4 -mt-3">S</span>
|
<span class="ml-4 -mt-3">S</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-10 pb-2 text-blue-200">{{ displayStartTime }}</div>
|
<div
|
||||||
|
:class="{'w-10': displayStartTime !== 'Not started yet'}"
|
||||||
|
class="pb-2 text-blue-200"
|
||||||
|
>
|
||||||
|
{{ displayStartTime }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="w-1/2 text-left">
|
<ul class="w-1/2 text-left">
|
||||||
<li
|
<li
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export const axios = axiosHttp
|
||||||
|
|
||||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
||||||
axios.defaults.headers.common['Content-Type'] = 'application/json'
|
axios.defaults.headers.common['Content-Type'] = 'application/json'
|
||||||
axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? 'http://localhost:5000/' : 'https://api.leaguestats.gg/'
|
axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? 'http://localhost:3333/' : 'https://api.leaguestats.gg/'
|
||||||
|
|
||||||
const CancelToken = axios.CancelToken
|
const CancelToken = axios.CancelToken
|
||||||
const axiosSource = CancelToken.source()
|
const axiosSource = CancelToken.source()
|
||||||
|
|
|
||||||
|
|
@ -115,32 +115,35 @@ export const actions = {
|
||||||
commit('BASIC_REQUEST')
|
commit('BASIC_REQUEST')
|
||||||
try {
|
try {
|
||||||
const resp = await axios(({ url: 'summoner/basic', data: { summoner, region: regionId }, method: 'POST' }))
|
const resp = await axios(({ url: 'summoner/basic', data: { summoner, region: regionId }, method: 'POST' }))
|
||||||
if (resp.data) {
|
if (!resp.data) {
|
||||||
console.log(`---SUMMONER INFOS ${resp.data.account.name}---`)
|
|
||||||
console.log(resp.data)
|
|
||||||
const infos = createBasicSummonerData(resp.data)
|
|
||||||
commit('SUMMONER_FOUND', infos)
|
|
||||||
|
|
||||||
// Add summoner to recent searches
|
|
||||||
dispatch('settings/addRecentSearch', {
|
|
||||||
name: infos.account.name,
|
|
||||||
icon: infos.account.profileIconId,
|
|
||||||
region,
|
|
||||||
}, { root: true })
|
|
||||||
} else {
|
|
||||||
commit('SUMMONER_NOT_FOUND')
|
|
||||||
|
|
||||||
dispatch('notification/add', {
|
dispatch('notification/add', {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Summoner not found.'
|
message: 'Summoner not found.'
|
||||||
}, { root: true })
|
}, { root: true })
|
||||||
console.log('Summoner not found - store')
|
return commit('SUMMONER_NOT_FOUND')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`---SUMMONER INFOS ${resp.data.account.name}---`)
|
||||||
|
console.log(resp.data)
|
||||||
|
const infos = createBasicSummonerData(resp.data)
|
||||||
|
commit('SUMMONER_FOUND', infos)
|
||||||
|
|
||||||
|
// Add summoner to recent searches
|
||||||
|
dispatch('settings/addRecentSearch', {
|
||||||
|
name: infos.account.name,
|
||||||
|
icon: infos.account.profileIconId,
|
||||||
|
region,
|
||||||
|
}, { root: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.response && error.response.status === 422) {
|
||||||
|
dispatch('notification/add', {
|
||||||
|
type: 'error',
|
||||||
|
message: 'Summoner not found.'
|
||||||
|
}, { root: true })
|
||||||
|
}
|
||||||
if (error.message !== 'Summoner changed') {
|
if (error.message !== 'Summoner changed') {
|
||||||
commit('SUMMONER_NOT_FOUND')
|
commit('SUMMONER_NOT_FOUND')
|
||||||
}
|
}
|
||||||
console.log(error)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
championsNotLoaded({ commit }) {
|
championsNotLoaded({ commit }) {
|
||||||
|
|
@ -155,9 +158,14 @@ export const actions = {
|
||||||
},
|
},
|
||||||
async liveMatchRequest({ commit, rootState }) {
|
async liveMatchRequest({ commit, rootState }) {
|
||||||
commit('LIVE_LOADING')
|
commit('LIVE_LOADING')
|
||||||
const region = rootState.regionsList[rootState.settings.region]
|
const resp = await axios(({
|
||||||
|
url: 'summoner/live',
|
||||||
const resp = await axios(({ url: 'summoner/live', data: { account: state.basic.account, region }, method: 'POST' })).catch(() => { })
|
data: {
|
||||||
|
id: state.basic.account.id,
|
||||||
|
region: rootState.regionsList[rootState.settings.region]
|
||||||
|
},
|
||||||
|
method: 'POST'
|
||||||
|
})).catch(() => { })
|
||||||
console.log('---LIVE---')
|
console.log('---LIVE---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
|
|
||||||
|
|
@ -167,20 +175,36 @@ export const actions = {
|
||||||
commit('SUMMONER_NOT_PLAYING')
|
commit('SUMMONER_NOT_PLAYING')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async moreMatches({ commit }) {
|
async moreMatches({ commit, rootState }) {
|
||||||
commit('MATCHES_LOADING')
|
commit('MATCHES_LOADING')
|
||||||
|
|
||||||
const account = state.basic.account
|
|
||||||
const gameIds = state.basic.matchList.slice(state.overview.matchIndex, state.overview.matchIndex + 10).map(({ gameId }) => gameId)
|
const gameIds = state.basic.matchList.slice(state.overview.matchIndex, state.overview.matchIndex + 10).map(({ gameId }) => gameId)
|
||||||
|
|
||||||
const resp = await axios(({ url: 'match', data: { account, gameIds }, method: 'POST' })).catch(() => { })
|
const resp = await axios(({
|
||||||
|
url: 'match',
|
||||||
|
data: {
|
||||||
|
puuid: state.basic.account.puuid,
|
||||||
|
accountId: state.basic.account.accountId,
|
||||||
|
region: rootState.regionsList[rootState.settings.region],
|
||||||
|
gameIds
|
||||||
|
},
|
||||||
|
method: 'POST'
|
||||||
|
})).catch(() => { })
|
||||||
console.log('---MATCHES INFOS---')
|
console.log('---MATCHES INFOS---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
const newMatches = createMatchData(resp.data.matches)
|
const newMatches = createMatchData(resp.data.matches)
|
||||||
commit('MATCHES_FOUND', { newMatches, stats: resp.data.stats })
|
commit('MATCHES_FOUND', { newMatches, stats: resp.data.stats })
|
||||||
},
|
},
|
||||||
async overviewRequest({ commit }) {
|
async overviewRequest({ commit, rootState }) {
|
||||||
const resp = await axios(({ url: 'summoner/overview', data: { account: state.basic.account }, method: 'POST' })).catch(() => { })
|
const resp = await axios(({
|
||||||
|
url: 'summoner/overview',
|
||||||
|
data: {
|
||||||
|
puuid: state.basic.account.puuid,
|
||||||
|
accountId: state.basic.account.accountId,
|
||||||
|
region: rootState.regionsList[rootState.settings.region],
|
||||||
|
},
|
||||||
|
method: 'POST'
|
||||||
|
})).catch(() => { })
|
||||||
console.log('---OVERVIEW---')
|
console.log('---OVERVIEW---')
|
||||||
console.log(resp.data)
|
console.log(resp.data)
|
||||||
resp.data.matches = createMatchData(resp.data.matchesDetails)
|
resp.data.matches = createMatchData(resp.data.matchesDetails)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<div v-if="liveLoaded" class="flex items-center justify-end -mt-4 text-base text-blue-200">
|
<div v-if="liveLoaded" class="flex items-center justify-end -mt-4 text-base text-blue-200">
|
||||||
<div>{{ gamemode.type }} {{ gamemode.name }}</div>
|
<div>{{ gamemode.type }} {{ gamemode.name }}</div>
|
||||||
<div class="mx-2">-</div>
|
<div class="mx-2">-</div>
|
||||||
<div class="w-12">{{ displayStartTime }}</div>
|
<div :class="{'w-12': displayStartTime !== 'Not started yet'}">{{ displayStartTime }}</div>
|
||||||
<button
|
<button
|
||||||
@click="liveMatchRequest"
|
@click="liveMatchRequest"
|
||||||
class="px-3 py-1 ml-4 text-blue-100 bg-blue-800 rounded-md shadow-md hover:bg-blue-760"
|
class="px-3 py-1 ml-4 text-blue-100 bg-blue-800 rounded-md shadow-md hover:bg-blue-760"
|
||||||
|
|
|
||||||
29
server/.adonisrc.json
Normal file
29
server/.adonisrc.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"typescript": true,
|
||||||
|
"commands": [
|
||||||
|
"./commands",
|
||||||
|
"@adonisjs/core/build/commands",
|
||||||
|
"@zakodium/adonis-mongodb/lib/commands"
|
||||||
|
],
|
||||||
|
"exceptionHandlerNamespace": "App/Exceptions/Handler",
|
||||||
|
"aliases": {
|
||||||
|
"App": "app",
|
||||||
|
"Contracts": "contracts",
|
||||||
|
"Config": "config",
|
||||||
|
"Database": "database"
|
||||||
|
},
|
||||||
|
"preloads": [
|
||||||
|
"./start/routes",
|
||||||
|
"./start/kernel"
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
"./providers/AppProvider",
|
||||||
|
"@adonisjs/core",
|
||||||
|
"@zakodium/adonis-mongodb",
|
||||||
|
"@adonisjs/redis"
|
||||||
|
],
|
||||||
|
"metaFiles": [
|
||||||
|
".env",
|
||||||
|
".adonisrc.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
# editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_size = 2
|
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
insert_final_newline = ignore
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,14 @@
|
||||||
HOST=127.0.0.1
|
PORT=3333
|
||||||
PORT=5000
|
HOST=0.0.0.0
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
APP_NAME=LeagueStats
|
|
||||||
APP_URL=http://${HOST}:${PORT}
|
|
||||||
|
|
||||||
CACHE_VIEWS=false
|
|
||||||
|
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|
||||||
DB_CONNECTION=mongodb
|
MONGODB_URL=mongodb://localhost:27017
|
||||||
DB_HOST=127.0.0.1
|
MONGODB_DATABASE=leaguestats
|
||||||
DB_PORT=27017
|
|
||||||
DB_USER=root
|
|
||||||
DB_PASSWORD=
|
|
||||||
DB_DATABASE=leaguestats
|
|
||||||
|
|
||||||
REDIS_CONNECTION=local
|
REDIS_CONNECTION=local
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
HASH_DRIVER=bcrypt
|
|
||||||
|
|
||||||
API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
|
|
||||||
1
server/.eslintignore
Normal file
1
server/.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build
|
||||||
13
server/.eslintrc.json
Normal file
13
server/.eslintrc.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"plugin:adonis/typescriptApp"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"max-len": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"code": 130
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
server/.gitignore
vendored
14
server/.gitignore
vendored
|
|
@ -1,11 +1,7 @@
|
||||||
# Node modules
|
|
||||||
node_modules
|
node_modules
|
||||||
|
build
|
||||||
# Adonis directory for storing tmp files
|
coverage
|
||||||
tmp
|
.vscode
|
||||||
|
.DS_STORE
|
||||||
# Environment variables, never commit this file
|
|
||||||
.env
|
.env
|
||||||
|
tmp
|
||||||
# The development sqlite file
|
|
||||||
database/development.sqlite
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# LeagueStats Backend
|
|
||||||
|
|
||||||
This is the boilerplate for creating an API server in AdonisJs, it comes pre-configured with.
|
|
||||||
|
|
||||||
1. Bodyparser
|
|
||||||
2. Authentication
|
|
||||||
3. CORS
|
|
||||||
4. Lucid ORM
|
|
||||||
5. Migrations and seeds
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Use the adonis command to install the blueprint
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adonis new yardstick --api-only
|
|
||||||
```
|
|
||||||
|
|
||||||
or manually clone the repo and then run `npm install`.
|
|
||||||
|
|
||||||
|
|
||||||
### Migrations
|
|
||||||
|
|
||||||
Run the following command to run startup migrations.
|
|
||||||
|
|
||||||
```js
|
|
||||||
adonis migration:run
|
|
||||||
```
|
|
||||||
20
server/ace
20
server/ace
|
|
@ -1,21 +1,19 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Ace Commands
|
| Ace Commands
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| The ace file is just a regular Javascript file but with no extension. You
|
| This file is the entry point for running ace commands. For typescript
|
||||||
| can call `node ace` followed by the command name and it just works.
|
| projects, the ace commands will fallback to the compiled code and
|
||||||
|
|
| hence this file has to be executable by node directly.
|
||||||
| Also you can use `adonis` followed by the command name, since the adonis
|
|
||||||
| global proxies all the ace commands.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { Ignitor } = require('@adonisjs/ignitor')
|
require('reflect-metadata')
|
||||||
|
require('source-map-support').install({ handleUncaughtExceptions: false })
|
||||||
|
|
||||||
new Ignitor(require('@adonisjs/fold'))
|
const { Ignitor } = require('@adonisjs/core/build/src/Ignitor')
|
||||||
.appRoot(__dirname)
|
new Ignitor(__dirname)
|
||||||
.fireAce()
|
.ace()
|
||||||
|
.handle(process.argv.slice(2))
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const { Command } = require('@adonisjs/ace')
|
|
||||||
const Database = use('Database')
|
|
||||||
const Match = use('App/Models/Match')
|
|
||||||
|
|
||||||
class DeleteMatch extends Command {
|
|
||||||
static get signature() {
|
|
||||||
return `
|
|
||||||
delete:match
|
|
||||||
{ field : Field to check }
|
|
||||||
{ value?=null : Value of the field, if true: delete match }
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
static get description() {
|
|
||||||
return 'Delete matches from db with a condition'
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(args, options) {
|
|
||||||
console.time('DeleteMatches')
|
|
||||||
const nbMatchesBefore = await Match.count()
|
|
||||||
const matches = await Match.all()
|
|
||||||
for (const match of matches.toJSON()) {
|
|
||||||
await Match.where('_id', match._id).where(args.field, args.value).delete()
|
|
||||||
}
|
|
||||||
const nbMatchesAfter = await Match.count()
|
|
||||||
Database.close()
|
|
||||||
console.timeEnd('DeleteMatches')
|
|
||||||
this.success(`${this.icon('success')} Delete Matches completed: ${nbMatchesBefore - nbMatchesAfter} matche(s) deleted`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DeleteMatch
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const { Command } = require('@adonisjs/ace')
|
|
||||||
const DetailedMatchTransformer = use('App/Transformers/DetailedMatchTransformer')
|
|
||||||
const Database = use('Database')
|
|
||||||
const Jax = use('App/Services/Jax')
|
|
||||||
const DetailedMatch = use('App/Models/DetailedMatch')
|
|
||||||
const Queue = use('Bee/Queue')
|
|
||||||
|
|
||||||
class EditDetailedMatch extends Command {
|
|
||||||
static get signature() {
|
|
||||||
return `
|
|
||||||
edit:detailed:match
|
|
||||||
{ concurrent?=10 : Number of concurrent jobs }
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
static get description() {
|
|
||||||
return 'Edit DetailedMatches in the db with the new Transformer version'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create edit-detailed-matches Queue
|
|
||||||
*/
|
|
||||||
createQueue(concurrent) {
|
|
||||||
Queue.get('edit-detailed-matches').process(concurrent, async (job) => {
|
|
||||||
// Get stats from Riot API
|
|
||||||
const matchRiot = await Jax.Match.get(job.data.gameId, job.data.region)
|
|
||||||
// Transform raw matches data
|
|
||||||
const transformedMatch = await DetailedMatchTransformer.transform(matchRiot)
|
|
||||||
|
|
||||||
// Update match in DB
|
|
||||||
await DetailedMatch.where('_id', job.data._id).update(transformedMatch)
|
|
||||||
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job (edit detailed match) finished with success
|
|
||||||
*/
|
|
||||||
Queue.get('edit-detailed-matches').on('succeeded', (job, result) => {
|
|
||||||
console.log(`Job ${job.id} succeeded`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(args, options) {
|
|
||||||
console.time('EditDetailedMatches')
|
|
||||||
|
|
||||||
this.createQueue(args.concurrent)
|
|
||||||
|
|
||||||
// All detailed matches from the db
|
|
||||||
const matches = await DetailedMatch.all()
|
|
||||||
const matchesArray = matches.toJSON()
|
|
||||||
|
|
||||||
// Create jobs
|
|
||||||
const jobs = []
|
|
||||||
for (const match of matchesArray) {
|
|
||||||
const matchInfos = {
|
|
||||||
_id: match._id,
|
|
||||||
gameId: match.gameId,
|
|
||||||
region: match.region,
|
|
||||||
}
|
|
||||||
const job = await Queue
|
|
||||||
.get('edit-detailed-matches')
|
|
||||||
.createJob(matchInfos)
|
|
||||||
.save()
|
|
||||||
jobs.push(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait that all jobs are done
|
|
||||||
const finalResult = await new Promise((resolve, reject) => {
|
|
||||||
const lastJob = jobs[jobs.length - 1]
|
|
||||||
lastJob.on('succeeded', result => {
|
|
||||||
resolve(`FINAL RESULT for job ${lastJob.id}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Database.close()
|
|
||||||
console.timeEnd('EditDetailedMatches')
|
|
||||||
this.success(`${this.icon('success')} Edit ${matchesArray.length} DetailedMatches completed`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = EditDetailedMatch
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const { Command } = require('@adonisjs/ace')
|
|
||||||
const BasicMatchTransformer = use('App/Transformers/BasicMatchTransformer')
|
|
||||||
const Database = use('Database')
|
|
||||||
const Jax = use('App/Services/Jax')
|
|
||||||
const Match = use('App/Models/Match')
|
|
||||||
const Queue = use('Bee/Queue')
|
|
||||||
|
|
||||||
class EditMatch extends Command {
|
|
||||||
static get signature() {
|
|
||||||
return `
|
|
||||||
edit:match
|
|
||||||
{ concurrent?=10 : Number of concurrent jobs },
|
|
||||||
{ puuid? : Only update matches for a specific summoner },
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
static get description() {
|
|
||||||
return 'Edit matches in the db with the new Transformer version'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create edit-matches Queue
|
|
||||||
*/
|
|
||||||
createQueue(concurrent) {
|
|
||||||
Queue.get('edit-matches').process(concurrent, async (job) => {
|
|
||||||
// Get stats from Riot API
|
|
||||||
const matchRiot = await Jax.Match.get(job.data.gameId, job.data.region)
|
|
||||||
const account = {
|
|
||||||
accountId: (matchRiot.participantIdentities.find(s => s.player.summonerName === job.data.name)).player.currentAccountId,
|
|
||||||
puuid: job.data.summoner_puuid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform raw matches data
|
|
||||||
const transformedMatch = await BasicMatchTransformer.transform(matchRiot, { account })
|
|
||||||
|
|
||||||
// Update match in DB
|
|
||||||
await Match.where('_id', job.data._id).update(transformedMatch)
|
|
||||||
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job (edit match) finished with success
|
|
||||||
*/
|
|
||||||
Queue.get('edit-matches').on('succeeded', (job, result) => {
|
|
||||||
console.log(`Job ${job.id} succeeded`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(args, options) {
|
|
||||||
console.time('EditMatches')
|
|
||||||
|
|
||||||
this.info(`Concurrent: ${args.concurrent}`)
|
|
||||||
this.info(`PUUID: ${args.puuid}`)
|
|
||||||
this.createQueue(args.concurrent)
|
|
||||||
|
|
||||||
// All matches from the db
|
|
||||||
const matches = args.puuid ? await Match.query().where({ summoner_puuid: args.puuid }).fetch() : await Match.all()
|
|
||||||
const matchesArray = matches.toJSON()
|
|
||||||
console.log(`${matchesArray.length} matches to edit.`)
|
|
||||||
|
|
||||||
// Create jobs
|
|
||||||
const jobs = []
|
|
||||||
for (const match of matchesArray) {
|
|
||||||
const matchInfos = {
|
|
||||||
_id: match._id,
|
|
||||||
gameId: match.gameId,
|
|
||||||
name: match.name,
|
|
||||||
region: match.region,
|
|
||||||
summoner_puuid: match.summoner_puuid
|
|
||||||
}
|
|
||||||
const job = await Queue
|
|
||||||
.get('edit-matches')
|
|
||||||
.createJob(matchInfos)
|
|
||||||
.save()
|
|
||||||
jobs.push(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait that all jobs are done
|
|
||||||
const finalResult = await new Promise((resolve, reject) => {
|
|
||||||
const lastJob = jobs[jobs.length - 1]
|
|
||||||
lastJob.on('succeeded', result => {
|
|
||||||
resolve(`FINAL RESULT for job ${lastJob.id}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Database.close()
|
|
||||||
console.timeEnd('EditMatches')
|
|
||||||
this.success(`${this.icon('success')} Edit ${matchesArray.length} Matches completed`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = EditMatch
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const { Command } = require('@adonisjs/ace')
|
|
||||||
const Database = use('Database')
|
|
||||||
const Queue = use('Bee/Queue')
|
|
||||||
const Summoner = use('App/Models/Summoner')
|
|
||||||
const { getSeasonNumber } = use('App/helpers')
|
|
||||||
|
|
||||||
class EditMatchList extends Command {
|
|
||||||
static get signature() {
|
|
||||||
return 'edit:match:list'
|
|
||||||
}
|
|
||||||
|
|
||||||
static get description() {
|
|
||||||
return 'Edit all matchlist of players to add season of the matches'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create edit-matchList Queue
|
|
||||||
*/
|
|
||||||
createQueue(concurrent) {
|
|
||||||
Queue.get('edit-matchList').process(concurrent, async (job) => {
|
|
||||||
// Update matchlist with season
|
|
||||||
job.data.matchList = job.data.matchList.map(m => {
|
|
||||||
m.seasonMatch = getSeasonNumber(m.timestamp)
|
|
||||||
return m
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update Summoner in DB
|
|
||||||
await Summoner.where('puuid', job.data.puuid).update(job.data)
|
|
||||||
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job (edit matchList) finished with success
|
|
||||||
*/
|
|
||||||
Queue.get('edit-matchList').on('succeeded', (job, result) => {
|
|
||||||
console.log(`Job ${job.id} succeeded`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle(args, options) {
|
|
||||||
console.time('EditMatchList')
|
|
||||||
this.info('Start EditMatchList Command')
|
|
||||||
|
|
||||||
this.createQueue(10)
|
|
||||||
|
|
||||||
// All sumoners from the db
|
|
||||||
const summoners = await Summoner.all()
|
|
||||||
const summonersArray = summoners.toJSON()
|
|
||||||
|
|
||||||
// Create jobs
|
|
||||||
const jobs = []
|
|
||||||
for (const summoner of summonersArray) {
|
|
||||||
const job = await Queue
|
|
||||||
.get('edit-matchList')
|
|
||||||
.createJob(summoner)
|
|
||||||
.save()
|
|
||||||
jobs.push(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait that all jobs are done
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const lastJob = jobs[jobs.length - 1]
|
|
||||||
lastJob.on('succeeded', result => {
|
|
||||||
resolve(`FINAL RESULT for job ${lastJob.id}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Database.close()
|
|
||||||
console.timeEnd('EditMatchList')
|
|
||||||
this.success(`${this.icon('success')} Edit ${summonersArray.length} Summoners completed`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = EditMatchList
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const Jax = use('App/Services/Jax')
|
|
||||||
const DetailedMatch = use('App/Models/DetailedMatch')
|
|
||||||
const DetailedMatchTransformer = use('App/Transformers/DetailedMatchTransformer')
|
|
||||||
const MatchService = use('App/Services/MatchService')
|
|
||||||
const StatsService = use('App/Services/StatsService')
|
|
||||||
const SummonerService = use('App/Services/SummonerService')
|
|
||||||
const Summoner = use('App/Models/Summoner')
|
|
||||||
|
|
||||||
class MatchController {
|
|
||||||
/**
|
|
||||||
* Get the soloQ rank of all the players of the team
|
|
||||||
* @param summoner all the data of the summoner
|
|
||||||
* @param region of the match
|
|
||||||
*/
|
|
||||||
async _getPlayerRank(summoner, region) {
|
|
||||||
const account = await SummonerService.getAccount(summoner.name, 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
|
|
||||||
*/
|
|
||||||
async index({ request, response }) {
|
|
||||||
console.log('More Matches Request')
|
|
||||||
const account = request.input('account')
|
|
||||||
const gameIds = request.input('gameIds')
|
|
||||||
|
|
||||||
const summonerDB = await Summoner.where({ puuid: account.puuid }).first()
|
|
||||||
const matches = await MatchService.getMatches(account, gameIds, summonerDB)
|
|
||||||
|
|
||||||
await summonerDB.save()
|
|
||||||
|
|
||||||
const stats = await StatsService.getSummonerStats(account)
|
|
||||||
|
|
||||||
return response.json({
|
|
||||||
matches,
|
|
||||||
stats,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST - Return details data for one specific match
|
|
||||||
*/
|
|
||||||
async show({ request, response }) {
|
|
||||||
console.time('MatchDetails')
|
|
||||||
console.log('Match details request')
|
|
||||||
const gameId = request.input('gameId')
|
|
||||||
const region = request.input('region')
|
|
||||||
console.log(gameId, region)
|
|
||||||
|
|
||||||
let matchDetails = {}
|
|
||||||
const alreadySaved = await DetailedMatch.where({ gameId, region }).first()
|
|
||||||
if (alreadySaved) {
|
|
||||||
console.log('MATCH DETAILS ALREADY SAVED')
|
|
||||||
matchDetails = alreadySaved
|
|
||||||
} else {
|
|
||||||
matchDetails = await Jax.Match.get(gameId, region)
|
|
||||||
matchDetails = await DetailedMatchTransformer.transform(matchDetails)
|
|
||||||
await DetailedMatch.create(matchDetails)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.timeEnd('MatchDetails')
|
|
||||||
|
|
||||||
return response.json({
|
|
||||||
matchDetails
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST - Return ranks of players for a specific game
|
|
||||||
*/
|
|
||||||
async showRanks({ request, response }) {
|
|
||||||
console.time('Ranks')
|
|
||||||
const gameId = request.input('gameId')
|
|
||||||
const region = request.input('region')
|
|
||||||
|
|
||||||
let matchDetails = await DetailedMatch.where({ gameId, region }).first()
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MatchController
|
|
||||||
112
server/app/Controllers/Http/MatchesController.ts
Normal file
112
server/app/Controllers/Http/MatchesController.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import mongodb from '@ioc:Mongodb/Database'
|
||||||
|
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 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
|
||||||
|
*/
|
||||||
|
private async getPlayerRank (summoner: ParticipantDetails, region: string) {
|
||||||
|
const account = await SummonerService.getAccount(summoner.name, 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 } = await request.validate(MatchesIndexValidator)
|
||||||
|
|
||||||
|
const summonerDB = await Summoner.findOne({ puuid })
|
||||||
|
if (!summonerDB) {
|
||||||
|
return response.json(null)
|
||||||
|
}
|
||||||
|
const matches = await MatchService.getMatches(puuid, accountId, region, gameIds, summonerDB)
|
||||||
|
|
||||||
|
await summonerDB.save()
|
||||||
|
|
||||||
|
const stats = await StatsService.getSummonerStats(puuid)
|
||||||
|
|
||||||
|
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 { gameId, region } = await request.validate(DetailedMatchValidator)
|
||||||
|
|
||||||
|
let matchDetails: DetailedMatchModel
|
||||||
|
// TODO: replace it with Match Model once the package is fixed
|
||||||
|
const detailedMatchesCollection = await mongodb.connection().collection('detailed_matches')
|
||||||
|
const alreadySaved = await detailedMatchesCollection.findOne<DetailedMatchModel>({ gameId, region })
|
||||||
|
if (alreadySaved) {
|
||||||
|
console.log('MATCH DETAILS ALREADY SAVED')
|
||||||
|
matchDetails = alreadySaved
|
||||||
|
} else {
|
||||||
|
const match = await Jax.Match.get(gameId, region)
|
||||||
|
matchDetails = await DetailedMatchTransformer.transform(match)
|
||||||
|
await DetailedMatch.create(matchDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.timeEnd('MatchDetails')
|
||||||
|
|
||||||
|
return response.json({
|
||||||
|
matchDetails,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - Return ranks of players for a specific game
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public async showRanks ({ request, response }: HttpContextContract) {
|
||||||
|
console.time('Ranks')
|
||||||
|
const { gameId, region } = await request.validate(DetailedMatchValidator)
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const Jax = use('App/Services/Jax')
|
|
||||||
const LiveMatchTransformer = use('App/Transformers/LiveMatchTransformer')
|
|
||||||
const MatchRepository = make('App/Repositories/MatchRepository')
|
|
||||||
const MatchService = use('App/Services/MatchService')
|
|
||||||
const SummonerService = use('App/Services/SummonerService')
|
|
||||||
const StatsService = use('App/Services/StatsService')
|
|
||||||
const Summoner = use('App/Models/Summoner')
|
|
||||||
|
|
||||||
class SummonerController {
|
|
||||||
async _getSeasons(puuid) {
|
|
||||||
let seasons = await MatchRepository.seasons(puuid)
|
|
||||||
return seasons.length ? seasons.map(s => s._id) : [10]
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* POST: get basic summoner data
|
|
||||||
*/
|
|
||||||
async basic({ request, response }) {
|
|
||||||
console.time('all')
|
|
||||||
const summoner = request.input('summoner')
|
|
||||||
const region = request.input('region')
|
|
||||||
console.log(summoner, region)
|
|
||||||
|
|
||||||
const regexSummonerName = new RegExp('^[0-9\\p{L} _\\.]+$', 'u')
|
|
||||||
if (!regexSummonerName.exec(summoner)) {
|
|
||||||
return response.json(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalJSON = {}
|
|
||||||
Jax.regionName = region
|
|
||||||
|
|
||||||
try {
|
|
||||||
const account = await SummonerService.getAccount(summoner, region)
|
|
||||||
// Check if the summoner is found
|
|
||||||
if (!account) return response.json(null)
|
|
||||||
account.region = region
|
|
||||||
finalJSON.account = account
|
|
||||||
|
|
||||||
// Summoner in DB
|
|
||||||
const summonerDB = await Summoner.findOrCreate(
|
|
||||||
{ puuid: account.puuid },
|
|
||||||
{ puuid: account.puuid }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Summoner names
|
|
||||||
finalJSON.account.names = SummonerService.getAllSummonerNames(account, summonerDB)
|
|
||||||
|
|
||||||
// MATCH LIST
|
|
||||||
await MatchService.updateMatchList(account, summonerDB)
|
|
||||||
finalJSON.matchList = summonerDB.matchList
|
|
||||||
|
|
||||||
// All seasons the summoner has played
|
|
||||||
finalJSON.seasons = await this._getSeasons(account.puuid)
|
|
||||||
|
|
||||||
// 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, region)
|
|
||||||
|
|
||||||
// SAVE IN DB
|
|
||||||
await summonerDB.save()
|
|
||||||
} catch (error) {
|
|
||||||
console.log('username not found')
|
|
||||||
console.log(error)
|
|
||||||
return response.json(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.timeEnd('all')
|
|
||||||
return response.json(finalJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST: get overview view summoner data
|
|
||||||
*/
|
|
||||||
async overview({ request, response }) {
|
|
||||||
console.time('overview')
|
|
||||||
const account = request.input('account')
|
|
||||||
const season = request.input('season')
|
|
||||||
const finalJSON = {}
|
|
||||||
|
|
||||||
// Summoner in DB
|
|
||||||
const summonerDB = await Summoner.findOrCreate(
|
|
||||||
{ puuid: account.puuid },
|
|
||||||
{ puuid: account.puuid }
|
|
||||||
)
|
|
||||||
|
|
||||||
// MATCHES BASIC
|
|
||||||
const gameIds = summonerDB.matchList.slice(0)
|
|
||||||
.filter(m => {
|
|
||||||
return season ? m.seasonMatch === season : true
|
|
||||||
})
|
|
||||||
.slice(0, 10)
|
|
||||||
.map(({ gameId }) => gameId)
|
|
||||||
finalJSON.matchesDetails = await MatchService.getMatches(account, gameIds, summonerDB)
|
|
||||||
|
|
||||||
// STATS
|
|
||||||
console.time('STATS')
|
|
||||||
finalJSON.stats = await StatsService.getSummonerStats(account, season)
|
|
||||||
console.timeEnd('STATS')
|
|
||||||
|
|
||||||
// SAVE IN DB
|
|
||||||
await summonerDB.save()
|
|
||||||
|
|
||||||
console.timeEnd('overview')
|
|
||||||
return response.json(finalJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST: get champions view summoner data
|
|
||||||
*/
|
|
||||||
async champions({ request, response }) {
|
|
||||||
const puuid = request.input('puuid')
|
|
||||||
const queue = request.input('queue')
|
|
||||||
const season = request.input('season')
|
|
||||||
console.time('championsRequest')
|
|
||||||
const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
|
|
||||||
console.timeEnd('championsRequest')
|
|
||||||
return response.json(championStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST: get records view summoner data
|
|
||||||
*/
|
|
||||||
async records({ request, response }) {
|
|
||||||
const puuid = request.input('puuid')
|
|
||||||
const season = request.input('season')
|
|
||||||
console.time('recordsRequest')
|
|
||||||
const records = await MatchRepository.records(puuid, season)
|
|
||||||
console.timeEnd('recordsRequest')
|
|
||||||
return response.json(records[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST - Return live match details
|
|
||||||
*/
|
|
||||||
async liveMatchDetails({ request, response }) {
|
|
||||||
console.time('liveMatchDetails')
|
|
||||||
const account = request.input('account')
|
|
||||||
const region = request.input('region')
|
|
||||||
|
|
||||||
// CURRENT GAME
|
|
||||||
let currentGame = await Jax.Spectator.summonerID(account.id, region)
|
|
||||||
|
|
||||||
if (!currentGame) {
|
|
||||||
return response.json(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentGame = await LiveMatchTransformer.transform(currentGame, { region })
|
|
||||||
console.timeEnd('liveMatchDetails')
|
|
||||||
|
|
||||||
return response.json(currentGame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SummonerController
|
|
||||||
159
server/app/Controllers/Http/SummonersController.ts
Normal file
159
server/app/Controllers/Http/SummonersController.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import Summoner from 'App/Models/Summoner'
|
||||||
|
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||||
|
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'
|
||||||
|
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) : [10]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST: get basic summoner data
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public async basic ({ request, response }: HttpContextContract) {
|
||||||
|
console.time('all')
|
||||||
|
const { summoner, region } = await request.validate(SummonerBasicValidator)
|
||||||
|
const finalJSON: any = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const account = await SummonerService.getAccount(summoner, region)
|
||||||
|
// Check if the summoner is found
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summoner names
|
||||||
|
finalJSON.account.names = SummonerService.getAllSummonerNames(account, summonerDB)
|
||||||
|
|
||||||
|
// MATCH LIST
|
||||||
|
await MatchService.updateMatchList(account, summonerDB)
|
||||||
|
finalJSON.matchList = summonerDB.matchList
|
||||||
|
|
||||||
|
// All seasons the summoner has played
|
||||||
|
finalJSON.seasons = await this.getSeasons(account.puuid)
|
||||||
|
|
||||||
|
// 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, region)
|
||||||
|
|
||||||
|
// SAVE IN DB
|
||||||
|
await summonerDB.save()
|
||||||
|
} catch (error) {
|
||||||
|
console.log('username not found')
|
||||||
|
console.log(error)
|
||||||
|
return response.json(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.timeEnd('all')
|
||||||
|
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)
|
||||||
|
const finalJSON: any = {}
|
||||||
|
|
||||||
|
// Summoner in DB
|
||||||
|
let summonerDB = await Summoner.findOne({ puuid: puuid })
|
||||||
|
if (!summonerDB) {
|
||||||
|
summonerDB = await Summoner.create({ puuid: puuid })
|
||||||
|
}
|
||||||
|
|
||||||
|
// MATCHES BASIC
|
||||||
|
const gameIds = summonerDB.matchList!.slice(0)
|
||||||
|
.filter(m => {
|
||||||
|
return season ? m.seasonMatch === season : true
|
||||||
|
})
|
||||||
|
.slice(0, 10)
|
||||||
|
.map(({ gameId }) => gameId)
|
||||||
|
finalJSON.matchesDetails = await MatchService.getMatches(puuid, accountId, region, gameIds, summonerDB)
|
||||||
|
|
||||||
|
// STATS
|
||||||
|
console.time('STATS')
|
||||||
|
finalJSON.stats = await StatsService.getSummonerStats(puuid, season)
|
||||||
|
console.timeEnd('STATS')
|
||||||
|
|
||||||
|
// SAVE IN DB
|
||||||
|
await summonerDB.save()
|
||||||
|
|
||||||
|
console.timeEnd('overview')
|
||||||
|
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)
|
||||||
|
console.timeEnd('championsRequest')
|
||||||
|
return response.json(championStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, season)
|
||||||
|
console.timeEnd('recordsRequest')
|
||||||
|
return response.json(records)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
let currentGame = await Jax.Spectator.summonerID(id, region)
|
||||||
|
|
||||||
|
if (!currentGame) {
|
||||||
|
return response.json(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentGame = await LiveMatchTransformer.transform(currentGame, { region })
|
||||||
|
console.timeEnd('liveMatchDetails')
|
||||||
|
|
||||||
|
return response.json(currentGame)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
server/app/Exceptions/Handler.ts
Normal file
23
server/app/Exceptions/Handler.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
class ConvertEmptyStringsToNull {
|
|
||||||
async handle ({ request }, next) {
|
|
||||||
if (Object.keys(request.body).length) {
|
|
||||||
request.body = Object.assign(
|
|
||||||
...Object.keys(request.body).map(key => ({
|
|
||||||
[key]: request.body[key] !== '' ? request.body[key] : null
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ConvertEmptyStringsToNull
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
|
|
||||||
const Model = use('Model')
|
|
||||||
|
|
||||||
class DetailedMatch extends Model {
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DetailedMatch
|
|
||||||
57
server/app/Models/DetailedMatch.ts
Normal file
57
server/app/Models/DetailedMatch.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Model } from '@ioc:Mongodb/Model'
|
||||||
|
import { Champion, ParticipantDetails } from 'App/Models/Match'
|
||||||
|
|
||||||
|
export interface DetailedMatchModel {
|
||||||
|
gameId: number,
|
||||||
|
season: number,
|
||||||
|
blueTeam: Team,
|
||||||
|
redTeam: Team,
|
||||||
|
map: number,
|
||||||
|
gamemode: number,
|
||||||
|
date: number,
|
||||||
|
region: string,
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Team {
|
||||||
|
bans: Ban[],
|
||||||
|
barons: number,
|
||||||
|
color: string,
|
||||||
|
dragons: number,
|
||||||
|
inhibitors: number,
|
||||||
|
players: ParticipantDetails[],
|
||||||
|
result: string,
|
||||||
|
riftHerald: number,
|
||||||
|
teamStats: TeamStats,
|
||||||
|
towers: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Ban {
|
||||||
|
championId: number,
|
||||||
|
pickTurn: number,
|
||||||
|
champion: Champion<null | number, null | string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TeamStats {
|
||||||
|
kills: number,
|
||||||
|
deaths: number,
|
||||||
|
assists: number,
|
||||||
|
gold: number,
|
||||||
|
dmgChamp: number,
|
||||||
|
dmgObj: number,
|
||||||
|
dmgTaken: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DetailedMatch extends Model implements DetailedMatchModel {
|
||||||
|
public static collectionName = 'detailed_matches'
|
||||||
|
|
||||||
|
public gameId: number
|
||||||
|
public season: number
|
||||||
|
public blueTeam: Team
|
||||||
|
public redTeam: Team
|
||||||
|
public map: number
|
||||||
|
public gamemode: number
|
||||||
|
public date: number
|
||||||
|
public region: string
|
||||||
|
public time: number
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
|
|
||||||
const Model = use('Model')
|
|
||||||
|
|
||||||
class Match extends Model {
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Match
|
|
||||||
127
server/app/Models/Match.ts
Normal file
127
server/app/Models/Match.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import { Model } from '@ioc:Mongodb/Model'
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ParticipantBasic {
|
||||||
|
account_id: string,
|
||||||
|
name: string,
|
||||||
|
role: string,
|
||||||
|
champion: Champion
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
image: string,
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
price: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stats {
|
||||||
|
kills: number,
|
||||||
|
deaths: number,
|
||||||
|
assists: number,
|
||||||
|
minions: number,
|
||||||
|
vision: number,
|
||||||
|
gold: number,
|
||||||
|
dmgChamp: number,
|
||||||
|
dmgObj: number,
|
||||||
|
dmgTaken: number,
|
||||||
|
kda: number | string,
|
||||||
|
realKda: number,
|
||||||
|
criticalStrike?: number,
|
||||||
|
killingSpree?: number,
|
||||||
|
doubleKills?: number,
|
||||||
|
tripleKills?: number,
|
||||||
|
quadraKills?: number,
|
||||||
|
pentaKills?: number,
|
||||||
|
heal?: number,
|
||||||
|
towers?: number,
|
||||||
|
longestLiving?: number,
|
||||||
|
kp: number | string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PercentStats {
|
||||||
|
minions: number,
|
||||||
|
vision: number,
|
||||||
|
gold: string,
|
||||||
|
dmgChamp: string,
|
||||||
|
dmgObj: string,
|
||||||
|
dmgTaken: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Match extends Model implements MatchModel {
|
||||||
|
public static collectionName = 'matches'
|
||||||
|
|
||||||
|
public account_id: string
|
||||||
|
public summoner_puuid: string
|
||||||
|
public gameId: number
|
||||||
|
public 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
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
|
|
||||||
const Model = use('Model')
|
|
||||||
|
|
||||||
class Summoner extends Model {
|
|
||||||
matches() {
|
|
||||||
return this.hasMany('App/Models/Match', 'puuid', 'summoner_puuid')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Summoner
|
|
||||||
21
server/app/Models/Summoner.ts
Normal file
21
server/app/Models/Summoner.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Model } from '@ioc:Mongodb/Model'
|
||||||
|
import { MatchReferenceDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint'
|
||||||
|
|
||||||
|
export interface SummonerModel {
|
||||||
|
puuid: string,
|
||||||
|
matchList?: MatchReferenceDto[],
|
||||||
|
names?: SummonerNames[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SummonerNames {
|
||||||
|
name: string,
|
||||||
|
date: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Summoner extends Model implements SummonerModel {
|
||||||
|
public static collectionName = 'summoners'
|
||||||
|
|
||||||
|
public puuid: string
|
||||||
|
public matchList?: MatchReferenceDto[]
|
||||||
|
public names?: SummonerNames[]
|
||||||
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
class NoTimestamp {
|
|
||||||
register (Model) {
|
|
||||||
Object.defineProperties(Model, {
|
|
||||||
createdAtColumn: {
|
|
||||||
get: () => null,
|
|
||||||
},
|
|
||||||
updatedAtColumn: {
|
|
||||||
get: () => null,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = NoTimestamp
|
|
||||||
|
|
@ -1,43 +1,50 @@
|
||||||
'use strict'
|
import mongodb from '@ioc:Mongodb/Database'
|
||||||
|
import { Collection } from 'mongodb'
|
||||||
|
|
||||||
class MatchRepository {
|
class MatchRepository {
|
||||||
static get inject() {
|
private collection: Collection
|
||||||
return ['App/Models/Match']
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(Match) {
|
constructor () {
|
||||||
this.Match = Match
|
this.getCollection()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic matchParams used in a lot of requests
|
* Basic matchParams used in a lot of requests
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
*/
|
*/
|
||||||
_matchParams(puuid) {
|
private matchParams (puuid: string, season?: number) {
|
||||||
return {
|
return {
|
||||||
summoner_puuid: puuid,
|
summoner_puuid: puuid,
|
||||||
result: { $not: { $eq: 'Remake' } },
|
result: { $not: { $eq: 'Remake' } },
|
||||||
gamemode: { $nin: [800, 810, 820, 830, 840, 850] },
|
gamemode: { $nin: [800, 810, 820, 830, 840, 850] },
|
||||||
season: this.season ? this.season : { $exists: true }
|
season: season ? season : { $exists: true },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the aggregate mongo query
|
* Build the aggregate mongo query
|
||||||
* @param {Number} puuid
|
* @param puuid
|
||||||
* @param {Object} matchParams
|
* @param matchParams
|
||||||
* @param {Array} intermediateSteps
|
* @param intermediateSteps
|
||||||
* @param {*} groupId
|
* @param groupId
|
||||||
* @param {Object} groupParams
|
* @param groupParams
|
||||||
* @param {Array} finalSteps
|
* @param finalSteps
|
||||||
*/
|
*/
|
||||||
_aggregate(puuid, matchParams, intermediateSteps, groupId, groupParams, finalSteps) {
|
private async aggregate (
|
||||||
return this.Match.query().aggregate([
|
puuid: string,
|
||||||
|
matchParams: object,
|
||||||
|
intermediateSteps: any[],
|
||||||
|
groupId: any,
|
||||||
|
groupParams: object,
|
||||||
|
finalSteps: any[],
|
||||||
|
season?: number,
|
||||||
|
) {
|
||||||
|
return this.collection.aggregate([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
...this._matchParams(puuid),
|
...this.matchParams(puuid, season),
|
||||||
...matchParams
|
...matchParams,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
...intermediateSteps,
|
...intermediateSteps,
|
||||||
{
|
{
|
||||||
|
|
@ -46,27 +53,37 @@ class MatchRepository {
|
||||||
count: { $sum: 1 },
|
count: { $sum: 1 },
|
||||||
wins: {
|
wins: {
|
||||||
$sum: {
|
$sum: {
|
||||||
$cond: [{ $eq: ['$result', 'Win'] }, 1, 0]
|
$cond: [{ $eq: ['$result', 'Win'] }, 1, 0],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
losses: {
|
losses: {
|
||||||
$sum: {
|
$sum: {
|
||||||
$cond: [{ $eq: ['$result', 'Fail'] }, 1, 0]
|
$cond: [{ $eq: ['$result', 'Fail'] }, 1, 0],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
...groupParams
|
...groupParams,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...finalSteps
|
...finalSteps,
|
||||||
])
|
]).toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get MongoDB matches collection
|
||||||
|
*/
|
||||||
|
public async getCollection () {
|
||||||
|
if (!this.collection) {
|
||||||
|
this.collection = await mongodb.connection().collection('matches')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's statistics for the N most played champions
|
* Get Summoner's statistics for the N most played champions
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
* @param limit number of champions to fetch
|
* @param limit number of champions to fetch
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
championStats(puuid, limit = 5) {
|
public async championStats (puuid: string, limit = 5, season?: number) {
|
||||||
const groupParams = {
|
const groupParams = {
|
||||||
champion: { $first: '$champion' },
|
champion: { $first: '$champion' },
|
||||||
kills: { $sum: '$stats.kills' },
|
kills: { $sum: '$stats.kills' },
|
||||||
|
|
@ -75,33 +92,29 @@ class MatchRepository {
|
||||||
}
|
}
|
||||||
const finalSteps = [
|
const finalSteps = [
|
||||||
{ $sort: { 'count': -1, 'champion.name': 1 } },
|
{ $sort: { 'count': -1, 'champion.name': 1 } },
|
||||||
{ $limit: limit }
|
{ $limit: limit },
|
||||||
]
|
]
|
||||||
return this._aggregate(puuid, {}, [], '$champion.id', groupParams, finalSteps)
|
return this.aggregate(puuid, {}, [], '$champion.id', groupParams, finalSteps, season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's statistics for all played champion classes
|
* Get Summoner's statistics for all played champion classes
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
championClassStats(puuid) {
|
public async championClassStats (puuid: string, season?: number) {
|
||||||
const groupId = { '$arrayElemAt': ['$champion.roles', 0] }
|
const groupId = { '$arrayElemAt': ['$champion.roles', 0] }
|
||||||
return this._aggregate(puuid, {}, [], groupId, {}, [])
|
return this.aggregate(puuid, {}, [], groupId, {}, [], season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's complete statistics for the all played champs
|
* Get Summoner's complete statistics for the all played champs
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
* @param queue of the matches to fetch, if null get all matches
|
* @param queue of the matches to fetch, if not set: get all matches
|
||||||
* @param season of the matches to fetch, if null get all seasons
|
* @param season of the matches to fetch, if not set: get all seasons
|
||||||
*/
|
*/
|
||||||
championCompleteStats(puuid, queue, season) {
|
public async championCompleteStats (puuid: string, queue?: number, season?: number) {
|
||||||
const matchParams = {}
|
const matchParams = queue ? { gamemode: { $eq: Number(queue) } } : {}
|
||||||
if (queue) {
|
|
||||||
matchParams.gamemode = { $eq: Number(queue) }
|
|
||||||
}
|
|
||||||
this.season = season
|
|
||||||
|
|
||||||
const groupParams = {
|
const groupParams = {
|
||||||
time: { $sum: '$time' },
|
time: { $sum: '$time' },
|
||||||
gameLength: { $avg: '$time' },
|
gameLength: { $avg: '$time' },
|
||||||
|
|
@ -117,24 +130,26 @@ class MatchRepository {
|
||||||
kp: { $avg: '$stats.kp' },
|
kp: { $avg: '$stats.kp' },
|
||||||
}
|
}
|
||||||
const finalSteps = [
|
const finalSteps = [
|
||||||
{ $sort: { 'count': -1, 'champion.name': 1 } }
|
{ $sort: { 'count': -1, 'champion.name': 1 } },
|
||||||
]
|
]
|
||||||
return this._aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps)
|
return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's statistics for all played modes
|
* Get Summoner's statistics for all played modes
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
gamemodeStats(puuid) {
|
public async gamemodeStats (puuid: string, season?: number) {
|
||||||
return this._aggregate(puuid, {}, [], '$gamemode', {}, [])
|
return this.aggregate(puuid, {}, [], '$gamemode', {}, [], season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get global Summoner's statistics
|
* Get global Summoner's statistics
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
globalStats(puuid) {
|
public async globalStats (puuid: string, season?: number) {
|
||||||
const groupParams = {
|
const groupParams = {
|
||||||
time: { $sum: '$time' },
|
time: { $sum: '$time' },
|
||||||
kills: { $sum: '$stats.kills' },
|
kills: { $sum: '$stats.kills' },
|
||||||
|
|
@ -144,7 +159,7 @@ class MatchRepository {
|
||||||
vision: { $sum: '$stats.vision' },
|
vision: { $sum: '$stats.vision' },
|
||||||
kp: { $avg: '$stats.kp' },
|
kp: { $avg: '$stats.kp' },
|
||||||
}
|
}
|
||||||
return this._aggregate(puuid, {}, [], null, groupParams, [])
|
return this.aggregate(puuid, {}, [], null, groupParams, [], season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -152,14 +167,12 @@ class MatchRepository {
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
* @param season of the matches to fetch, if null get all seasons
|
* @param season of the matches to fetch, if null get all seasons
|
||||||
*/
|
*/
|
||||||
records(puuid, season) {
|
public async records (puuid: string, season?: number) {
|
||||||
this.season = season
|
const records = await this.collection.aggregate([
|
||||||
|
|
||||||
return this.Match.query().aggregate([
|
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
...this._matchParams(puuid),
|
...this.matchParams(puuid, season),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$group: {
|
$group: {
|
||||||
|
|
@ -213,13 +226,14 @@ class MatchRepository {
|
||||||
'result': '$result',
|
'result': '$result',
|
||||||
'date': '$date',
|
'date': '$date',
|
||||||
'gamemode': '$gamemode',
|
'gamemode': '$gamemode',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$project: {
|
$project: {
|
||||||
_id: 0,
|
_id: 0,
|
||||||
|
/* eslint-disable max-len */
|
||||||
maxKills: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kills', '$maxKills'] } } }, 0] },
|
maxKills: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kills', '$maxKills'] } } }, 0] },
|
||||||
maxDeaths: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.deaths', '$maxDeaths'] } } }, 0] },
|
maxDeaths: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.deaths', '$maxDeaths'] } } }, 0] },
|
||||||
maxAssists: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.assists', '$maxAssists'] } } }, 0] },
|
maxAssists: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.assists', '$maxAssists'] } } }, 0] },
|
||||||
|
|
@ -241,18 +255,21 @@ class MatchRepository {
|
||||||
maxTriple: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.tripleKills', '$maxTriple'] } } }, 0] },
|
maxTriple: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.tripleKills', '$maxTriple'] } } }, 0] },
|
||||||
maxQuadra: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.quadraKills', '$maxQuadra'] } } }, 0] },
|
maxQuadra: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.quadraKills', '$maxQuadra'] } } }, 0] },
|
||||||
maxPenta: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.pentaKills', '$maxPenta'] } } }, 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
|
* Get Summoner's statistics for the 5 differnt roles
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
roleStats(puuid) {
|
public async roleStats (puuid: string, season?: number) {
|
||||||
const matchParams = {
|
const matchParams = {
|
||||||
role: { $not: { $eq: 'NONE' } }
|
role: { $not: { $eq: 'NONE' } },
|
||||||
}
|
}
|
||||||
const finalSteps = [
|
const finalSteps = [
|
||||||
{
|
{
|
||||||
|
|
@ -261,36 +278,35 @@ class MatchRepository {
|
||||||
count: '$count',
|
count: '$count',
|
||||||
wins: '$wins',
|
wins: '$wins',
|
||||||
losses: '$losses',
|
losses: '$losses',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
return this._aggregate(puuid, matchParams, [], '$role', {}, finalSteps)
|
return this.aggregate(puuid, matchParams, [], '$role', {}, finalSteps, season)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's played seasons
|
* Get Summoner's played seasons
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
*/
|
*/
|
||||||
seasons(puuid) {
|
public async seasons (puuid: string) {
|
||||||
this.season = null
|
return this.collection.aggregate([
|
||||||
|
|
||||||
return this.Match.query().aggregate([
|
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
...this._matchParams(puuid),
|
...this.matchParams(puuid),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$group: { _id: '$season' }
|
$group: { _id: '$season' },
|
||||||
},
|
},
|
||||||
])
|
]).toArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Summoner's mates list
|
* Get Summoner's mates list
|
||||||
* @param puuid of the summoner
|
* @param puuid of the summoner
|
||||||
|
* @param season
|
||||||
*/
|
*/
|
||||||
mates(puuid) {
|
public async mates (puuid: string, season?: number) {
|
||||||
const intermediateSteps = [
|
const intermediateSteps = [
|
||||||
{ $sort: { 'gameId': -1 } },
|
{ $sort: { 'gameId': -1 } },
|
||||||
{ $unwind: '$allyTeam' },
|
{ $unwind: '$allyTeam' },
|
||||||
|
|
@ -303,20 +319,20 @@ class MatchRepository {
|
||||||
const finalSteps = [
|
const finalSteps = [
|
||||||
{
|
{
|
||||||
'$addFields': {
|
'$addFields': {
|
||||||
'idEq': { '$eq': ['$mateId', '$account_id'] }
|
'idEq': { '$eq': ['$mateId', '$account_id'] },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
'idEq': false,
|
'idEq': false,
|
||||||
'count': { $gte: 2 }
|
'count': { $gte: 2 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ $sort: { 'count': -1, 'name': 1 } },
|
{ $sort: { 'count': -1, 'name': 1 } },
|
||||||
{ $limit: 15 },
|
{ $limit: 15 },
|
||||||
]
|
]
|
||||||
return this._aggregate(puuid, {}, intermediateSteps, '$allyTeam.account_id', groupParams, finalSteps)
|
return this.aggregate(puuid, {}, intermediateSteps, '$allyTeam.account_id', groupParams, finalSteps, season)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MatchRepository
|
export default new MatchRepository()
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
require('dotenv').config()
|
|
||||||
const { STRATEGY } = require('riot-ratelimiter/dist/RateLimiter')
|
|
||||||
|
|
||||||
const JAX_CONFIG = {
|
|
||||||
key: process.env.API_KEY,
|
|
||||||
region: 'euw1',
|
|
||||||
requestOptions: {
|
|
||||||
retriesBeforeAbort: 3,
|
|
||||||
delayBeforeRetry: 1000,
|
|
||||||
// strategy: process.env.NODE_ENV === 'production' ? STRATEGY.SPREAD : STRATEGY.BURST,
|
|
||||||
strategy: STRATEGY.SPREAD,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = JAX_CONFIG
|
|
||||||
21
server/app/Services/Jax/JaxConfig.ts
Normal file
21
server/app/Services/Jax/JaxConfig.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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.getOrFail('API_KEY') as string,
|
||||||
|
region: 'euw1',
|
||||||
|
requestOptions: {
|
||||||
|
retriesBeforeAbort: 3,
|
||||||
|
delayBeforeRetry: 1000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
const Jax = require('./src/Jax')
|
|
||||||
const Config = require('./JaxConfig')
|
|
||||||
|
|
||||||
module.exports = new Jax(Config)
|
|
||||||
4
server/app/Services/Jax/index.ts
Normal file
4
server/app/Services/Jax/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Jax from './src/Jax'
|
||||||
|
import { JAX_CONFIG } from './JaxConfig'
|
||||||
|
|
||||||
|
export = new Jax(JAX_CONFIG)
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
const { promisify } = require('util')
|
import { promisify } from 'util'
|
||||||
const got = require('got')
|
import got from 'got'
|
||||||
const Logger = use('Logger')
|
import Logger from '@ioc:Adonis/Core/Logger'
|
||||||
const Redis = use('Redis')
|
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||||
|
import { JaxConfig } from '../JaxConfig'
|
||||||
|
|
||||||
class CDragonRequest {
|
export default class CDragonRequest {
|
||||||
constructor(config, endpoint, cacheTime) {
|
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.config = config
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.cacheTime = cacheTime
|
this.cacheTime = cacheTime
|
||||||
|
|
@ -17,8 +24,9 @@ class CDragonRequest {
|
||||||
// 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/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/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/champion-summary.json
|
||||||
|
// https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json
|
||||||
|
|
||||||
async execute() {
|
public async execute () {
|
||||||
const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}`
|
const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}`
|
||||||
|
|
||||||
const requestCached = await Redis.get(url)
|
const requestCached = await Redis.get(url)
|
||||||
|
|
@ -27,14 +35,14 @@ class CDragonRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await got(url);
|
const response = await got(url)
|
||||||
|
|
||||||
await Redis.set(url, response.body, 'EX', this.cacheTime)
|
await Redis.set(url, response.body, 'EX', this.cacheTime)
|
||||||
return JSON.parse(response.body)
|
return JSON.parse(response.body)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.retries--
|
this.retries--
|
||||||
|
|
||||||
Logger.transport('file').error('CDragon Error : ', error)
|
Logger.error('CDragon Error : ', error)
|
||||||
|
|
||||||
if (this.retries > 0) {
|
if (this.retries > 0) {
|
||||||
await this.sleep(this.config.requestOptions.delayBeforeRetry)
|
await this.sleep(this.config.requestOptions.delayBeforeRetry)
|
||||||
|
|
@ -42,7 +50,4 @@ class CDragonRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CDragonRequest
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
const CDragonRequest = require('../CDragonRequest')
|
|
||||||
|
|
||||||
class CDragonEndpoint {
|
|
||||||
constructor(config) {
|
|
||||||
this.config = config
|
|
||||||
}
|
|
||||||
|
|
||||||
champions() {
|
|
||||||
return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
items() {
|
|
||||||
return new CDragonRequest(this.config, 'items.json', 36000).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
perks() {
|
|
||||||
return new CDragonRequest(this.config, 'perks.json', 36000).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
perkstyles() {
|
|
||||||
return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
summonerSpells() {
|
|
||||||
return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CDragonEndpoint
|
|
||||||
104
server/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts
Normal file
104
server/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const JaxRequest = require('../JaxRequest')
|
|
||||||
|
|
||||||
class LeagueEndpoint {
|
|
||||||
constructor(config, limiter) {
|
|
||||||
this.config = config
|
|
||||||
this.limiter = limiter
|
|
||||||
}
|
|
||||||
|
|
||||||
summonerID(summonerID, region) {
|
|
||||||
return new JaxRequest(
|
|
||||||
region,
|
|
||||||
this.config,
|
|
||||||
`league/v4/entries/by-summoner/${summonerID}`,
|
|
||||||
this.limiter,
|
|
||||||
300
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = LeagueEndpoint
|
|
||||||
48
server/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts
Normal file
48
server/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
const JaxRequest = require('../JaxRequest')
|
|
||||||
|
|
||||||
class MatchEndpoint {
|
|
||||||
constructor(config, limiter) {
|
|
||||||
this.config = config
|
|
||||||
this.limiter = limiter
|
|
||||||
|
|
||||||
this.get = this.get.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
get(matchID, region) {
|
|
||||||
return new JaxRequest(
|
|
||||||
region,
|
|
||||||
this.config,
|
|
||||||
`match/v4/matches/${matchID}`,
|
|
||||||
this.limiter,
|
|
||||||
1500
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MatchEndpoint
|
|
||||||
233
server/app/Services/Jax/src/Endpoints/MatchEndpoint.ts
Normal file
233
server/app/Services/Jax/src/Endpoints/MatchEndpoint.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: number, region: string): Promise<MatchDto> {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`match/v4/matches/${matchID}`,
|
||||||
|
this.limiter,
|
||||||
|
1500
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
43
server/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts
Normal file
43
server/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter'
|
||||||
|
import RiotRateLimiter from 'riot-ratelimiter'
|
||||||
|
import { JaxConfig } from '../../JaxConfig'
|
||||||
|
import JaxRequest from '../JaxRequest'
|
||||||
|
|
||||||
|
export interface 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
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MatchlistEndpoint {
|
||||||
|
private config: JaxConfig
|
||||||
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
|
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
|
this.config = config
|
||||||
|
this.limiter = limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
public accountID (accountID: string, region: string, beginIndex = 0): Promise<MatchlistDto> {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`,
|
||||||
|
this.limiter,
|
||||||
|
0
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const JaxRequest = require('../JaxRequest')
|
|
||||||
|
|
||||||
class MatchlistEndpoint {
|
|
||||||
constructor(config, limiter) {
|
|
||||||
this.config = config
|
|
||||||
this.limiter = limiter
|
|
||||||
}
|
|
||||||
|
|
||||||
accountID(accountID, region, beginIndex = 0) {
|
|
||||||
return new JaxRequest(
|
|
||||||
region,
|
|
||||||
this.config,
|
|
||||||
`match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`,
|
|
||||||
this.limiter,
|
|
||||||
0
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MatchlistEndpoint
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const JaxRequest = require('../JaxRequest')
|
|
||||||
|
|
||||||
class SpectatorEndpoint {
|
|
||||||
constructor(config, limiter) {
|
|
||||||
this.config = config
|
|
||||||
this.limiter = limiter
|
|
||||||
}
|
|
||||||
|
|
||||||
summonerID(summonerID, region) {
|
|
||||||
return new JaxRequest(
|
|
||||||
region,
|
|
||||||
this.config,
|
|
||||||
`spectator/v4/active-games/by-summoner/${summonerID}`,
|
|
||||||
this.limiter,
|
|
||||||
0
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SpectatorEndpoint
|
|
||||||
78
server/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts
Normal file
78
server/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// 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,
|
||||||
|
gameType: string
|
||||||
|
gameStartTime: number,
|
||||||
|
mapId: number,
|
||||||
|
gameLength: number,
|
||||||
|
platformId: string,
|
||||||
|
gameMode: string,
|
||||||
|
bannedChampions: BannedChampion[],
|
||||||
|
gameQueueConfigId: number,
|
||||||
|
observers: Observer,
|
||||||
|
participants: CurrentGameParticipant[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BannedChampion {
|
||||||
|
pickTurn: number,
|
||||||
|
championId: number,
|
||||||
|
teamId: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Observer {
|
||||||
|
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 Perks {
|
||||||
|
perkIds: number[]
|
||||||
|
perkStyle: number,
|
||||||
|
perkSubStyle: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameCustomizationObject {
|
||||||
|
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) {
|
||||||
|
return new JaxRequest(
|
||||||
|
region,
|
||||||
|
this.config,
|
||||||
|
`spectator/v4/active-games/by-summoner/${summonerID}`,
|
||||||
|
this.limiter,
|
||||||
|
0
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const JaxRequest = require('../JaxRequest')
|
|
||||||
|
|
||||||
class SummonerEndpoint {
|
|
||||||
constructor(config, limiter) {
|
|
||||||
this.config = config
|
|
||||||
this.limiter = limiter
|
|
||||||
}
|
|
||||||
|
|
||||||
summonerName(summonerName, region) {
|
|
||||||
return new JaxRequest(
|
|
||||||
region,
|
|
||||||
this.config,
|
|
||||||
`summoner/v4/summoners/by-name/${encodeURI(summonerName)}`,
|
|
||||||
this.limiter,
|
|
||||||
36000
|
|
||||||
).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SummonerEndpoint
|
|
||||||
35
server/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts
Normal file
35
server/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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,
|
||||||
|
region?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SummonerEndpoint {
|
||||||
|
private config: JaxConfig
|
||||||
|
private limiter: RiotRateLimiter
|
||||||
|
|
||||||
|
constructor (config: JaxConfig, limiter: RiotRateLimiter) {
|
||||||
|
this.config = config
|
||||||
|
this.limiter = limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
const RiotRateLimiter = require('riot-ratelimiter')
|
|
||||||
const LeagueEndpoint = require('./Endpoints/LeagueEndpoint')
|
|
||||||
const MatchEndpoint = require('./Endpoints/MatchEndpoint')
|
|
||||||
const MatchlistEndpoint = require('./Endpoints/MatchlistEndpoint')
|
|
||||||
const SpectatorEndpoint = require('./Endpoints/SpectatorEndpoint')
|
|
||||||
const SummonerEndpoint = require('./Endpoints/SummonerEndpoint')
|
|
||||||
|
|
||||||
const CDragonEndpoint = require('./Endpoints/CDragonEndpoint')
|
|
||||||
|
|
||||||
class Jax {
|
|
||||||
constructor(config) {
|
|
||||||
this.key = config.key
|
|
||||||
const limiterOptions = {
|
|
||||||
strategy: config.requestOptions.strategy
|
|
||||||
}
|
|
||||||
this.limiter = new RiotRateLimiter(limiterOptions)
|
|
||||||
this.config = config
|
|
||||||
|
|
||||||
this.League = new LeagueEndpoint(this.config, this.limiter)
|
|
||||||
this.Match = new MatchEndpoint(this.config, this.limiter)
|
|
||||||
this.Matchlist = new MatchlistEndpoint(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
set regionName(regionName) {
|
|
||||||
this.config.region = regionName
|
|
||||||
const blacklistedProperties = ['key', 'limiter', 'config', 'version', 'CDragon']
|
|
||||||
|
|
||||||
for (const key of Object.getOwnPropertyNames(this)) {
|
|
||||||
if(blacklistedProperties.includes(key)) continue
|
|
||||||
|
|
||||||
this[key].region = regionName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Jax
|
|
||||||
42
server/app/Services/Jax/src/Jax.ts
Normal file
42
server/app/Services/Jax/src/Jax.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
export default class Jax {
|
||||||
|
public key: string
|
||||||
|
public limiter: RiotRateLimiter
|
||||||
|
public config: JaxConfig
|
||||||
|
public League: LeagueEndpoint
|
||||||
|
public Match: MatchEndpoint
|
||||||
|
public Matchlist: MatchlistEndpoint
|
||||||
|
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.Matchlist = new MatchlistEndpoint(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
const { promisify } = require('util')
|
|
||||||
const Logger = use('Logger')
|
|
||||||
const Redis = use('Redis')
|
|
||||||
|
|
||||||
class JaxRequest {
|
|
||||||
constructor(region, config, endpoint, limiter, cacheTime) {
|
|
||||||
this.region = region
|
|
||||||
this.config = config
|
|
||||||
this.endpoint = endpoint
|
|
||||||
this.limiter = limiter
|
|
||||||
this.cacheTime = cacheTime
|
|
||||||
this.retries = config.requestOptions.retriesBeforeAbort
|
|
||||||
|
|
||||||
this.sleep = promisify(setTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = await this.limiter.executing({
|
|
||||||
url,
|
|
||||||
token: this.config.key,
|
|
||||||
resolveWithFullResponse: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.cacheTime > 0) {
|
|
||||||
await Redis.set(url, resp, 'EX', this.cacheTime)
|
|
||||||
}
|
|
||||||
return JSON.parse(resp)
|
|
||||||
} catch ({ statusCode, ...rest }) {
|
|
||||||
this.retries--
|
|
||||||
|
|
||||||
if (statusCode !== 500 && statusCode !== 503 && statusCode !== 504) {
|
|
||||||
// Don't log 404 when summoner isn't playing or the summoner doesn't exist
|
|
||||||
if (!this.endpoint.includes('spectator/v4/active-games/by-summoner') && !this.endpoint.includes('summoner/v4/summoners/by-name')) {
|
|
||||||
Logger.transport('file').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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = JaxRequest
|
|
||||||
85
server/app/Services/Jax/src/JaxRequest.ts
Normal file
85
server/app/Services/Jax/src/JaxRequest.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
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 = await this.limiter.execute({
|
||||||
|
// url,
|
||||||
|
// options: {
|
||||||
|
// headers: {
|
||||||
|
// 'X-Riot-Token': this.config.key,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
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 ({ status, ...rest }) {
|
||||||
|
this.retries--
|
||||||
|
|
||||||
|
if (status !== 500 && status !== 503 && status !== 504) { //
|
||||||
|
// 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(`JaxRequest Error ${status}: `, rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('====================================')
|
||||||
|
console.log(status)
|
||||||
|
console.log('====================================')
|
||||||
|
|
||||||
|
if (this.retries > 0) {
|
||||||
|
await this.sleep(this.config.requestOptions.delayBeforeRetry)
|
||||||
|
return this.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
'use strict'
|
import Jax from './Jax'
|
||||||
|
import Logger from '@ioc:Adonis/Core/Logger'
|
||||||
const Logger = use('Logger')
|
import { getSeasonNumber } from 'App/helpers'
|
||||||
const Jax = use('App/Services/Jax')
|
import { MatchReferenceDto } from './Jax/src/Endpoints/MatchListEndpoint'
|
||||||
const BasicMatchTransformer = use('App/Transformers/BasicMatchTransformer')
|
import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint'
|
||||||
const { getSeasonNumber } = use('App/helpers')
|
import { SummonerModel } from 'App/Models/Summoner'
|
||||||
|
import Match, { MatchModel } from 'App/Models/Match'
|
||||||
|
import BasicMatchTransformer from 'App/Transformers/BasicMatchTransformer'
|
||||||
|
import mongodb from '@ioc:Mongodb/Database'
|
||||||
|
|
||||||
class MatchService {
|
class MatchService {
|
||||||
/**
|
/**
|
||||||
|
|
@ -11,12 +14,12 @@ class MatchService {
|
||||||
* @param account of the summoner
|
* @param account of the summoner
|
||||||
* @param stopFetching condition to stop fetching the MatchList
|
* @param stopFetching condition to stop fetching the MatchList
|
||||||
*/
|
*/
|
||||||
async _fetchMatchListUntil(account, stopFetching) {
|
private async _fetchMatchListUntil (account: SummonerDTO, stopFetching: any) {
|
||||||
let matchList = []
|
let matchList: MatchReferenceDto[] = []
|
||||||
let alreadyIn = false
|
let alreadyIn = false
|
||||||
let index = 0
|
let index = 0
|
||||||
do {
|
do {
|
||||||
let newMatchList = await Jax.Matchlist.accountID(account.accountId, account.region, index)
|
let newMatchList = await Jax.Matchlist.accountID(account.accountId, account.region as string, index)
|
||||||
// Error while fetching Riot API
|
// Error while fetching Riot API
|
||||||
if (!newMatchList) {
|
if (!newMatchList) {
|
||||||
matchList = matchList.map(m => {
|
matchList = matchList.map(m => {
|
||||||
|
|
@ -25,15 +28,14 @@ class MatchService {
|
||||||
})
|
})
|
||||||
return matchList
|
return matchList
|
||||||
}
|
}
|
||||||
newMatchList = newMatchList.matches
|
matchList = [...matchList, ...newMatchList.matches]
|
||||||
matchList = [...matchList, ...newMatchList]
|
alreadyIn = newMatchList.matches.length === 0 || stopFetching(newMatchList.matches)
|
||||||
alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList)
|
|
||||||
// If the match is made in another region : we stop fetching
|
// If the match is made in another region : we stop fetching
|
||||||
if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) {
|
if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) {
|
||||||
alreadyIn = true;
|
alreadyIn = true
|
||||||
}
|
}
|
||||||
index += 100
|
index += 100
|
||||||
} while (!alreadyIn);
|
} while (!alreadyIn)
|
||||||
|
|
||||||
// Remove matches from MatchList made in another region and tutorial games
|
// Remove matches from MatchList made in another region and tutorial games
|
||||||
const tutorialModes = [2000, 2010, 2020]
|
const tutorialModes = [2000, 2010, 2020]
|
||||||
|
|
@ -56,14 +58,14 @@ class MatchService {
|
||||||
* @param account of the summoner
|
* @param account of the summoner
|
||||||
* @param summonerDB summoner in the database
|
* @param summonerDB summoner in the database
|
||||||
*/
|
*/
|
||||||
async updateMatchList(account, summonerDB) {
|
public async updateMatchList (account: SummonerDTO, summonerDB: SummonerModel) {
|
||||||
console.time('matchList')
|
console.time('matchList')
|
||||||
|
|
||||||
// Summoner has already been searched : we already have a MatchList and we need to update it
|
// Summoner has already been searched : we already have a MatchList and we need to update it
|
||||||
if (summonerDB.matchList) {
|
if (summonerDB.matchList) {
|
||||||
// Get MatchList
|
// Get MatchList
|
||||||
const matchList = await this._fetchMatchListUntil(account, (newMatchList) => {
|
const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => {
|
||||||
return summonerDB.matchList.some(m => m.gameId === newMatchList[newMatchList.length - 1].gameId)
|
return summonerDB.matchList!.some(m => m.gameId === newMatchList[newMatchList.length - 1].gameId)
|
||||||
})
|
})
|
||||||
// Update Summoner's MatchList
|
// Update Summoner's MatchList
|
||||||
for (const match of matchList.reverse()) {
|
for (const match of matchList.reverse()) {
|
||||||
|
|
@ -71,12 +73,10 @@ class MatchService {
|
||||||
summonerDB.matchList.unshift(match)
|
summonerDB.matchList.unshift(match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else { // First search of the Summoner
|
||||||
// First search of the Summoner
|
|
||||||
else {
|
|
||||||
const today = Date.now()
|
const today = Date.now()
|
||||||
// Get MatchList
|
// Get MatchList
|
||||||
const matchList = await this._fetchMatchListUntil(account, (newMatchList) => {
|
const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => {
|
||||||
return (newMatchList.length !== 100 || today - newMatchList[newMatchList.length - 1].timestamp > 10368000000)
|
return (newMatchList.length !== 100 || today - newMatchList[newMatchList.length - 1].timestamp > 10368000000)
|
||||||
})
|
})
|
||||||
// Create Summoner's MatchList in Database
|
// Create Summoner's MatchList in Database
|
||||||
|
|
@ -87,17 +87,24 @@ class MatchService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch list of matches for a specific Summoner
|
* Fetch list of matches for a specific Summoner
|
||||||
* @param account of the summoner
|
* @param puuid
|
||||||
* @param gameIds of the matches to fetch
|
* @param accountId
|
||||||
* @param summonerDB summoner in the database
|
* @param region
|
||||||
|
* @param gameIds
|
||||||
|
* @param summonerDB
|
||||||
*/
|
*/
|
||||||
async getMatches(account, gameIds, summonerDB) {
|
public async getMatches (puuid: string, accountId: string, region: string, gameIds: number[], summonerDB: SummonerModel) {
|
||||||
console.time('getMatches')
|
console.time('getMatches')
|
||||||
|
|
||||||
let matchesDetails = []
|
let matchesDetails: MatchModel[] = []
|
||||||
const matchesToGetFromRiot = []
|
const matchesToGetFromRiot: number[] = []
|
||||||
|
// TODO: replace it with Match Model once the package is fixed
|
||||||
|
const matchesCollection = await mongodb.connection().collection('matches')
|
||||||
for (let i = 0; i < gameIds.length; ++i) {
|
for (let i = 0; i < gameIds.length; ++i) {
|
||||||
const matchSaved = await summonerDB.matches().where({ gameId: gameIds[i] }).first()
|
const matchSaved = await matchesCollection.findOne({
|
||||||
|
summoner_puuid: puuid,
|
||||||
|
gameId: gameIds[i],
|
||||||
|
})
|
||||||
if (matchSaved) {
|
if (matchSaved) {
|
||||||
matchesDetails.push(matchSaved)
|
matchesDetails.push(matchSaved)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -105,7 +112,7 @@ class MatchService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, account.region))
|
const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, region))
|
||||||
let matchesFromApi = await Promise.all(requests)
|
let matchesFromApi = await Promise.all(requests)
|
||||||
|
|
||||||
/* If we have to store some matches in the db */
|
/* If we have to store some matches in the db */
|
||||||
|
|
@ -113,19 +120,19 @@ class MatchService {
|
||||||
// Try to see why matches are sometimes undefined
|
// Try to see why matches are sometimes undefined
|
||||||
matchesFromApi.filter(m => {
|
matchesFromApi.filter(m => {
|
||||||
if (m === undefined) {
|
if (m === undefined) {
|
||||||
Logger.transport('file').info(`Match undefined, summoner: ${summonerDB.puuid}`, m)
|
Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Transform raw matches data
|
// Transform raw matches data
|
||||||
await BasicMatchTransformer.transform(matchesFromApi, { account })
|
const transformedMatches = await BasicMatchTransformer.transform(matchesFromApi, { puuid, accountId })
|
||||||
|
|
||||||
/* Save all matches from Riot Api in db */
|
/* Save all matches from Riot Api in db */
|
||||||
for (const match of matchesFromApi) {
|
for (const match of transformedMatches) {
|
||||||
await summonerDB.matches().create(match)
|
await Match.create(match)
|
||||||
match.newMatch = true
|
match.newMatch = true
|
||||||
}
|
}
|
||||||
matchesDetails = [...matchesDetails, ...matchesFromApi]
|
matchesDetails = [...matchesDetails, ...transformedMatches]
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort matches */
|
/* Sort matches */
|
||||||
|
|
@ -136,4 +143,4 @@ class MatchService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new MatchService()
|
export default new MatchService()
|
||||||
|
|
@ -1,11 +1,62 @@
|
||||||
'use strict'
|
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||||
|
import got from 'got/dist/source'
|
||||||
|
|
||||||
const got = require('got')
|
export interface ChampionsPlayRate {
|
||||||
const Redis = use('Redis')
|
[champion: string]: {
|
||||||
|
TOP: number,
|
||||||
|
JUNGLE: number,
|
||||||
|
MIDDLE: number,
|
||||||
|
BOTTOM: number,
|
||||||
|
UTILITY: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FinalRoleComposition {
|
||||||
|
TOP?: number,
|
||||||
|
JUNGLE?: number,
|
||||||
|
MIDDLE?: number,
|
||||||
|
BOTTOM?: number,
|
||||||
|
SUPPORT?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoleComposition {
|
||||||
|
TOP?: number,
|
||||||
|
JUNGLE?: number,
|
||||||
|
MIDDLE?: number,
|
||||||
|
BOTTOM?: number,
|
||||||
|
UTILITY?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChampionComposition {
|
||||||
|
[champion: number]: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiRoleResponse {
|
||||||
|
data: { [key: string]: ChampionInitialRates };
|
||||||
|
patch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChampionInitialRates {
|
||||||
|
MIDDLE?: RoleRate;
|
||||||
|
UTILITY?: RoleRate;
|
||||||
|
JUNGLE?: RoleRate;
|
||||||
|
TOP?: RoleRate;
|
||||||
|
BOTTOM?: RoleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoleRate {
|
||||||
|
playRate: number;
|
||||||
|
winRate: number;
|
||||||
|
banRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChampionsRates {
|
||||||
|
[champion: number]: RoleComposition
|
||||||
|
}
|
||||||
|
|
||||||
class RoleIdentificationService {
|
class RoleIdentificationService {
|
||||||
_getPermutations(array) {
|
private _getPermutations (array: number[]) {
|
||||||
const result = []
|
const result: number[][] = []
|
||||||
|
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
const rest = this._getPermutations(array.slice(0, i).concat(array.slice(i + 1)))
|
const rest = this._getPermutations(array.slice(0, i).concat(array.slice(i + 1)))
|
||||||
|
|
@ -21,15 +72,23 @@ class RoleIdentificationService {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateMetric(championPositions, bestPositions) {
|
private _calculateMetric (championPositions: ChampionsRates, bestPositions: RoleComposition) {
|
||||||
return Object.entries(bestPositions).reduce((agg, [position, champion]) => {
|
return Object.entries(bestPositions).reduce((agg, [position, champion]) => {
|
||||||
return agg + (championPositions[champion][position] || 0)
|
return agg + (championPositions[champion][position] || 0)
|
||||||
}, 0) / Object.keys(bestPositions).length
|
}, 0) / Object.keys(bestPositions).length
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPositions(championPositions, composition, top, jungle, middle, adc, support) {
|
private _getPositions (
|
||||||
|
championPositions: ChampionsRates,
|
||||||
|
composition: number[],
|
||||||
|
top?: number,
|
||||||
|
jungle?: number,
|
||||||
|
middle?: number,
|
||||||
|
adc?: number,
|
||||||
|
support?: number
|
||||||
|
) {
|
||||||
// Set the initial guess to be the champion in the composition, order doesn't matter
|
// Set the initial guess to be the champion in the composition, order doesn't matter
|
||||||
let bestPositions = {
|
let bestPositions: RoleComposition = {
|
||||||
'TOP': composition[0],
|
'TOP': composition[0],
|
||||||
'JUNGLE': composition[1],
|
'JUNGLE': composition[1],
|
||||||
'MIDDLE': composition[2],
|
'MIDDLE': composition[2],
|
||||||
|
|
@ -39,7 +98,7 @@ class RoleIdentificationService {
|
||||||
|
|
||||||
let bestMetric = this._calculateMetric(championPositions, bestPositions)
|
let bestMetric = this._calculateMetric(championPositions, bestPositions)
|
||||||
let secondBestMetric = -Infinity
|
let secondBestMetric = -Infinity
|
||||||
let secondBestPositions = null
|
let secondBestPositions: RoleComposition | null = null
|
||||||
|
|
||||||
// Figure out which champions and positions we need to fill
|
// Figure out which champions and positions we need to fill
|
||||||
const knownChampions = [top, jungle, middle, adc, support].filter(Boolean)
|
const knownChampions = [top, jungle, middle, adc, support].filter(Boolean)
|
||||||
|
|
@ -50,7 +109,7 @@ class RoleIdentificationService {
|
||||||
.filter(pos => !pos[1])
|
.filter(pos => !pos[1])
|
||||||
.map(pos => pos[0])
|
.map(pos => pos[0])
|
||||||
|
|
||||||
const testComposition = {
|
const testComposition: RoleComposition = {
|
||||||
'TOP': top,
|
'TOP': top,
|
||||||
'JUNGLE': jungle,
|
'JUNGLE': jungle,
|
||||||
'MIDDLE': middle,
|
'MIDDLE': middle,
|
||||||
|
|
@ -79,12 +138,12 @@ class RoleIdentificationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bestPlayPercents = {}
|
const bestPlayPercents: ChampionComposition = {}
|
||||||
for (const [position, champion] of Object.entries(bestPositions)) {
|
for (const [position, champion] of Object.entries(bestPositions)) {
|
||||||
bestPlayPercents[champion] = championPositions[champion][position]
|
bestPlayPercents[champion] = championPositions[champion][position]
|
||||||
}
|
}
|
||||||
|
|
||||||
let secondBestPlayPercents = null
|
let secondBestPlayPercents: ChampionComposition | null = null
|
||||||
if (secondBestPositions !== null) {
|
if (secondBestPositions !== null) {
|
||||||
secondBestPlayPercents = {}
|
secondBestPlayPercents = {}
|
||||||
for (const [position, champion] of Object.entries(secondBestPositions)) {
|
for (const [position, champion] of Object.entries(secondBestPositions)) {
|
||||||
|
|
@ -104,7 +163,7 @@ class RoleIdentificationService {
|
||||||
/**
|
/**
|
||||||
* Get the CDN data of the champion playrates by role
|
* Get the CDN data of the champion playrates by role
|
||||||
*/
|
*/
|
||||||
async pullData() {
|
public async pullData (): Promise<ChampionsPlayRate> {
|
||||||
const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json'
|
const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json'
|
||||||
|
|
||||||
// Check if cached
|
// Check if cached
|
||||||
|
|
@ -114,9 +173,9 @@ class RoleIdentificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {}
|
const data = {}
|
||||||
const response = await got(url, { responseType: 'json' })
|
const { body }: { body: ApiRoleResponse } = await got(url, { responseType: 'json' })
|
||||||
|
|
||||||
for (const [championId, roles] of Object.entries(response.body.data)) {
|
for (const [championId, roles] of Object.entries(body.data)) {
|
||||||
const playRates = {}
|
const playRates = {}
|
||||||
|
|
||||||
for (const [position, rates] of Object.entries(roles)) {
|
for (const [position, rates] of Object.entries(roles)) {
|
||||||
|
|
@ -145,7 +204,12 @@ class RoleIdentificationService {
|
||||||
* @param jungle
|
* @param jungle
|
||||||
* @param support
|
* @param support
|
||||||
*/
|
*/
|
||||||
getRoles(championPositions, composition, jungle = null, support = null) {
|
public getRoles (
|
||||||
|
championPositions: ChampionsRates,
|
||||||
|
composition: number[],
|
||||||
|
jungle?: number,
|
||||||
|
support?: number
|
||||||
|
): FinalRoleComposition {
|
||||||
// Set composition champion playrate to 0% if not present in the json data
|
// Set composition champion playrate to 0% if not present in the json data
|
||||||
for (const compChamp of composition) {
|
for (const compChamp of composition) {
|
||||||
if (championPositions[compChamp]) {
|
if (championPositions[compChamp]) {
|
||||||
|
|
@ -157,13 +221,13 @@ class RoleIdentificationService {
|
||||||
UTILITY: 0,
|
UTILITY: 0,
|
||||||
TOP: 0,
|
TOP: 0,
|
||||||
JUNGLE: 0,
|
JUNGLE: 0,
|
||||||
BOTTOM: 0
|
BOTTOM: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const identified = {}
|
const identified: RoleComposition = {}
|
||||||
let positions = {}
|
let positions: RoleComposition = {}
|
||||||
let secondaryPositions = null
|
let secondaryPositions: RoleComposition | null = null
|
||||||
let secondaryMetric = -Infinity
|
let secondaryMetric = -Infinity
|
||||||
|
|
||||||
if (jungle) {
|
if (jungle) {
|
||||||
|
|
@ -177,19 +241,19 @@ class RoleIdentificationService {
|
||||||
while (Object.keys(identified).length < composition.length - 1) {
|
while (Object.keys(identified).length < composition.length - 1) {
|
||||||
let { bestPositions, bestMetric: metric, secondBestPositions: sbp } =
|
let { bestPositions, bestMetric: metric, secondBestPositions: sbp } =
|
||||||
this._getPositions(championPositions, composition,
|
this._getPositions(championPositions, composition,
|
||||||
identified.TOP, identified.JUNGLE, identified.MIDDLE, identified.ADC, identified.UTILITY
|
identified.TOP, identified.JUNGLE, identified.MIDDLE, identified.BOTTOM, identified.UTILITY
|
||||||
)
|
)
|
||||||
|
|
||||||
positions = bestPositions
|
positions = bestPositions
|
||||||
|
|
||||||
if (sbp !== null) {
|
if (sbp !== null) {
|
||||||
let _metric = this._calculateMetric(championPositions, { ...sbp })
|
let currentMetric = this._calculateMetric(championPositions, { ...sbp })
|
||||||
|
|
||||||
if (secondaryPositions === null) {
|
if (secondaryPositions === null) {
|
||||||
secondaryPositions = sbp
|
secondaryPositions = sbp
|
||||||
secondaryMetric = _metric
|
secondaryMetric = currentMetric
|
||||||
} else if (metric > _metric && _metric > secondaryMetric) {
|
} else if (metric > currentMetric && currentMetric > secondaryMetric) {
|
||||||
secondaryMetric = _metric
|
secondaryMetric = currentMetric
|
||||||
secondaryPositions = sbp
|
secondaryPositions = sbp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,6 +269,14 @@ class RoleIdentificationService {
|
||||||
metric: championPositions[champion][position],
|
metric: championPositions[champion][position],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Tmp fix
|
||||||
|
if (!Object.keys(positionsWithMetric).length) {
|
||||||
|
jungle = undefined
|
||||||
|
support = undefined
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const bestPosition = Object.keys(positionsWithMetric).reduce((posA, posB) => {
|
const bestPosition = Object.keys(positionsWithMetric).reduce((posA, posB) => {
|
||||||
return positionsWithMetric[posA].metric > positionsWithMetric[posB].metric ? posA : posB
|
return positionsWithMetric[posA].metric > positionsWithMetric[posB].metric ? posA : posB
|
||||||
})
|
})
|
||||||
|
|
@ -223,4 +295,4 @@ class RoleIdentificationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new RoleIdentificationService()
|
export default new RoleIdentificationService()
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const Helpers = use('App/helpers')
|
|
||||||
const MatchRepository = make('App/Repositories/MatchRepository')
|
|
||||||
|
|
||||||
class StatsService {
|
|
||||||
constructor() {
|
|
||||||
this.matchRepository = MatchRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSummonerStats(account, season) {
|
|
||||||
this.matchRepository.season = season
|
|
||||||
console.time('GLOBAL')
|
|
||||||
const globalStats = await this.matchRepository.globalStats(account.puuid)
|
|
||||||
console.timeEnd('GLOBAL')
|
|
||||||
console.time('GAMEMODE')
|
|
||||||
const gamemodeStats = await this.matchRepository.gamemodeStats(account.puuid)
|
|
||||||
console.timeEnd('GAMEMODE')
|
|
||||||
console.time('ROLE')
|
|
||||||
const roleStats = await this.matchRepository.roleStats(account.puuid)
|
|
||||||
// Check if all roles are in the array
|
|
||||||
const roles = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
|
||||||
for (const role of roles) {
|
|
||||||
if (!roleStats.find(r => r.role === role)) {
|
|
||||||
roleStats.push({
|
|
||||||
count: 0,
|
|
||||||
losses: 0,
|
|
||||||
role,
|
|
||||||
wins: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.timeEnd('ROLE')
|
|
||||||
console.time('CHAMPION')
|
|
||||||
const championStats = await this.matchRepository.championStats(account.puuid, 5)
|
|
||||||
console.timeEnd('CHAMPION')
|
|
||||||
console.time('CHAMPION-CLASS')
|
|
||||||
const championClassStats = await this.matchRepository.championClassStats(account.puuid)
|
|
||||||
console.timeEnd('CHAMPION-CLASS')
|
|
||||||
console.time('MATES')
|
|
||||||
const mates = await this.matchRepository.mates(account.puuid)
|
|
||||||
console.timeEnd('MATES')
|
|
||||||
|
|
||||||
return {
|
|
||||||
global: globalStats[0],
|
|
||||||
league: gamemodeStats,
|
|
||||||
role: roleStats.sort(Helpers.sortTeamByRole),
|
|
||||||
class: championClassStats,
|
|
||||||
mates,
|
|
||||||
champion: championStats,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new StatsService()
|
|
||||||
48
server/app/Services/StatsService.ts
Normal file
48
server/app/Services/StatsService.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import MatchRepository from 'App/Repositories/MatchRepository'
|
||||||
|
import { sortTeamByRole } from 'App/helpers'
|
||||||
|
|
||||||
|
class StatsService {
|
||||||
|
public async getSummonerStats (puuid: string, season?: number) {
|
||||||
|
console.time('GLOBAL')
|
||||||
|
const globalStats = await MatchRepository.globalStats(puuid, season)
|
||||||
|
console.timeEnd('GLOBAL')
|
||||||
|
console.time('GAMEMODE')
|
||||||
|
const gamemodeStats = await MatchRepository.gamemodeStats(puuid, season)
|
||||||
|
console.timeEnd('GAMEMODE')
|
||||||
|
console.time('ROLE')
|
||||||
|
const roleStats = await MatchRepository.roleStats(puuid, season)
|
||||||
|
// Check if all roles are in the array
|
||||||
|
const roles = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
||||||
|
for (const role of roles) {
|
||||||
|
if (!roleStats.find(r => r.role === role)) {
|
||||||
|
roleStats.push({
|
||||||
|
count: 0,
|
||||||
|
losses: 0,
|
||||||
|
role,
|
||||||
|
wins: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.timeEnd('ROLE')
|
||||||
|
console.time('CHAMPION')
|
||||||
|
const championStats = await MatchRepository.championStats(puuid, 5, season)
|
||||||
|
console.timeEnd('CHAMPION')
|
||||||
|
console.time('CHAMPION-CLASS')
|
||||||
|
const championClassStats = await MatchRepository.championClassStats(puuid, season)
|
||||||
|
console.timeEnd('CHAMPION-CLASS')
|
||||||
|
console.time('MATES')
|
||||||
|
const mates = await MatchRepository.mates(puuid, season)
|
||||||
|
console.timeEnd('MATES')
|
||||||
|
|
||||||
|
return {
|
||||||
|
global: globalStats[0],
|
||||||
|
league: gamemodeStats,
|
||||||
|
role: roleStats.sort(sortTeamByRole),
|
||||||
|
class: championClassStats,
|
||||||
|
mates,
|
||||||
|
champion: championStats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new StatsService()
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const Jax = use('App/Services/Jax')
|
|
||||||
|
|
||||||
class SummonerService {
|
|
||||||
constructor() {
|
|
||||||
this.uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
|
|
||||||
this.leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to transform League Data from the Riot API
|
|
||||||
* @param league raw data of the league from Riot API
|
|
||||||
*/
|
|
||||||
_getleagueData(league) {
|
|
||||||
if (!league) return null
|
|
||||||
league.fullRank = this.uniqueLeagues.includes(league.tier) ? league.tier : `${league.tier} ${league.rank}`
|
|
||||||
league.winrate = +(league.wins * 100 / (league.wins + league.losses)).toFixed(1) + '%'
|
|
||||||
league.shortName = this.uniqueLeagues.includes(league.tier) ? league.leaguePoints : league.tier[0] + this.leaguesNumbers[league.rank]
|
|
||||||
return league
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get account infos for a searched summoner name
|
|
||||||
* @param summonerName
|
|
||||||
* @param region
|
|
||||||
*/
|
|
||||||
async getAccount(summonerName, region) {
|
|
||||||
const name = summonerName.toLowerCase().replace(/ /g, '')
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
getAllSummonerNames(account, summonerDB) {
|
|
||||||
const names = summonerDB.names ? summonerDB.names : []
|
|
||||||
|
|
||||||
if (!names.find(n => n.name === account.name)) {
|
|
||||||
names.push({
|
|
||||||
name: account.name,
|
|
||||||
date: new Date()
|
|
||||||
})
|
|
||||||
summonerDB.names = names
|
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ranked data for a specific Summoner
|
|
||||||
* @param account
|
|
||||||
* @param region
|
|
||||||
*/
|
|
||||||
async getRanked(account, region) {
|
|
||||||
const ranked = await Jax.League.summonerID(account.id, region)
|
|
||||||
const result = {
|
|
||||||
soloQ: this._getleagueData(ranked.find(e => e.queueType === 'RANKED_SOLO_5x5')) || null,
|
|
||||||
flex5v5: this._getleagueData(ranked.find(e => e.queueType === 'RANKED_FLEX_SR')) || null,
|
|
||||||
flex3v3: this._getleagueData(ranked.find(e => e.queueType === 'RANKED_FLEX_TT')) || null
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new SummonerService()
|
|
||||||
88
server/app/Services/SummonerService.ts
Normal file
88
server/app/Services/SummonerService.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import Jax from './Jax'
|
||||||
|
import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint'
|
||||||
|
import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint'
|
||||||
|
import { SummonerModel } from 'App/Models/Summoner'
|
||||||
|
|
||||||
|
export interface LeagueEntriesByQueue {
|
||||||
|
soloQ?: LeagueEntryByQueue,
|
||||||
|
flex5v5?: LeagueEntryByQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueEntryByQueue extends LeagueEntryDTO {
|
||||||
|
fullRank: string,
|
||||||
|
winrate: string,
|
||||||
|
shortName: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
class SummonerService {
|
||||||
|
private uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER']
|
||||||
|
private leaguesNumbers = { 'I': 1, 'II': 2, 'III': 3, 'IV': 4 }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = +(league.wins * 100 / (league.wins + league.losses)).toFixed(1) + '%'
|
||||||
|
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().replace(/ /g, '')
|
||||||
|
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 getAllSummonerNames (account: SummonerDTO, summonerDB: SummonerModel) {
|
||||||
|
const names = summonerDB.names ? summonerDB.names : []
|
||||||
|
|
||||||
|
if (!names.find(n => n.name === account.name)) {
|
||||||
|
names.push({
|
||||||
|
name: account.name,
|
||||||
|
date: new Date(),
|
||||||
|
})
|
||||||
|
summonerDB.names = names
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ranked data for a specific Summoner
|
||||||
|
* @param account
|
||||||
|
* @param region
|
||||||
|
*/
|
||||||
|
public async getRanked (account: SummonerDTO, region: string): Promise<LeagueEntriesByQueue> {
|
||||||
|
const ranked = await Jax.League.summonerID(account.id, region)
|
||||||
|
const result = {
|
||||||
|
soloQ: this.getleagueData(ranked.find(e => e.queueType === 'RANKED_SOLO_5x5')) || undefined,
|
||||||
|
flex5v5: this.getleagueData(ranked.find(e => e.queueType === 'RANKED_FLEX_SR')) || undefined,
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SummonerService()
|
||||||
|
|
@ -1,44 +1,22 @@
|
||||||
'use strict'
|
import { MatchModel, ParticipantBasic } from 'App/Models/Match'
|
||||||
|
import { MatchDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
||||||
|
import MatchTransformer from 'App/Transformers/MatchTransformer'
|
||||||
|
|
||||||
const MatchTransformer = use('App/Transformers/MatchTransformer')
|
|
||||||
const { queuesWithRole } = use('App/helpers')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BasicMatchTransformer class
|
|
||||||
*
|
|
||||||
* @class BasicMatchTransformer
|
|
||||||
*/
|
|
||||||
class BasicMatchTransformer extends MatchTransformer {
|
class BasicMatchTransformer extends MatchTransformer {
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param matches data from Riot API, Array of match or a single match
|
|
||||||
* @param ctx context
|
|
||||||
*/
|
|
||||||
async transform(matches, { account }) {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
if (Array.isArray(matches)) {
|
|
||||||
matches.forEach((match, index) => {
|
|
||||||
matches[index] = this.transformOneMatch(match, account)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
matches = this.transformOneMatch(matches, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform raw data for 1 match
|
* Transform raw data for 1 match
|
||||||
|
* @param match
|
||||||
|
* @param puuid
|
||||||
|
* @param accountId
|
||||||
*/
|
*/
|
||||||
transformOneMatch(match, account) {
|
private transformOneMatch (match: MatchDto, puuid: string, accountId: string): MatchModel {
|
||||||
// Global data about the match
|
// Global data about the match
|
||||||
const globalInfos = super.getGameInfos(match)
|
const globalInfos = super.getGameInfos(match)
|
||||||
|
|
||||||
const identity = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId)
|
const identity = match.participantIdentities.find((p) => p.player.currentAccountId === accountId)
|
||||||
const player = match.participants[identity.participantId - 1]
|
const player = match.participants[identity!.participantId - 1]
|
||||||
|
|
||||||
let win = match.teams.find((t) => t.teamId === player.teamId).win
|
let win = match.teams.find((t) => t.teamId === player.teamId)!.win
|
||||||
|
|
||||||
// Match less than 5min
|
// Match less than 5min
|
||||||
if (match.gameDuration < 300) {
|
if (match.gameDuration < 300) {
|
||||||
|
|
@ -49,15 +27,15 @@ class BasicMatchTransformer extends MatchTransformer {
|
||||||
const playerData = super.getPlayerData(match, player, false)
|
const playerData = super.getPlayerData(match, player, false)
|
||||||
|
|
||||||
// Teams data
|
// Teams data
|
||||||
const allyTeam = []
|
const allyTeam: ParticipantBasic[] = []
|
||||||
const enemyTeam = []
|
const enemyTeam: ParticipantBasic[] = []
|
||||||
for (let summoner of match.participantIdentities) {
|
for (let summoner of match.participantIdentities) {
|
||||||
const allData = match.participants[summoner.participantId - 1]
|
const allData = match.participants[summoner.participantId - 1]
|
||||||
const playerInfos = {
|
const playerInfos = {
|
||||||
account_id: summoner.player.currentAccountId,
|
account_id: summoner.player.currentAccountId,
|
||||||
name: summoner.player.summonerName,
|
name: summoner.player.summonerName,
|
||||||
role: super.getRoleName(allData.timeline, match.queueId),
|
role: super.getRoleName(allData.timeline, match.queueId),
|
||||||
champion: super.getChampion(allData.championId)
|
champion: super.getChampion(allData.championId),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allData.teamId === player.teamId) {
|
if (allData.teamId === player.teamId) {
|
||||||
|
|
@ -71,16 +49,31 @@ class BasicMatchTransformer extends MatchTransformer {
|
||||||
super.getMatchRoles(match, allyTeam, enemyTeam, player.teamId, playerData)
|
super.getMatchRoles(match, allyTeam, enemyTeam, player.teamId, playerData)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account_id: identity.player.currentAccountId,
|
account_id: identity!.player.currentAccountId,
|
||||||
summoner_puuid: account.puuid,
|
summoner_puuid: puuid,
|
||||||
gameId: match.gameId,
|
gameId: match.gameId,
|
||||||
result: win,
|
result: win,
|
||||||
allyTeam,
|
allyTeam,
|
||||||
enemyTeam,
|
enemyTeam,
|
||||||
...globalInfos,
|
...globalInfos,
|
||||||
...playerData
|
...playerData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform raw data from Riot API
|
||||||
|
* @param matches data from Riot API, Array of matches
|
||||||
|
* @param ctx context
|
||||||
|
*/
|
||||||
|
public async transform (matches: MatchDto[], { puuid, accountId }: { puuid: string, accountId: string }) {
|
||||||
|
await super.getContext()
|
||||||
|
|
||||||
|
const finalMatches: MatchModel[] = []
|
||||||
|
matches.forEach((match, index) => {
|
||||||
|
finalMatches[index] = this.transformOneMatch(match, puuid, accountId)
|
||||||
|
})
|
||||||
|
return finalMatches
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new BasicMatchTransformer()
|
export default new BasicMatchTransformer()
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict'
|
import { Ban, DetailedMatchModel } from 'App/Models/DetailedMatch'
|
||||||
|
import { MatchDto, TeamStatsDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
||||||
const MatchTransformer = use('App/Transformers/MatchTransformer')
|
import MatchTransformer from './MatchTransformer'
|
||||||
const SummonerService = use('App/Services/SummonerService')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DetailedMatchTransformer class
|
* DetailedMatchTransformer class
|
||||||
|
|
@ -9,38 +8,12 @@ const SummonerService = use('App/Services/SummonerService')
|
||||||
* @class DetailedMatchTransformer
|
* @class DetailedMatchTransformer
|
||||||
*/
|
*/
|
||||||
class DetailedMatchTransformer extends MatchTransformer {
|
class DetailedMatchTransformer extends MatchTransformer {
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param match data from Riot API
|
|
||||||
*/
|
|
||||||
async transform(match) {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
// Global data
|
|
||||||
const globalInfos = super.getGameInfos(match)
|
|
||||||
|
|
||||||
// Teams
|
|
||||||
const firstTeam = this.getTeamData(match, match.teams[0])
|
|
||||||
const secondTeam = this.getTeamData(match, match.teams[1])
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
super.getMatchRoles(match, firstTeam.players, secondTeam.players)
|
|
||||||
|
|
||||||
return {
|
|
||||||
gameId: match.gameId,
|
|
||||||
season: match.seasonId,
|
|
||||||
blueTeam: firstTeam.color === 'Blue' ? firstTeam : secondTeam,
|
|
||||||
redTeam: firstTeam.color === 'Blue' ? secondTeam : firstTeam,
|
|
||||||
...globalInfos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all data of one team
|
* Get all data of one team
|
||||||
* @param match raw match data from Riot API
|
* @param match raw match data from Riot API
|
||||||
* @param team raw team data from Riot API
|
* @param team raw team data from Riot API
|
||||||
*/
|
*/
|
||||||
getTeamData(match, team) {
|
private getTeamData (match: MatchDto, team: TeamStatsDto) {
|
||||||
let win = team.win
|
let win = team.win
|
||||||
if (match.gameDuration < 300) {
|
if (match.gameDuration < 300) {
|
||||||
win = 'Remake'
|
win = 'Remake'
|
||||||
|
|
@ -56,31 +29,28 @@ class DetailedMatchTransformer extends MatchTransformer {
|
||||||
prev.dmgChamp += cur.stats.totalDamageDealtToChampions
|
prev.dmgChamp += cur.stats.totalDamageDealtToChampions
|
||||||
prev.dmgObj += cur.stats.damageDealtToObjectives
|
prev.dmgObj += cur.stats.damageDealtToObjectives
|
||||||
prev.dmgTaken += cur.stats.totalDamageTaken
|
prev.dmgTaken += cur.stats.totalDamageTaken
|
||||||
return prev;
|
return prev
|
||||||
}, { kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 });
|
}, { kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 })
|
||||||
|
|
||||||
// Bans
|
// Bans
|
||||||
let bans = null
|
const bans: Ban[] = []
|
||||||
if (team.bans) {
|
if (team.bans) {
|
||||||
bans = team.bans.map(b => {
|
for (const ban of team.bans) {
|
||||||
if (b.championId === -1) {
|
const champion = (ban.championId === -1) ? { id: null, name: null } : super.getChampion(ban.championId)
|
||||||
b.champion = {
|
|
||||||
id: null,
|
bans.push({
|
||||||
name: null
|
...ban,
|
||||||
}
|
champion,
|
||||||
} else {
|
})
|
||||||
b.champion = super.getChampion(b.championId)
|
}
|
||||||
}
|
|
||||||
return b
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Players
|
// Players
|
||||||
let players = teamPlayers
|
let players = teamPlayers
|
||||||
.map(p => super.getPlayerData(match, p, true, teamStats))
|
.map(p => super.getPlayerData(match, p, true, teamStats))
|
||||||
.map(p => {
|
.map(p => {
|
||||||
p.firstSum = super.getSummonerSpell(p.firstSum)
|
p.firstSum = super.getSummonerSpell(p.firstSum as number)
|
||||||
p.secondSum = super.getSummonerSpell(p.secondSum)
|
p.secondSum = super.getSummonerSpell(p.secondSum as number)
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
.sort(this.sortTeamByRole)
|
.sort(this.sortTeamByRole)
|
||||||
|
|
@ -98,6 +68,31 @@ class DetailedMatchTransformer extends MatchTransformer {
|
||||||
towers: team.towerKills,
|
towers: team.towerKills,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform raw data from Riot API
|
||||||
|
* @param match data from Riot API
|
||||||
|
*/
|
||||||
|
public async transform (match: MatchDto): Promise<DetailedMatchModel> {
|
||||||
|
await super.getContext()
|
||||||
|
|
||||||
|
// Global data
|
||||||
|
const globalInfos = super.getGameInfos(match)
|
||||||
|
|
||||||
|
// Teams
|
||||||
|
const firstTeam = this.getTeamData(match, match.teams[0])
|
||||||
|
const secondTeam = this.getTeamData(match, match.teams[1])
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
super.getMatchRoles(match, firstTeam.players, secondTeam.players)
|
||||||
|
|
||||||
|
return {
|
||||||
|
gameId: match.gameId,
|
||||||
|
blueTeam: firstTeam.color === 'Blue' ? firstTeam : secondTeam,
|
||||||
|
redTeam: firstTeam.color === 'Blue' ? secondTeam : firstTeam,
|
||||||
|
...globalInfos,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new DetailedMatchTransformer()
|
export default new DetailedMatchTransformer()
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const MatchTransformer = use('App/Transformers/MatchTransformer')
|
|
||||||
const SummonerService = use('App/Services/SummonerService')
|
|
||||||
const { queuesWithRole } = use('App/helpers')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LiveMatchTransformer class
|
|
||||||
*
|
|
||||||
* @class LiveMatchTransformer
|
|
||||||
*/
|
|
||||||
class LiveMatchTransformer extends MatchTransformer {
|
|
||||||
async _getPlayerRank(participant, region) {
|
|
||||||
const account = await SummonerService.getAccount(participant.summonerName, region)
|
|
||||||
if (account) {
|
|
||||||
participant.level = account.summonerLevel
|
|
||||||
const ranked = await SummonerService.getRanked(account, region)
|
|
||||||
participant.rank = ranked
|
|
||||||
} else {
|
|
||||||
participant.rank = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return participant
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform raw data from Riot API
|
|
||||||
* @param match data from Riot API, one live match
|
|
||||||
*/
|
|
||||||
async transform(match, { region }) {
|
|
||||||
await super.getContext()
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
const blueTeam = [] // 100
|
|
||||||
const redTeam = [] // 200
|
|
||||||
let blueRoles = []
|
|
||||||
let redRoles = []
|
|
||||||
const needsRole = this.championRoles && queuesWithRole.includes(match.gameQueueConfigId)
|
|
||||||
if (needsRole) {
|
|
||||||
match.participants.map(p => {
|
|
||||||
const playerRole = { champion: p.championId, jungle: p.spell1Id === 11 || p.spell2Id === 11 }
|
|
||||||
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
|
|
||||||
})
|
|
||||||
|
|
||||||
blueRoles = super.getTeamRoles(blueTeam)
|
|
||||||
redRoles = super.getTeamRoles(redTeam)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const participant of match.participants) {
|
|
||||||
// Perks
|
|
||||||
participant.runes = participant.perks ? super.getPerksImages(participant.perks.perkIds[0], participant.perks.perkSubStyle) : {}
|
|
||||||
|
|
||||||
// Roles
|
|
||||||
if (needsRole) {
|
|
||||||
const roles = participant.teamId === 100 ? blueRoles : redRoles
|
|
||||||
participant.role = Object.entries(roles).find(([, champion]) => participant.championId === champion)[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ranks
|
|
||||||
const requestsParticipants = match.participants.map(p => this._getPlayerRank(p, region))
|
|
||||||
match.participants = await Promise.all(requestsParticipants)
|
|
||||||
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new LiveMatchTransformer()
|
|
||||||
72
server/app/Transformers/LiveMatchTransformer.ts
Normal file
72
server/app/Transformers/LiveMatchTransformer.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { queuesWithRole } from 'App/helpers'
|
||||||
|
import { CurrentGameInfo, CurrentGameParticipant } from 'App/Services/Jax/src/Endpoints/SpectatorEndpoint'
|
||||||
|
import { FinalRoleComposition } from 'App/Services/RoleIdentiticationService'
|
||||||
|
import SummonerService, { LeagueEntriesByQueue } from 'App/Services/SummonerService'
|
||||||
|
import MatchTransformer, { PlayerRole } from './MatchTransformer'
|
||||||
|
|
||||||
|
class LiveMatchTransformer extends MatchTransformer {
|
||||||
|
/**
|
||||||
|
* Get player soloQ and flex rank from his summonerName
|
||||||
|
* @param participant
|
||||||
|
* @param region
|
||||||
|
*/
|
||||||
|
private async getPlayerRank (participant: CurrentGameParticipant, region: string) {
|
||||||
|
const account = await SummonerService.getAccount(participant.summonerName, region)
|
||||||
|
let ranked: LeagueEntriesByQueue
|
||||||
|
if (account) {
|
||||||
|
ranked = await SummonerService.getRanked(account, region)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...participant,
|
||||||
|
level: account ? account.summonerLevel : undefined,
|
||||||
|
rank: account ? ranked! : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform raw data from Riot API
|
||||||
|
* @param liveMatch
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public async transform (liveMatch: CurrentGameInfo, { region }: { region: string }) {
|
||||||
|
await super.getContext()
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
const blueTeam: PlayerRole[] = [] // 100
|
||||||
|
const redTeam: PlayerRole[] = [] // 200
|
||||||
|
let blueRoles: FinalRoleComposition = {}
|
||||||
|
let redRoles: FinalRoleComposition = {}
|
||||||
|
const needsRole = this.championRoles && queuesWithRole.includes(liveMatch.gameQueueConfigId)
|
||||||
|
if (needsRole) {
|
||||||
|
liveMatch.participants.map(p => {
|
||||||
|
const playerRole = { champion: p.championId, jungle: p.spell1Id === 11 || p.spell2Id === 11 }
|
||||||
|
p.teamId === 100 ? blueTeam.push(playerRole) : redTeam.push(playerRole)
|
||||||
|
})
|
||||||
|
|
||||||
|
blueRoles = super.getTeamRoles(blueTeam)
|
||||||
|
redRoles = super.getTeamRoles(redTeam)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const participant of liveMatch.participants) {
|
||||||
|
// Perks
|
||||||
|
participant.runes = participant.perks ?
|
||||||
|
super.getPerksImages(participant.perks.perkIds[0], participant.perks.perkSubStyle)
|
||||||
|
: {}
|
||||||
|
|
||||||
|
// Roles
|
||||||
|
if (needsRole) {
|
||||||
|
const roles = participant.teamId === 100 ? blueRoles : redRoles
|
||||||
|
participant.role = Object.entries(roles).find(([, champion]) => participant.championId === champion)![0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranks
|
||||||
|
const requestsParticipants = liveMatch.participants.map(p => this.getPlayerRank(p, region))
|
||||||
|
liveMatch.participants = await Promise.all(requestsParticipants)
|
||||||
|
|
||||||
|
return liveMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LiveMatchTransformer()
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
'use strict'
|
import { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } from 'App/helpers'
|
||||||
|
import Jax from 'App/Services/Jax'
|
||||||
|
import { MatchDto, ParticipantDto, ParticipantTimelineDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint'
|
||||||
|
import { Champion, Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats, SummonerSpell } from 'App/Models/Match'
|
||||||
|
import RoleIdentificationService, { ChampionsPlayRate } from 'App/Services/RoleIdentiticationService'
|
||||||
|
import { ChampionDTO, ItemDTO, PerkDTO, PerkStyleDTO, SummonerSpellDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint'
|
||||||
|
import { TeamStats } from 'App/Models/DetailedMatch'
|
||||||
|
|
||||||
const Jax = use('App/Services/Jax')
|
export interface PlayerRole {
|
||||||
const RoleIdentificationService = use('App/Services/RoleIdentificationService')
|
champion: number,
|
||||||
const { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } = use('App/helpers')
|
jungle?: boolean,
|
||||||
|
support?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
export default abstract class MatchTransformer {
|
||||||
* MatchTransformer class
|
protected champions: ChampionDTO[]
|
||||||
*
|
protected items: ItemDTO[]
|
||||||
* @class MatchTransformer
|
protected perks: PerkDTO[]
|
||||||
*/
|
protected perkstyles: PerkStyleDTO[]
|
||||||
class MatchTransformer {
|
protected summonerSpells: SummonerSpellDTO[]
|
||||||
|
protected championRoles: ChampionsPlayRate
|
||||||
|
protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number
|
||||||
/**
|
/**
|
||||||
* Get global Context with CDragon Data
|
* Get global Context with CDragon Data
|
||||||
*/
|
*/
|
||||||
async getContext() {
|
public async getContext () {
|
||||||
const items = await Jax.CDragon.items()
|
const items = await Jax.CDragon.items()
|
||||||
const champions = await Jax.CDragon.champions()
|
const champions = await Jax.CDragon.champions()
|
||||||
const perks = await Jax.CDragon.perks()
|
const perks = await Jax.CDragon.perks()
|
||||||
|
|
@ -26,7 +36,7 @@ class MatchTransformer {
|
||||||
this.perks = perks
|
this.perks = perks
|
||||||
this.perkstyles = perkstyles.styles
|
this.perkstyles = perkstyles.styles
|
||||||
this.summonerSpells = summonerSpells
|
this.summonerSpells = summonerSpells
|
||||||
this.championRoles = championRoles
|
this.championRoles = championRoles as ChampionsPlayRate
|
||||||
this.sortTeamByRole = sortTeamByRole
|
this.sortTeamByRole = sortTeamByRole
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,24 +44,31 @@ class MatchTransformer {
|
||||||
* Get champion specific data
|
* Get champion specific data
|
||||||
* @param id of the champion
|
* @param id of the champion
|
||||||
*/
|
*/
|
||||||
getChampion(id) {
|
public getChampion (id: number): Champion {
|
||||||
const champion = { ...this.champions.find(c => c.id === id) }
|
const originalChampionData = this.champions.find(c => c.id === id)
|
||||||
champion.icon = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${champion.squarePortraitPath.split('/assets/')[1].toLowerCase()}`
|
const icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/'
|
||||||
delete champion.squarePortraitPath
|
+ originalChampionData!.squarePortraitPath.split('/assets/')[1].toLowerCase()
|
||||||
return champion
|
|
||||||
|
return {
|
||||||
|
icon,
|
||||||
|
id: originalChampionData!.id,
|
||||||
|
name: originalChampionData!.name,
|
||||||
|
alias: originalChampionData!.alias,
|
||||||
|
roles: originalChampionData!.roles,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get global data about the match
|
* Get global data about the match
|
||||||
*/
|
*/
|
||||||
getGameInfos(match) {
|
public getGameInfos (match: MatchDto) {
|
||||||
return {
|
return {
|
||||||
map: match.mapId,
|
map: match.mapId,
|
||||||
gamemode: match.queueId,
|
gamemode: match.queueId,
|
||||||
date: match.gameCreation,
|
date: match.gameCreation,
|
||||||
region: match.platformId.toLowerCase(),
|
region: match.platformId.toLowerCase(),
|
||||||
season: getSeasonNumber(match.gameCreation),
|
season: getSeasonNumber(match.gameCreation),
|
||||||
time: match.gameDuration
|
time: match.gameDuration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,15 +79,15 @@ class MatchTransformer {
|
||||||
* @param detailed : detailed or not stats
|
* @param detailed : detailed or not stats
|
||||||
* @param teamStats : if detailed, the teamStats argument is mandatory
|
* @param teamStats : if detailed, the teamStats argument is mandatory
|
||||||
*/
|
*/
|
||||||
getPlayerData(match, player, detailed, teamStats = {}) {
|
public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats?: TeamStats) {
|
||||||
const identity = match.participantIdentities.find(p => p.participantId === player.participantId)
|
const identity = match.participantIdentities.find(p => p.participantId === player.participantId)
|
||||||
const name = identity.player.summonerName
|
const name = identity!.player.summonerName
|
||||||
const champion = this.getChampion(player.championId)
|
const champion = this.getChampion(player.championId)
|
||||||
const role = this.getRoleName(player.timeline, match.queueId)
|
const role = this.getRoleName(player.timeline, match.queueId)
|
||||||
const level = player.stats.champLevel
|
const level = player.stats.champLevel
|
||||||
|
|
||||||
// Regular stats / Full match stats
|
// Regular stats / Full match stats
|
||||||
const stats = {
|
const stats: Stats = {
|
||||||
kills: player.stats.kills,
|
kills: player.stats.kills,
|
||||||
deaths: player.stats.deaths,
|
deaths: player.stats.deaths,
|
||||||
assists: player.stats.assists,
|
assists: player.stats.assists,
|
||||||
|
|
@ -80,6 +97,9 @@ class MatchTransformer {
|
||||||
dmgChamp: player.stats.totalDamageDealtToChampions,
|
dmgChamp: player.stats.totalDamageDealtToChampions,
|
||||||
dmgObj: player.stats.damageDealtToObjectives,
|
dmgObj: player.stats.damageDealtToObjectives,
|
||||||
dmgTaken: player.stats.totalDamageTaken,
|
dmgTaken: player.stats.totalDamageTaken,
|
||||||
|
kp: 0,
|
||||||
|
kda: 0,
|
||||||
|
realKda: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stats.kills + stats.assists !== 0 && stats.deaths === 0) {
|
if (stats.kills + stats.assists !== 0 && stats.deaths === 0) {
|
||||||
|
|
@ -91,8 +111,9 @@ class MatchTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Percent stats / Per minute stats : only for detailed match
|
// Percent stats / Per minute stats : only for detailed match
|
||||||
let percentStats
|
let percentStats: PercentStats
|
||||||
if (detailed) {
|
if (detailed) {
|
||||||
|
teamStats = teamStats!
|
||||||
percentStats = {
|
percentStats = {
|
||||||
minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2),
|
minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2),
|
||||||
vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2),
|
vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2),
|
||||||
|
|
@ -123,13 +144,13 @@ class MatchTransformer {
|
||||||
stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1)
|
stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let primaryRune = null
|
let primaryRune: string | null = null
|
||||||
let secondaryRune = null
|
let secondaryRune: string | null = null
|
||||||
if (player.stats.perkPrimaryStyle) {
|
if (player.stats.perkPrimaryStyle) {
|
||||||
({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle))
|
({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle))
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = []
|
const items: (Item | null)[] = []
|
||||||
for (let i = 0; i < 6; i++) {
|
for (let i = 0; i < 6; i++) {
|
||||||
const id = player.stats['item' + i]
|
const id = player.stats['item' + i]
|
||||||
if (id === 0) {
|
if (id === 0) {
|
||||||
|
|
@ -138,22 +159,22 @@ class MatchTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = this.items.find(i => i.id === id)
|
const item = this.items.find(i => i.id === id)
|
||||||
const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase()
|
const itemUrl = item!.iconPath.split('/assets/')[1].toLowerCase()
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`,
|
image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`,
|
||||||
name: item.name,
|
name: item!.name,
|
||||||
description: item.description,
|
description: item!.description,
|
||||||
price: item.priceTotal
|
price: item!.priceTotal,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstSum = player.spell1Id
|
const firstSum = player.spell1Id
|
||||||
const secondSum = player.spell2Id
|
const secondSum = player.spell2Id
|
||||||
|
|
||||||
return {
|
const playerData: ParticipantDetails = {
|
||||||
name,
|
name,
|
||||||
summonerId: identity.player.summonerId,
|
summonerId: identity!.player.summonerId,
|
||||||
champion,
|
champion,
|
||||||
role,
|
role,
|
||||||
primaryRune,
|
primaryRune,
|
||||||
|
|
@ -163,8 +184,12 @@ class MatchTransformer {
|
||||||
firstSum,
|
firstSum,
|
||||||
secondSum,
|
secondSum,
|
||||||
stats,
|
stats,
|
||||||
percentStats,
|
|
||||||
}
|
}
|
||||||
|
if (detailed) {
|
||||||
|
playerData.percentStats = percentStats!
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,15 +197,17 @@ class MatchTransformer {
|
||||||
* @param perk0 primary perks id
|
* @param perk0 primary perks id
|
||||||
* @param perkSubStyle secondary perks category
|
* @param perkSubStyle secondary perks category
|
||||||
*/
|
*/
|
||||||
getPerksImages(perk0, perkSubStyle) {
|
public getPerksImages (perk0: number, perkSubStyle: number) {
|
||||||
const firstRune = this.perks.find(p => p.id === perk0)
|
const firstRune = this.perks.find(p => p.id === perk0)
|
||||||
const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase()
|
const firstRuneUrl = firstRune!.iconPath.split('/assets/')[1].toLowerCase()
|
||||||
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
|
const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}`
|
||||||
|
|
||||||
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
|
const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle)
|
||||||
|
|
||||||
const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null
|
const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null
|
||||||
const secondaryRune = secondRuneStyleUrl ? `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}` : ''
|
const secondaryRune = secondRuneStyleUrl ?
|
||||||
|
`https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}`
|
||||||
|
: ''
|
||||||
|
|
||||||
return { primaryRune, secondaryRune }
|
return { primaryRune, secondaryRune }
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +217,7 @@ class MatchTransformer {
|
||||||
* @param timeline from Riot Api
|
* @param timeline from Riot Api
|
||||||
* @param gamemode of the match to check if a role is needed
|
* @param gamemode of the match to check if a role is needed
|
||||||
*/
|
*/
|
||||||
getRoleName(timeline, gamemode) {
|
public getRoleName (timeline: ParticipantTimelineDto, gamemode: number) {
|
||||||
if (!queuesWithRole.includes(gamemode)) {
|
if (!queuesWithRole.includes(gamemode)) {
|
||||||
return 'NONE'
|
return 'NONE'
|
||||||
}
|
}
|
||||||
|
|
@ -206,30 +233,34 @@ class MatchTransformer {
|
||||||
* Return the 5 roles of a team based on champions
|
* Return the 5 roles of a team based on champions
|
||||||
* @param team 5 champions + smite from a team
|
* @param team 5 champions + smite from a team
|
||||||
*/
|
*/
|
||||||
getTeamRoles(team) {
|
public getTeamRoles (team: PlayerRole[]) {
|
||||||
const teamJunglers = team.filter(p => p.jungle && !p.support)
|
const teamJunglers = team.filter(p => p.jungle && !p.support)
|
||||||
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : null
|
const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined
|
||||||
const teamSupports = team.filter(p => p.support && !p.jungle)
|
const teamSupports = team.filter(p => p.support && !p.jungle)
|
||||||
const support = teamSupports.length === 1 ? teamSupports[0].champion : null
|
const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined
|
||||||
|
|
||||||
return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support)
|
return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update roles for a team if Riot's ones are badly identified
|
* Update roles for a team if Riot's ones are badly identified
|
||||||
* @param {Object} team 5 players data of the team
|
* @param team 5 players data of the team
|
||||||
* @param {Array} champs 5 champions + smite from the team
|
* @param champs 5 champions + smite from the team
|
||||||
* @param {Object} playerData data of the searched player, only for basic matches
|
* @param playerData data of the searched player, only for basic matches
|
||||||
*/
|
*/
|
||||||
updateTeamRoles(team, champs, playerData = null) {
|
public updateTeamRoles (
|
||||||
|
team: ParticipantBasic[] | ParticipantDetails[],
|
||||||
|
champs: PlayerRole[],
|
||||||
|
playerData?: ParticipantDetails
|
||||||
|
) {
|
||||||
// const actualRoles = [...new Set(team.map(p => p.role))]
|
// const actualRoles = [...new Set(team.map(p => p.role))]
|
||||||
// if (actualRoles.length === 5) {
|
// if (actualRoles.length === 5) {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
champs = this.getTeamRoles(champs)
|
const identifiedChamps = this.getTeamRoles(champs)
|
||||||
for (const summoner of team) {
|
for (const summoner of team) {
|
||||||
summoner.role = Object.entries(champs).find(([, champion]) => summoner.champion.id === champion)[0]
|
summoner.role = Object.entries(identifiedChamps).find(([, champion]) => summoner.champion.id === champion)![0]
|
||||||
|
|
||||||
if (playerData && summoner.champion.id === playerData.champion.id) {
|
if (playerData && summoner.champion.id === playerData.champion.id) {
|
||||||
playerData.role = summoner.role
|
playerData.role = summoner.role
|
||||||
|
|
@ -239,25 +270,31 @@ class MatchTransformer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} match from Riot Api
|
* @param match from Riot Api
|
||||||
* @param {Array} allyTeam 5 players of the first team
|
* @param allyTeam 5 players of the first team
|
||||||
* @param {Array} enemyTeam 5 players of the second team
|
* @param enemyTeam 5 players of the second team
|
||||||
* @param {Number} allyTeamId team id of the searched player, only for basic matches
|
* @param allyTeamId team id of the searched player, only for basic matches
|
||||||
* @param {Object} playerData data of the searched player, only for basic matches
|
* @param playerData data of the searched player, only for basic matches
|
||||||
*/
|
*/
|
||||||
getMatchRoles(match, allyTeam, enemyTeam, allyTeamId = 100, playerData = null) {
|
public getMatchRoles (
|
||||||
|
match: MatchDto,
|
||||||
|
allyTeam: ParticipantBasic[] | ParticipantDetails[],
|
||||||
|
enemyTeam: ParticipantBasic[] | ParticipantDetails[],
|
||||||
|
allyTeamId = 100,
|
||||||
|
playerData?: ParticipantDetails
|
||||||
|
) {
|
||||||
if (!this.championRoles || !queuesWithRole.includes(match.queueId)) {
|
if (!this.championRoles || !queuesWithRole.includes(match.queueId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let allyChamps = []
|
let allyChamps: PlayerRole[] = []
|
||||||
let enemyChamps = []
|
let enemyChamps: PlayerRole[] = []
|
||||||
match.participants.map(p => {
|
match.participants.map(p => {
|
||||||
const items = [p.stats.item0, p.stats.item1, p.stats.item2, p.stats.item3, p.stats.item4, p.stats.item5]
|
const items = [p.stats.item0, p.stats.item1, p.stats.item2, p.stats.item3, p.stats.item4, p.stats.item5]
|
||||||
const playerRole = {
|
const playerRole = {
|
||||||
champion: p.championId,
|
champion: p.championId,
|
||||||
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
jungle: p.spell1Id === 11 || p.spell2Id === 11,
|
||||||
support: supportItems.some(suppItem => items.includes(suppItem))
|
support: supportItems.some(suppItem => items.includes(suppItem)),
|
||||||
}
|
}
|
||||||
p.teamId === allyTeamId ? allyChamps.push(playerRole) : enemyChamps.push(playerRole)
|
p.teamId === allyTeamId ? allyChamps.push(playerRole) : enemyChamps.push(playerRole)
|
||||||
})
|
})
|
||||||
|
|
@ -273,16 +310,16 @@ class MatchTransformer {
|
||||||
* Get Summoner Spell Data from CDragon
|
* Get Summoner Spell Data from CDragon
|
||||||
* @param id of the summonerSpell
|
* @param id of the summonerSpell
|
||||||
*/
|
*/
|
||||||
getSummonerSpell(id) {
|
public getSummonerSpell (id: number): SummonerSpell | null {
|
||||||
if (id === 0) return null
|
if (id === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
const spell = this.summonerSpells.find(s => s.id === id)
|
const spell = this.summonerSpells.find(s => s.id === id)
|
||||||
const spellName = spell.iconPath.split('/assets/')[1].toLowerCase()
|
const spellName = spell!.iconPath.split('/assets/')[1].toLowerCase()
|
||||||
return {
|
return {
|
||||||
name: spell.name,
|
name: spell!.name,
|
||||||
description: spell.description,
|
description: spell!.description,
|
||||||
icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`
|
icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MatchTransformer
|
|
||||||
53
server/app/Validators/DetailedMatchValidator.ts
Normal file
53
server/app/Validators/DetailedMatchValidator.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class DetailedMatchValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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({
|
||||||
|
gameId: schema.number(),
|
||||||
|
region: schema.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
57
server/app/Validators/MatchesIndexValidator.ts
Normal file
57
server/app/Validators/MatchesIndexValidator.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class MatchesIndexValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
accountId: schema.string(),
|
||||||
|
region: schema.string(),
|
||||||
|
gameIds: schema.array().members(
|
||||||
|
schema.number()
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
55
server/app/Validators/SummonerBasicValidator.ts
Normal file
55
server/app/Validators/SummonerBasicValidator.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { rules, schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class SummonerBasicValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
54
server/app/Validators/SummonerChampionValidator.ts
Normal file
54
server/app/Validators/SummonerChampionValidator.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class SummonerChampionValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
53
server/app/Validators/SummonerLiveValidator.ts
Normal file
53
server/app/Validators/SummonerLiveValidator.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class SummonerLiveValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
55
server/app/Validators/SummonerOverviewValidator.ts
Normal file
55
server/app/Validators/SummonerOverviewValidator.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class SummonerOverviewValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
accountId: schema.string(),
|
||||||
|
region: schema.string(),
|
||||||
|
season: schema.number.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
53
server/app/Validators/SummonerRecordValidator.ts
Normal file
53
server/app/Validators/SummonerRecordValidator.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||||
|
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||||
|
|
||||||
|
export default class SummonerRecordValidator {
|
||||||
|
constructor (private ctx: HttpContextContract) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defining a 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(),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `schema` first gets compiled to a reusable function and then that compiled
|
||||||
|
* function validates the data at runtime.
|
||||||
|
*
|
||||||
|
* Since, compiling the schema is an expensive operation, you must always cache it by
|
||||||
|
* defining a unique cache key. The simplest way is to use the current request route
|
||||||
|
* key, which is a combination of the route pattern and HTTP method.
|
||||||
|
*/
|
||||||
|
public cacheKey = this.ctx.routeKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom messages for validation failures. You can make use of dot notation `(.)`
|
||||||
|
* 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 = {}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/**
|
|
||||||
* League of Legends queues with defined role for each summoner
|
|
||||||
*/
|
|
||||||
const queuesWithRole = [
|
|
||||||
0, // Custom
|
|
||||||
400, // Draft
|
|
||||||
420, // Solo/Duo
|
|
||||||
430, // Blind,
|
|
||||||
440, // Flex
|
|
||||||
700, // Clash
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* League of Legends seasons timestamps
|
|
||||||
*/
|
|
||||||
const seasons = {
|
|
||||||
0: 9,
|
|
||||||
1578628800000: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* League of Legends all support item ids
|
|
||||||
*/
|
|
||||||
const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864]
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
queuesWithRole,
|
|
||||||
seasons,
|
|
||||||
supportItems,
|
|
||||||
/**
|
|
||||||
* Get season number for a match
|
|
||||||
*/
|
|
||||||
getSeasonNumber(timestamp) {
|
|
||||||
const arrSeasons = Object.keys(seasons)
|
|
||||||
arrSeasons.push(timestamp)
|
|
||||||
arrSeasons.sort()
|
|
||||||
const indexSeason = arrSeasons.indexOf(timestamp) - 1
|
|
||||||
return seasons[arrSeasons[indexSeason]]
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Sort array of Roles according to a specific order
|
|
||||||
* @param a first role
|
|
||||||
* @param b second role
|
|
||||||
*/
|
|
||||||
sortTeamByRole(a, b) {
|
|
||||||
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
|
||||||
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
48
server/app/helpers.ts
Normal file
48
server/app/helpers.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { ParticipantBasic, ParticipantDetails } from './Models/Match'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 seasons timestamps
|
||||||
|
*/
|
||||||
|
export const seasons = {
|
||||||
|
0: 9,
|
||||||
|
1578628800000: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort array of Players by roles according to a specific order
|
||||||
|
* @param a first player
|
||||||
|
* @param b second player
|
||||||
|
*/
|
||||||
|
export function sortTeamByRole (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) {
|
||||||
|
const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT']
|
||||||
|
return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role)
|
||||||
|
}
|
||||||
19
server/commands/index.ts
Normal file
19
server/commands/index.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { listDirectoryFiles } from '@adonisjs/ace'
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index.js'])
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/framework/src/Env')} */
|
|
||||||
const Env = use('Env')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Application Name
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This value is the name of your application and can used when you
|
|
||||||
| need to place the application's name in a email, view or
|
|
||||||
| other location.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
name: Env.get('APP_NAME', 'AdonisJs'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| App Key
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| App key is a randomly generated 16 or 32 characters long string required
|
|
||||||
| to encrypt cookies, sessions and other sensitive data.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
appKey: Env.getOrFail('APP_KEY'),
|
|
||||||
|
|
||||||
http: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Allow Method Spoofing
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Method spoofing allows to make requests by spoofing the http verb.
|
|
||||||
| Which means you can make a GET request but instruct the server to
|
|
||||||
| treat as a POST or PUT request. If you want this feature, set the
|
|
||||||
| below value to true.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
allowMethodSpoofing: true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Trust Proxy
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Trust proxy defines whether X-Forwarded-* headers should be trusted or not.
|
|
||||||
| When your application is behind a proxy server like nginx, these values
|
|
||||||
| are set automatically and should be trusted. Apart from setting it
|
|
||||||
| to true or false Adonis supports handful or ways to allow proxy
|
|
||||||
| values. Read documentation for that.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
trustProxy: false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Subdomains
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Offset to be used for returning subdomains for a given request.For
|
|
||||||
| majority of applications it will be 2, until you have nested
|
|
||||||
| sudomains.
|
|
||||||
| cheatsheet.adonisjs.com - offset - 2
|
|
||||||
| virk.cheatsheet.adonisjs.com - offset - 3
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
subdomainOffset: 2,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| JSONP Callback
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Default jsonp callback to be used when callback query string is missing
|
|
||||||
| in request url.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
jsonpCallback: 'callback',
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Etag
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Set etag on all HTTP response. In order to disable for selected routes,
|
|
||||||
| you can call the `response.send` with an options object as follows.
|
|
||||||
|
|
|
||||||
| response.send('Hello', { ignoreEtag: true })
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
etag: false
|
|
||||||
},
|
|
||||||
|
|
||||||
views: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Cache Views
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define whether or not to cache the compiled view. Set it to true in
|
|
||||||
| production to optimize view loading time.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
cache: Env.get('CACHE_VIEWS', true)
|
|
||||||
},
|
|
||||||
|
|
||||||
static: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Dot Files
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define how to treat dot files when trying to server static resources.
|
|
||||||
| By default it is set to ignore, which will pretend that dotfiles
|
|
||||||
| does not exists.
|
|
||||||
|
|
|
||||||
| Can be one of the following
|
|
||||||
| ignore, deny, allow
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
dotfiles: 'ignore',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| ETag
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Enable or disable etag generation
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
etag: true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Extensions
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Set file extension fallbacks. When set, if a file is not found, the given
|
|
||||||
| extensions will be added to the file name and search for. The first
|
|
||||||
| that exists will be served. Example: ['html', 'htm'].
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
extensions: false
|
|
||||||
},
|
|
||||||
|
|
||||||
locales: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Loader
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The loader to be used for fetching and updating locales. Below is the
|
|
||||||
| list of available options.
|
|
||||||
|
|
|
||||||
| file, database
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
loader: 'file',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Locale
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Default locale to be used by Antl provider. You can always switch drivers
|
|
||||||
| in runtime or use the official Antl middleware to detect the driver
|
|
||||||
| based on HTTP headers/query string.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
locale: 'en'
|
|
||||||
},
|
|
||||||
|
|
||||||
logger: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Transport
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Transport to be used for logging messages. You can have multiple
|
|
||||||
| transports using same driver.
|
|
||||||
|
|
|
||||||
| Available drivers are: `file` and `console`.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
transport: 'console',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Console Transport
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Using `console` driver for logging. This driver writes to `stdout`
|
|
||||||
| and `stderr`
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
console: {
|
|
||||||
driver: 'console',
|
|
||||||
name: 'adonis-app',
|
|
||||||
level: 'info'
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| File Transport
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| File transport uses file driver and writes log messages for a given
|
|
||||||
| file inside `tmp` directory for your app.
|
|
||||||
|
|
|
||||||
| For a different directory, set an absolute path for the filename.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
file: {
|
|
||||||
driver: 'file',
|
|
||||||
name: 'adonis-app',
|
|
||||||
filename: 'adonis.log',
|
|
||||||
level: 'info'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Generic Cookie Options
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The following cookie options are generic settings used by AdonisJs to create
|
|
||||||
| cookies. However, some parts of the application like `sessions` can have
|
|
||||||
| separate settings for cookies inside `config/session.js`.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
cookie: {
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: false,
|
|
||||||
path: '/',
|
|
||||||
maxAge: 7200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
225
server/config/app.ts
Normal file
225
server/config/app.ts
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
/**
|
||||||
|
* 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 { LoggerConfig } from '@ioc:Adonis/Core/Logger'
|
||||||
|
import { RequestConfig } from '@ioc:Adonis/Core/Request'
|
||||||
|
import { ResponseConfig } from '@ioc:Adonis/Core/Response'
|
||||||
|
import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler'
|
||||||
|
|
||||||
|
type HttpConfig = RequestConfig & ResponseConfig
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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.getOrFail('APP_KEY') as string
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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: HttpConfig = {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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 to JSON
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| 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 `forceContentNegotiationToJSON = true`, you negotiate with the
|
||||||
|
| server in advance to always return JSON without relying on the client
|
||||||
|
| to set the header explicitly.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
forceContentNegotiationToJSON: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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') as string,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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') as string,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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: [],
|
||||||
|
}
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/framework/src/Env')} */
|
|
||||||
const Env = use('Env')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Authenticator
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Authentication is a combination of serializer and scheme with extra
|
|
||||||
| config to define on how to authenticate a user.
|
|
||||||
|
|
|
||||||
| Available Schemes - basic, session, jwt, api
|
|
||||||
| Available Serializers - lucid, database
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
authenticator: 'jwt',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Session
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Session authenticator makes use of sessions to authenticate a user.
|
|
||||||
| Session authentication is always persistent.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
session: {
|
|
||||||
serializer: 'lucid',
|
|
||||||
model: 'App/Models/User',
|
|
||||||
scheme: 'session',
|
|
||||||
uid: 'email',
|
|
||||||
password: 'password'
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Basic Auth
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The basic auth authenticator uses basic auth header to authenticate a
|
|
||||||
| user.
|
|
||||||
|
|
|
||||||
| NOTE:
|
|
||||||
| This scheme is not persistent and users are supposed to pass
|
|
||||||
| login credentials on each request.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
basic: {
|
|
||||||
serializer: 'lucid',
|
|
||||||
model: 'App/Models/User',
|
|
||||||
scheme: 'basic',
|
|
||||||
uid: 'email',
|
|
||||||
password: 'password'
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Jwt
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The jwt authenticator works by passing a jwt token on each HTTP request
|
|
||||||
| via HTTP `Authorization` header.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
jwt: {
|
|
||||||
serializer: 'lucid',
|
|
||||||
model: 'App/Models/User',
|
|
||||||
scheme: 'jwt',
|
|
||||||
uid: 'email',
|
|
||||||
password: 'password',
|
|
||||||
options: {
|
|
||||||
secret: Env.get('APP_KEY')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Api
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The Api scheme makes use of API personal tokens to authenticate a user.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
api: {
|
|
||||||
serializer: 'lucid',
|
|
||||||
model: 'App/Models/User',
|
|
||||||
scheme: 'api',
|
|
||||||
uid: 'email',
|
|
||||||
password: 'password'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| JSON Parser
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Below settings are applied when the request body contains a JSON payload.
|
|
||||||
| If you want body parser to ignore JSON payloads, then simply set `types`
|
|
||||||
| to an empty array.
|
|
||||||
*/
|
|
||||||
json: {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| limit
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Defines the limit of JSON that can be sent by the client. If payload
|
|
||||||
| is over 1mb it will not be processed.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
limit: '1mb',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| strict
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| When `strict` is set to true, body parser will only parse Arrays and
|
|
||||||
| Object. Otherwise everything parseable by `JSON.parse` is parsed.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
strict: true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| types
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Which content types are processed as JSON payloads. You are free to
|
|
||||||
| add your own types here, but the request body should be parseable
|
|
||||||
| by `JSON.parse` method.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
types: [
|
|
||||||
'application/json',
|
|
||||||
'application/json-patch+json',
|
|
||||||
'application/vnd.api+json',
|
|
||||||
'application/csp-report'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Raw Parser
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
raw: {
|
|
||||||
types: [
|
|
||||||
'text/*'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Form Parser
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
form: {
|
|
||||||
types: [
|
|
||||||
'application/x-www-form-urlencoded'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Files Parser
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
files: {
|
|
||||||
types: [
|
|
||||||
'multipart/form-data'
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Max Size
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Below value is the max size of all the files uploaded to the server. It
|
|
||||||
| is validated even before files have been processed and hard exception
|
|
||||||
| is thrown.
|
|
||||||
|
|
|
||||||
| Consider setting a reasonable value here, otherwise people may upload GB's
|
|
||||||
| of files which will keep your server busy.
|
|
||||||
|
|
|
||||||
| Also this value is considered when `autoProcess` is set to true.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
maxSize: '20mb',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Auto Process
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Whether or not to auto-process files. Since HTTP servers handle files via
|
|
||||||
| couple of specific endpoints. It is better to set this value off and
|
|
||||||
| manually process the files when required.
|
|
||||||
|
|
|
||||||
| This value can contain a boolean or an array of route patterns
|
|
||||||
| to be autoprocessed.
|
|
||||||
*/
|
|
||||||
autoProcess: true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Process Manually
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The list of routes that should not process files and instead rely on
|
|
||||||
| manual process. This list should only contain routes when autoProcess
|
|
||||||
| is to true. Otherwise everything is processed manually.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
processManually: []
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Temporary file name
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define a function, which should return a string to be used as the
|
|
||||||
| tmp file name.
|
|
||||||
|
|
|
||||||
| If not defined, Bodyparser will use `uuid` as the tmp file name.
|
|
||||||
|
|
|
||||||
| To be defined as. If you are defining the function, then do make sure
|
|
||||||
| to return a value from it.
|
|
||||||
|
|
|
||||||
| tmpFileName () {
|
|
||||||
| return 'some-unique-value'
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
186
server/config/bodyparser.ts
Normal file
186
server/config/bodyparser.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
/**
|
||||||
|
* 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: {},
|
||||||
|
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',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Origin
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Set a list of origins to be allowed. The value can be one of the following
|
|
||||||
|
|
|
||||||
| 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 current request origin
|
|
||||||
| Function - Receives the current origin 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
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| HTTP methods to be allowed. The value can be one of the following
|
|
||||||
|
|
|
||||||
| String - Comma separated list of allowed methods
|
|
||||||
| Array - An array of allowed methods
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Headers
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| List of headers to be allowed via Access-Control-Request-Headers header.
|
|
||||||
| The value can be one of the following.
|
|
||||||
|
|
|
||||||
| Boolean: true - Allow current request headers
|
|
||||||
| Boolean: false - Disallow all
|
|
||||||
| String - Comma separated list of allowed headers
|
|
||||||
| Array - An array of allowed headers
|
|
||||||
| String: * - A wildcard to allow current request 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 via `Access-Control-Expose-Headers`
|
|
||||||
| header. The value can be one of the following.
|
|
||||||
|
|
|
||||||
| Boolean: false - Disallow all
|
|
||||||
| String: Comma separated list of allowed headers
|
|
||||||
| Array - An array of allowed headers
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
exposeHeaders: false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Credentials
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define Access-Control-Allow-Credentials header. It should always be a
|
|
||||||
| boolean.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
credentials: false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| MaxAge
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define Access-Control-Allow-Max-Age
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
maxAge: 90
|
|
||||||
}
|
|
||||||
144
server/config/cors.ts
Normal file
144
server/config/cors.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/framework/src/Env')} */
|
|
||||||
const Env = use('Env')
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/ignitor/src/Helpers')} */
|
|
||||||
const Helpers = use('Helpers')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Connection
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Connection defines the default connection settings to be used while
|
|
||||||
| interacting with SQL databases.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
connection: Env.get('DB_CONNECTION', 'mongodb'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Sqlite
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Sqlite is a flat file database and can be a good choice for a development
|
|
||||||
| environment.
|
|
||||||
|
|
|
||||||
| npm i --save sqlite3
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
sqlite: {
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`)
|
|
||||||
},
|
|
||||||
useNullAsDefault: true,
|
|
||||||
debug: Env.get('DB_DEBUG', false)
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| MySQL
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here we define connection settings for MySQL database.
|
|
||||||
|
|
|
||||||
| npm i --save mysql
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
mysql: {
|
|
||||||
client: 'mysql',
|
|
||||||
connection: {
|
|
||||||
host: Env.get('DB_HOST', 'localhost'),
|
|
||||||
port: Env.get('DB_PORT', ''),
|
|
||||||
user: Env.get('DB_USER', 'root'),
|
|
||||||
password: Env.get('DB_PASSWORD', ''),
|
|
||||||
database: Env.get('DB_DATABASE', 'adonis')
|
|
||||||
},
|
|
||||||
debug: Env.get('DB_DEBUG', false)
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| PostgreSQL
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here we define connection settings for PostgreSQL database.
|
|
||||||
|
|
|
||||||
| npm i --save pg
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
pg: {
|
|
||||||
client: 'pg',
|
|
||||||
connection: {
|
|
||||||
host: Env.get('DB_HOST', 'localhost'),
|
|
||||||
port: Env.get('DB_PORT', ''),
|
|
||||||
user: Env.get('DB_USER', 'root'),
|
|
||||||
password: Env.get('DB_PASSWORD', ''),
|
|
||||||
database: Env.get('DB_DATABASE', 'adonis')
|
|
||||||
},
|
|
||||||
debug: Env.get('DB_DEBUG', false)
|
|
||||||
},
|
|
||||||
|
|
||||||
mongodb: {
|
|
||||||
client: 'mongodb',
|
|
||||||
connectionString: Env.get('DB_CONNECTION_STRING', ''),
|
|
||||||
connection: {
|
|
||||||
host: Env.get('DB_HOST', 'localhost'),
|
|
||||||
port: Env.get('DB_PORT', 27017),
|
|
||||||
username: Env.get('DB_USER', 'admin'),
|
|
||||||
password: Env.get('DB_PASSWORD', ''),
|
|
||||||
database: Env.get('DB_DATABASE', 'adonis'),
|
|
||||||
options: {
|
|
||||||
// replicaSet: Env.get('DB_REPLICA_SET', '')
|
|
||||||
// ssl: Env.get('DB_SSL, '')
|
|
||||||
// connectTimeoutMS: Env.get('DB_CONNECT_TIMEOUT_MS', 15000),
|
|
||||||
// socketTimeoutMS: Env.get('DB_SOCKET_TIMEOUT_MS', 180000),
|
|
||||||
// w: Env.get('DB_W, 0),
|
|
||||||
// readPreference: Env.get('DB_READ_PREFERENCE', 'secondary'),
|
|
||||||
// authSource: Env.get('DB_AUTH_SOURCE', ''),
|
|
||||||
// authMechanism: Env.get('DB_AUTH_MECHANISM', ''),
|
|
||||||
// other options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/framework/src/Env')} */
|
|
||||||
const Env = use('Env')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Driver
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Driver to be used for hashing values. The same driver is used by the
|
|
||||||
| auth module too.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
driver: Env.get('HASH_DRIVER', 'bcrypt'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Bcrypt
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt
|
|
||||||
| package is used internally.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
bcrypt: {
|
|
||||||
rounds: 10
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Argon
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Config related to argon. https://www.npmjs.com/package/argon2 package is
|
|
||||||
| used internally.
|
|
||||||
|
|
|
||||||
| Since argon is optional, you will have to install the dependency yourself
|
|
||||||
|
|
|
||||||
|============================================================================
|
|
||||||
| npm i argon2
|
|
||||||
|============================================================================
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
argon: {
|
|
||||||
type: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
server/config/hash.ts
Normal file
75
server/config/hash.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/**
|
||||||
|
* 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') as '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
|
||||||
14
server/config/mongodb.ts
Normal file
14
server/config/mongodb.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { MongodbConfig } from '@ioc:Mongodb/Database'
|
||||||
|
import Env from '@ioc:Adonis/Core/Env'
|
||||||
|
|
||||||
|
const config: MongodbConfig = {
|
||||||
|
default: 'mongodb',
|
||||||
|
connections: {
|
||||||
|
mongodb: {
|
||||||
|
url: Env.getOrFail('MONGODB_URL') as string,
|
||||||
|
database: Env.getOrFail('MONGODB_DATABASE') as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Redis Configuaration
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here we define the configuration for redis server. A single application
|
|
||||||
| can make use of multiple redis connections using the redis provider.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Env = use('Env')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| connection
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Redis connection to be used by default.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
connection: Env.get('REDIS_CONNECTION', 'local'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| local connection config
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Configuration for a named connection.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
local: {
|
|
||||||
host: Env.get('REDIS_HOST', '127.0.0.1'),
|
|
||||||
port: Env.get('REDIS_PORT', 6379),
|
|
||||||
password: Env.get('REDIS_PASSWORD', null),
|
|
||||||
db: 0,
|
|
||||||
keyPrefix: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| cluster config
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Below is the configuration for the redis cluster.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
cluster: {
|
|
||||||
clusters: [{
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 6379,
|
|
||||||
password: null,
|
|
||||||
db: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 6380,
|
|
||||||
password: null,
|
|
||||||
db: 0
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
server/config/redis.ts
Normal file
48
server/config/redis.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* 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', 'local') as 'local',
|
||||||
|
|
||||||
|
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', '127.0.0.1') as string,
|
||||||
|
port: Env.get('REDIS_PORT', '6379') as string,
|
||||||
|
password: Env.get('REDIS_PASSWORD', '') as string,
|
||||||
|
db: 0,
|
||||||
|
keyPrefix: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default redisConfig
|
||||||
30
server/contracts/events.ts
Normal file
30
server/contracts/events.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
21
server/contracts/hash.ts
Normal file
21
server/contracts/hash.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* 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' {
|
||||||
|
import { HashDrivers } from '@ioc:Adonis/Core/Hash'
|
||||||
|
|
||||||
|
interface HashersList {
|
||||||
|
bcrypt: {
|
||||||
|
config: BcryptConfig,
|
||||||
|
implementation: BcryptContract,
|
||||||
|
},
|
||||||
|
argon: {
|
||||||
|
config: ArgonConfig,
|
||||||
|
implementation: ArgonContract,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
3
server/contracts/league.ts
Normal file
3
server/contracts/league.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
declare module '@ioc:Adonis/League' {
|
||||||
|
|
||||||
|
}
|
||||||
12
server/contracts/redis.ts
Normal file
12
server/contracts/redis.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Factory
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Factories are used to define blueprints for database tables or Lucid
|
|
||||||
| models. Later you can use these blueprints to seed your database
|
|
||||||
| with dummy data.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/lucid/src/Factory')} */
|
|
||||||
// const Factory = use('Factory')
|
|
||||||
|
|
||||||
// Factory.blueprint('App/Models/User', (faker) => {
|
|
||||||
// return {
|
|
||||||
// username: faker.username()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/lucid/src/Schema')} */
|
|
||||||
const Schema = use('Schema')
|
|
||||||
|
|
||||||
class MatchSchema extends Schema {
|
|
||||||
up () {
|
|
||||||
this.create('matches', (collection) => {
|
|
||||||
collection.index('gameId', {gameId: 1})
|
|
||||||
collection.index('summoner_puuid', {summoner_puuid: 1})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
down () {
|
|
||||||
this.drop('matches')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MatchSchema
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/lucid/src/Schema')} */
|
|
||||||
const Schema = use('Schema')
|
|
||||||
|
|
||||||
class SummonerSchema extends Schema {
|
|
||||||
up () {
|
|
||||||
this.create('summoners', (collection) => {
|
|
||||||
collection.index('puuid', {puuid: 1})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
down () {
|
|
||||||
this.drop('summoners')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SummonerSchema
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/** @type {import('@adonisjs/lucid/src/Schema')} */
|
|
||||||
const Schema = use('Schema')
|
|
||||||
|
|
||||||
class DetailedMatchSchema extends Schema {
|
|
||||||
up () {
|
|
||||||
this.create('detailed_matches', (collection) => {
|
|
||||||
collection.index('gameId', {gameId: 1})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
down () {
|
|
||||||
this.drop('detailed_matches')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DetailedMatchSchema
|
|
||||||
9
server/mongodb/migrations/1601816721286_match.ts
Normal file
9
server/mongodb/migrations/1601816721286_match.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import BaseMigration from '@ioc:Mongodb/Migration'
|
||||||
|
|
||||||
|
export default class MatchMigration extends BaseMigration {
|
||||||
|
public up (): void {
|
||||||
|
this.createCollection('matches')
|
||||||
|
this.createIndex('matches', 'gameId')
|
||||||
|
this.createIndex('matches', 'summoner_puuid')
|
||||||
|
}
|
||||||
|
}
|
||||||
8
server/mongodb/migrations/1601819828861_summoner.ts
Normal file
8
server/mongodb/migrations/1601819828861_summoner.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import BaseMigration from '@ioc:Mongodb/Migration'
|
||||||
|
|
||||||
|
export default class SummonerMigration extends BaseMigration {
|
||||||
|
public up (): void {
|
||||||
|
this.createCollection('summoners')
|
||||||
|
this.createIndex('summoners', 'puuid')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import BaseMigration from '@ioc:Mongodb/Migration'
|
||||||
|
|
||||||
|
export default class DetailedMatchMigration extends BaseMigration {
|
||||||
|
public up (): void {
|
||||||
|
this.createCollection('detailed_matches')
|
||||||
|
this.createIndex('detailed_matches', 'gameId')
|
||||||
|
}
|
||||||
|
}
|
||||||
6499
server/package-lock.json
generated
6499
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,40 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "adonis-api-app",
|
"name": "leaguestats-api",
|
||||||
"version": "4.1.0",
|
"version": "0.0.0",
|
||||||
"adonis-version": "4.1.0",
|
|
||||||
"description": "Adonisjs boilerplate for API server with pre-configured JWT",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "adonis serve --dev",
|
|
||||||
"start": "node server.js",
|
|
||||||
"test": "node ace test"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"adonisjs",
|
|
||||||
"adonis-app"
|
|
||||||
],
|
|
||||||
"author": "",
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"scripts": {
|
||||||
"@adonisjs/ace": "^5.0.8",
|
"build": "node ace build",
|
||||||
"@adonisjs/auth": "^3.1.0",
|
"dev": "npm run start",
|
||||||
"@adonisjs/bodyparser": "^2.0.5",
|
"start": "node ace serve --watch",
|
||||||
"@adonisjs/cors": "^1.0.7",
|
"lint": "eslint . --ext=.ts"
|
||||||
"@adonisjs/fold": "^4.0.9",
|
|
||||||
"@adonisjs/framework": "^5.0.9",
|
|
||||||
"@adonisjs/ignitor": "^2.0.8",
|
|
||||||
"@adonisjs/lucid": "^6.2.0",
|
|
||||||
"@adonisjs/redis": "^2.0.7",
|
|
||||||
"bee-queue": "^1.2.3",
|
|
||||||
"got": "^11.5.2",
|
|
||||||
"lucid-mongo": "^3.1.6",
|
|
||||||
"mongodb-core": "^3.2.7",
|
|
||||||
"request": "^2.88.2",
|
|
||||||
"riot-ratelimiter": "^0.1.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {
|
||||||
"autoload": {
|
"@adonisjs/assembler": "^2.1.5",
|
||||||
"App": "./app"
|
"adonis-preset-ts": "^1.0.4",
|
||||||
|
"eslint": "^7.10.0",
|
||||||
|
"eslint-plugin-adonis": "^1.0.15",
|
||||||
|
"pino-pretty": "^4.2.1",
|
||||||
|
"typescript": "^4.0.3",
|
||||||
|
"youch": "^2.1.0",
|
||||||
|
"youch-terminal": "^1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@adonisjs/ace": "^6.9.4",
|
||||||
|
"@adonisjs/core": "^5.0.0-preview-rc-1.12",
|
||||||
|
"@adonisjs/fold": "^6.4.1",
|
||||||
|
"@adonisjs/redis": "^4.1.2",
|
||||||
|
"@fightmegg/riot-rate-limiter": "0.0.11",
|
||||||
|
"@zakodium/adonis-mongodb": "^0.3.0",
|
||||||
|
"got": "^11.7.0",
|
||||||
|
"proxy-addr": "^2.0.6",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"request": "^2.88.2",
|
||||||
|
"riot-ratelimiter": "^0.1.5",
|
||||||
|
"source-map-support": "^0.5.19"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue