From cdabe6a2ca74e99491e7903eafab238d7d7e8882 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 13:37:05 +0200 Subject: [PATCH 01/33] chore: add adonis 5 boilerplate --- server-new/.adonisrc.json | 26 + server-new/.editorconfig | 14 + server-new/.env.example | 4 + server-new/.eslintignore | 1 + server-new/.eslintrc.json | 5 + server-new/.gitignore | 7 + server-new/ace | 19 + server-new/app/Exceptions/Handler.ts | 23 + server-new/commands/index.ts | 19 + server-new/config/app.ts | 225 ++ server-new/config/bodyparser.ts | 186 ++ server-new/config/cors.ts | 134 + server-new/config/hash.ts | 75 + server-new/contracts/events.ts | 30 + server-new/contracts/hash.ts | 21 + server-new/package-lock.json | 4461 ++++++++++++++++++++++++++ server-new/package.json | 29 + server-new/providers/AppProvider.ts | 22 + server-new/server.ts | 22 + server-new/start/kernel.ts | 44 + server-new/start/routes.ts | 25 + server-new/tsconfig.json | 29 + 22 files changed, 5421 insertions(+) create mode 100644 server-new/.adonisrc.json create mode 100644 server-new/.editorconfig create mode 100644 server-new/.env.example create mode 100644 server-new/.eslintignore create mode 100644 server-new/.eslintrc.json create mode 100644 server-new/.gitignore create mode 100644 server-new/ace create mode 100644 server-new/app/Exceptions/Handler.ts create mode 100644 server-new/commands/index.ts create mode 100644 server-new/config/app.ts create mode 100644 server-new/config/bodyparser.ts create mode 100644 server-new/config/cors.ts create mode 100644 server-new/config/hash.ts create mode 100644 server-new/contracts/events.ts create mode 100644 server-new/contracts/hash.ts create mode 100644 server-new/package-lock.json create mode 100644 server-new/package.json create mode 100644 server-new/providers/AppProvider.ts create mode 100644 server-new/server.ts create mode 100644 server-new/start/kernel.ts create mode 100644 server-new/start/routes.ts create mode 100644 server-new/tsconfig.json diff --git a/server-new/.adonisrc.json b/server-new/.adonisrc.json new file mode 100644 index 0000000..310b011 --- /dev/null +++ b/server-new/.adonisrc.json @@ -0,0 +1,26 @@ +{ + "typescript": true, + "commands": [ + "./commands", + "@adonisjs/core/build/commands" + ], + "exceptionHandlerNamespace": "App/Exceptions/Handler", + "aliases": { + "App": "app", + "Contracts": "contracts", + "Config": "config", + "Database": "database" + }, + "preloads": [ + "./start/routes", + "./start/kernel" + ], + "providers": [ + "./providers/AppProvider", + "@adonisjs/core" + ], + "metaFiles": [ + ".env", + ".adonisrc.json" + ] +} diff --git a/server-new/.editorconfig b/server-new/.editorconfig new file mode 100644 index 0000000..9c61e39 --- /dev/null +++ b/server-new/.editorconfig @@ -0,0 +1,14 @@ + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.json] +insert_final_newline = ignore + +[*.md] +trim_trailing_whitespace = false diff --git a/server-new/.env.example b/server-new/.env.example new file mode 100644 index 0000000..ecbc796 --- /dev/null +++ b/server-new/.env.example @@ -0,0 +1,4 @@ +PORT= +HOST= +NODE_ENV= +APP_KEY= diff --git a/server-new/.eslintignore b/server-new/.eslintignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/server-new/.eslintignore @@ -0,0 +1 @@ +build diff --git a/server-new/.eslintrc.json b/server-new/.eslintrc.json new file mode 100644 index 0000000..fc3312c --- /dev/null +++ b/server-new/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "plugin:adonis/typescriptApp" + ] +} diff --git a/server-new/.gitignore b/server-new/.gitignore new file mode 100644 index 0000000..af14e82 --- /dev/null +++ b/server-new/.gitignore @@ -0,0 +1,7 @@ +node_modules +build +coverage +.vscode +.DS_STORE +.env +tmp diff --git a/server-new/ace b/server-new/ace new file mode 100644 index 0000000..54e6f8e --- /dev/null +++ b/server-new/ace @@ -0,0 +1,19 @@ +/* +|-------------------------------------------------------------------------- +| Ace Commands +|-------------------------------------------------------------------------- +| +| This file is the entry point for running ace commands. For typescript +| projects, the ace commands will fallback to the compiled code and +| hence this file has to be executable by node directly. +| +*/ + +require('reflect-metadata') +require('source-map-support').install({ handleUncaughtExceptions: false }) + +const { Ignitor } = require('@adonisjs/core/build/src/Ignitor') +new Ignitor(__dirname) + .ace() + .handle(process.argv.slice(2)) + .catch(console.error) diff --git a/server-new/app/Exceptions/Handler.ts b/server-new/app/Exceptions/Handler.ts new file mode 100644 index 0000000..79593c6 --- /dev/null +++ b/server-new/app/Exceptions/Handler.ts @@ -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) + } +} diff --git a/server-new/commands/index.ts b/server-new/commands/index.ts new file mode 100644 index 0000000..c25e0bd --- /dev/null +++ b/server-new/commands/index.ts @@ -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']) diff --git a/server-new/config/app.ts b/server-new/config/app.ts new file mode 100644 index 0000000..3bd3bbc --- /dev/null +++ b/server-new/config/app.ts @@ -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: [], +} diff --git a/server-new/config/bodyparser.ts b/server-new/config/bodyparser.ts new file mode 100644 index 0000000..0e2818a --- /dev/null +++ b/server-new/config/bodyparser.ts @@ -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 diff --git a/server-new/config/cors.ts b/server-new/config/cors.ts new file mode 100644 index 0000000..aef9d58 --- /dev/null +++ b/server-new/config/cors.ts @@ -0,0 +1,134 @@ +/** + * 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: true, + + /* + |-------------------------------------------------------------------------- + | 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 diff --git a/server-new/config/hash.ts b/server-new/config/hash.ts new file mode 100644 index 0000000..0e1e6f9 --- /dev/null +++ b/server-new/config/hash.ts @@ -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 diff --git a/server-new/contracts/events.ts b/server-new/contracts/events.ts new file mode 100644 index 0000000..665d2e9 --- /dev/null +++ b/server-new/contracts/events.ts @@ -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 { + } +} diff --git a/server-new/contracts/hash.ts b/server-new/contracts/hash.ts new file mode 100644 index 0000000..ae1c87e --- /dev/null +++ b/server-new/contracts/hash.ts @@ -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, + }, + } +} diff --git a/server-new/package-lock.json b/server-new/package-lock.json new file mode 100644 index 0000000..9d3b03d --- /dev/null +++ b/server-new/package-lock.json @@ -0,0 +1,4461 @@ +{ + "name": "leaguestats-api", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@adonisjs/ace": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@adonisjs/ace/-/ace-6.9.4.tgz", + "integrity": "sha512-73SaXumO9fYzPa8GvVT4uoz0xJhmzFlBHd9XuLUInu5qr6OX3LrZclPmAAKZNogaACUCUNOaGB3fG0JPhIlUkQ==", + "requires": { + "@poppinss/colors": "^2.0.1", + "@poppinss/fancy-logs": "^1.3.7", + "@poppinss/prompts": "^1.1.3", + "@poppinss/utils": "^2.5.2", + "fs-extra": "^9.0.1", + "getopts": "^2.2.4", + "leven": "^3.1.0", + "mustache": "^4.0.1", + "pluralize": "^8.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + } + } + }, + "@adonisjs/application": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@adonisjs/application/-/application-2.0.0.tgz", + "integrity": "sha512-rAHI4BgVsLCCMFWoIW/zRAe9AdaqUeAh7SfWYGkadMkv7Yw3LiHe/2+XC1N+jcIrymfiSmlJ9vkP11Rb3+1hPQ==", + "requires": { + "@poppinss/utils": "^2.3.0", + "semver": "^7.3.2" + } + }, + "@adonisjs/assembler": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@adonisjs/assembler/-/assembler-2.1.5.tgz", + "integrity": "sha512-PVSke7Y+G4ZKsM/SIPSJWTWNccd7zpnHH+lXD15mOQgBj0X+g4dJxrpR4pM4k4lYwfq3D5UA41oOMezGX7JBPw==", + "dev": true, + "requires": { + "@adonisjs/application": "^2.0.0", + "@adonisjs/env": "^1.0.18", + "@adonisjs/ioc-transformer": "^1.0.2", + "@adonisjs/sink": "^3.0.2", + "@poppinss/chokidar-ts": "^2.0.8", + "@poppinss/colors": "^2.0.1", + "@poppinss/fancy-logs": "^1.3.7", + "@poppinss/utils": "^2.5.2", + "chokidar": "^3.4.2", + "cpy": "^8.1.0", + "debounce": "^1.2.0", + "emittery": "^0.7.1", + "execa": "^4.0.3", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", + "has-yarn": "^2.1.0", + "import-fresh": "^3.2.1", + "mem": "^6.1.0", + "picomatch": "^2.2.2", + "slash": "^3.0.0" + } + }, + "@adonisjs/bodyparser": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@adonisjs/bodyparser/-/bodyparser-4.0.6.tgz", + "integrity": "sha512-MTDNNR7LTN7VTuSoE9rUp8ei5qc//dZ9tnL1rpIfH5AsGczcRFrKhf/RhDJB5akFdSuyrW2xBIsiCsSUtbqPrQ==", + "requires": { + "@poppinss/co-body": "^1.0.0", + "@poppinss/utils": "^2.5.5", + "bytes": "^3.1.0", + "cuid": "^2.1.8", + "end-of-stream": "^1.4.4", + "file-type": "^15.0.0", + "fs-extra": "^9.0.1", + "media-typer": "^1.1.0", + "multiparty": "^4.2.2" + } + }, + "@adonisjs/config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@adonisjs/config/-/config-1.1.0.tgz", + "integrity": "sha512-Y67wzNNgbcaBEItlRAmjP/Je0knH2caYgkyHozKbTjrlzzTR6lZ9VCLoIiGBMr8FdPJZaIZ7ryHk++pXTN3XEg==", + "requires": { + "@poppinss/utils": "^2.3.0" + } + }, + "@adonisjs/core": { + "version": "5.0.0-preview-rc-1.12", + "resolved": "https://registry.npmjs.org/@adonisjs/core/-/core-5.0.0-preview-rc-1.12.tgz", + "integrity": "sha512-XrBW3yshHZ9BptPZOg8IjMNd5fwGqLUuy4KI54m6fC5Ph2DbwvIj08gC5OgKhIGBXObY1viZ5ezwqoUURndP6w==", + "requires": { + "@adonisjs/application": "^2.0.0", + "@adonisjs/bodyparser": "^4.0.6", + "@adonisjs/config": "^1.1.0", + "@adonisjs/encryption": "^2.0.6", + "@adonisjs/env": "^1.0.18", + "@adonisjs/events": "^4.0.1", + "@adonisjs/hash": "^5.0.0", + "@adonisjs/http-server": "^3.0.2", + "@adonisjs/logger": "^2.1.0", + "@adonisjs/profiler": "^4.0.1", + "@adonisjs/validator": "^7.4.2", + "@poppinss/utils": "^2.5.5", + "@types/find-package-json": "^1.1.1", + "cli-table3": "^0.6.0", + "find-package-json": "^1.2.0", + "semver": "^7.3.2", + "serve-static": "^1.14.1" + } + }, + "@adonisjs/encryption": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@adonisjs/encryption/-/encryption-2.0.6.tgz", + "integrity": "sha512-eyFmdk421R5HDK9qwgH61z3Tt6m0b+NZZ3TqmzrTWIKWpYpdf9d+2CuTOo1OR0QEMxQyXEPKxdR0JcCCqalSdQ==", + "requires": { + "@poppinss/utils": "^2.3.0" + } + }, + "@adonisjs/env": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/@adonisjs/env/-/env-1.0.18.tgz", + "integrity": "sha512-Zo/dh70DRw8XgYpzL1ygBnzTO0gr7oIyVdcj9QGySE/7+gvtpgENgHZyin5JuefWOCDDERIVkPM86WpXBSPlnA==", + "requires": { + "@poppinss/utils": "^2.3.0", + "dotenv": "^8.2.0" + } + }, + "@adonisjs/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adonisjs/events/-/events-4.0.1.tgz", + "integrity": "sha512-tF3xt+I/efJ/IFSf6vHScf6mzDPWks6NmiLFSuHYe7cxzHwtjnr4lY+s22FT+saOr/lNZd93gkJtS0dOwiE5eQ==", + "requires": { + "emittery": "^0.7.1" + } + }, + "@adonisjs/fold": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@adonisjs/fold/-/fold-6.4.1.tgz", + "integrity": "sha512-xlilWpTbLMKb8wKrHEqhmXkCZMv8cjosz3zvIxH71htWFfTwccVeOtQRxZP+e8zkB/c/y9tmBTCnf9mGgONLGg==", + "requires": { + "@poppinss/utils": "^2.5.7" + } + }, + "@adonisjs/hash": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@adonisjs/hash/-/hash-5.0.0.tgz", + "integrity": "sha512-FjuwTgtnlQopz1eBaeJwVVcxllqaWKXwc9bHQSBC4K8AGavY1fUeFL6KQ20Fw4fISCh5QspWr6+UlPyiipdJmQ==", + "requires": { + "@phc/format": "^1.0.0", + "@poppinss/manager": "^3.0.4", + "@poppinss/utils": "^2.3.0" + } + }, + "@adonisjs/http-server": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@adonisjs/http-server/-/http-server-3.0.3.tgz", + "integrity": "sha512-Gx4VIhWcqJK/CFbSVOzRLoJKk0GP2RQjYEQWOqtKajWrrVZ11mXTZNR7DxtpYPM08TW16szxbm13qiJZO+9MGw==", + "requires": { + "@poppinss/utils": "^2.5.5", + "accepts": "^1.3.7", + "co-compose": "^6.0.1", + "content-disposition": "^0.5.3", + "cookie": "^0.4.1", + "cuid": "^2.1.8", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "haye": "^2.0.2", + "macroable": "^5.0.1", + "matchit": "git+https://github.com/thetutlage/matchit.git", + "mime-types": "^2.1.27", + "ms": "^2.1.2", + "on-finished": "^2.3.0", + "pluralize": "^8.0.0", + "proxy-addr": "^2.0.6", + "qs": "^6.9.4", + "quick-lru": "^5.1.1", + "type-is": "^1.6.18", + "vary": "^1.1.2" + } + }, + "@adonisjs/ioc-transformer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@adonisjs/ioc-transformer/-/ioc-transformer-1.0.3.tgz", + "integrity": "sha512-EklPemTIau4Y5WicRvUzO2P56/oPvfavWExOLLJJfaUoUMGNA+Eu0Eg8QCEQFzFzqi2OqKDfnM9qR3bGbnZK8Q==", + "dev": true + }, + "@adonisjs/logger": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@adonisjs/logger/-/logger-2.1.0.tgz", + "integrity": "sha512-BLaIdN17RD9wP/X1vl/5P9onnH3gkROCIPQpC8/mtvbMmUbE1NrYaRBCobH2r05AOMu/BfNKgvEDzgfqA8TVcg==", + "requires": { + "@poppinss/utils": "^2.3.0", + "@types/pino": "^6.3.0", + "abstract-logging": "^2.0.0", + "pino": "^6.4.0" + } + }, + "@adonisjs/profiler": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adonisjs/profiler/-/profiler-4.0.1.tgz", + "integrity": "sha512-Gn5pRfd6Ne+KQ+ExRFkGDiNUQWBmjHssG5gnVJUraF788upgtnU5dE/zqRuFolqlGVHcnkKEQT1nDkD9QeRIhA==", + "requires": { + "@poppinss/utils": "^2.3.0", + "cuid": "^2.1.8", + "jest-worker": "^26.1.0" + } + }, + "@adonisjs/sink": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@adonisjs/sink/-/sink-3.0.3.tgz", + "integrity": "sha512-UsoPJC/PoGuUm+zpbysQLOlyNJBRiyFwgmTMRJzVB3OEnfY2F80cAPAW35+D/oRC0WxHZHXsI+1kGBsvGYYOug==", + "dev": true, + "requires": { + "@poppinss/colors": "^2.0.1", + "@poppinss/fancy-logs": "^1.3.7", + "@poppinss/prompts": "^1.1.3", + "@poppinss/utils": "^2.5.5", + "cp-file": "^9.0.0", + "fs-extra": "^9.0.1", + "marked": "^1.1.1", + "marked-terminal": "^4.1.0", + "mrm-core": "^4.1.2", + "mustache": "^4.0.1", + "open": "^7.2.0" + } + }, + "@adonisjs/validator": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@adonisjs/validator/-/validator-7.4.2.tgz", + "integrity": "sha512-1wnoQeKHht8xWoF3RQ7DsXyL6y60jcRYHuXq7hM/aEAyds7IfczbqAfa7OTnDOqISsi+I0XeyqPYd/Uz4rgtPg==", + "requires": { + "@poppinss/utils": "^2.5.5", + "@types/luxon": "^1.24.4", + "@types/validator": "^13.1.0", + "luxon": "^1.25.0", + "validator": "^13.1.1" + } + }, + "@arr/every": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@arr/every/-/every-1.0.1.tgz", + "integrity": "sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==" + }, + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@hapi/bourne": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", + "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==", + "dev": true + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, + "@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==" + }, + "@poppinss/chokidar-ts": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@poppinss/chokidar-ts/-/chokidar-ts-2.2.1.tgz", + "integrity": "sha512-9qPBDKcYxU4EAUhSImtpXehHvwVc7QUoMLIpLrt2MXdSr9s5wFle7riFZaj1++UDzsePBjfV0mvklxktbJY2rg==", + "dev": true, + "requires": { + "builtin-modules": "^3.1.0", + "chokidar": "^3.4.2", + "debug": "^4.2.0", + "emittery": "^0.7.1", + "fs-extra": "^9.0.1", + "mem": "^6.1.1", + "picomatch": "^2.2.2" + } + }, + "@poppinss/co-body": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@poppinss/co-body/-/co-body-1.0.0.tgz", + "integrity": "sha512-uh3t7/TE5hvO8vpuPQVvQ+G3aq9j75xBfo++U8Pv94vv8FKRopwqdj1/O49TW/ZLgQtubEKbCdFuzDyptC0t5Q==", + "requires": { + "@poppinss/utils": "^2.5.1", + "destr": "^1.0.0", + "inflation": "^2.0.0", + "qs": "^6.9.4", + "raw-body": "^2.4.1", + "type-is": "^1.6.18" + } + }, + "@poppinss/colors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-2.0.3.tgz", + "integrity": "sha512-LMBtXQKMmklDmKlsPRUlAbwsgGUz7ZSv4CxFLCWXJkgTMAuYaesIA1D6Lp+K8jm5fjnfc0ACy+uFf/WFr1xHeA==", + "requires": { + "color-support": "^1.1.3", + "kleur": "^4.1.1" + } + }, + "@poppinss/fancy-logs": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@poppinss/fancy-logs/-/fancy-logs-1.3.9.tgz", + "integrity": "sha512-6p2xH5CWOKTPXCQs69rHz3rK1zEeLNwGEm+FPKOxjnVvulc1zZgr3YjYY8FP7VyARr7EHTwCviDTpS4YUvDkcg==", + "requires": { + "@poppinss/colors": "^2.0.3", + "figures": "^3.2.0", + "string-width": "^4.2.0" + } + }, + "@poppinss/manager": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@poppinss/manager/-/manager-3.0.7.tgz", + "integrity": "sha512-KTsgnHwdYM2W4K61k0oBt0ZSIUDTkIfW2bVQefU8y86Pld60LK+DsRDlXatKb4KyZlzxx8XH39ycBP0k9bQNgA==" + }, + "@poppinss/prompts": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/prompts/-/prompts-1.1.6.tgz", + "integrity": "sha512-v0r/mwWg53GrE8+6wu7K5ObmrIlowxtCGVWPNudpOK9l/uhpwEMnmgEoBIw+gNu4STk0oDUJ1Avmhy4b/MUaCg==", + "requires": { + "@poppinss/colors": "^2.0.3", + "enquirer": "^2.3.6" + } + }, + "@poppinss/utils": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@poppinss/utils/-/utils-2.5.7.tgz", + "integrity": "sha512-O2Qjz+iIRTIJAwwH/bOml9nj4/kszcpoEyNoeir1KhxsyXhstnHNeTK9FsMlJz38JOs1y1h/tAP3qkkEHlTMkg==", + "requires": { + "buffer-alloc": "^1.2.0", + "fast-safe-stringify": "^2.0.7", + "fs-readdir-recursive": "^1.1.0", + "klona": "^2.0.4", + "ms": "^2.1.2", + "require-all": "^3.0.0", + "resolve-from": "^5.0.0" + } + }, + "@tokenizer/token": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz", + "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/find-package-json": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.1.1.tgz", + "integrity": "sha512-XMCocYkg6VUpkbOQMKa3M5cgc3MvU/LJKQwd3VUJrWZbLr2ARUggupsCAF8DxjEEIuSO6HlnH+vl+XV4bgVeEQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "@types/luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-iIJp2CP6C32gVqI08HIYnzqj55tlLnodIBMCcMf28q9ckqMfMzocCmIzd9JWI/ALLPMUiTkCu1JGv3FFtu6t3g==" + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==" + }, + "@types/pino": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.2.tgz", + "integrity": "sha512-dLKNzFY35feTD92DLFtFY1YPEngQxlfjczK2iEzwtGhh/M2AlTNyxshcYsbstBA6yc8wpTKYNrmjw+NcppE2YQ==", + "requires": { + "@types/node": "*", + "@types/pino-std-serializers": "*", + "@types/sonic-boom": "*" + } + }, + "@types/pino-std-serializers": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/pino-std-serializers/-/pino-std-serializers-2.4.1.tgz", + "integrity": "sha512-17XcksO47M24IVTVKPeAByWUd3Oez7EbIjXpSbzMPhXVzgjGtrOa49gKBwxH9hb8dKv58OelsWQ+A1G1l9S3wQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/sonic-boom": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/sonic-boom/-/sonic-boom-0.7.0.tgz", + "integrity": "sha512-AfqR0fZMoUXUNwusgXKxcE9DPlHNDHQp6nKYUd4PSRpLobF5CCevSpyTEBcVZreqaWKCnGBr9KI1fHMTttoB7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/validator": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.0.tgz", + "integrity": "sha512-gHUHI6pJaANIO2r6WcbT7+WMgbL9GZooR4tWpuBOETpDIqFNxwaJluE+6rj6VGYe8k6OkfhbHz2Fkm8kl06Igw==" + }, + "@typescript-eslint/eslint-plugin": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz", + "integrity": "sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "3.10.1", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "abstract-logging": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.0.tgz", + "integrity": "sha512-/oA9z7JszpIioo6J6dB79LVUgJ3eD3cxkAmdCkvWWS+Y9tPtALs1rLqOekLUXUbYqM2fB9TTK0ibAyZJJOP/CA==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "adonis-preset-ts": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/adonis-preset-ts/-/adonis-preset-ts-1.0.4.tgz", + "integrity": "sha512-BpDCRLjsSO/k+pWK6xPdTMuCdc6HUird5TUExHHiYeeMzC5BcQKblpaCBN/7kh+E+l8GyeN22yuHXFdm4aLt2Q==", + "dev": true + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "dev": true, + "requires": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cli-table3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", + "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "optional": true + } + } + }, + "co-compose": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/co-compose/-/co-compose-6.0.3.tgz", + "integrity": "sha512-tDZcx5rKmL7KPijvfJlSNfy0cUX3/Xq0BpDFVLvdv6GB7kLEbQP4tKmltF3F3CDi1k/TFkkDm9ponY6cyQgFWg==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "comment-json": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-2.4.2.tgz", + "integrity": "sha512-T+iXox779qsqneMYx/x5BZyz4xjCeQRmuNVzz8tko7qZUs3MlzpA3RAs+O1XsgcKToNBMIvfVzafGOeiU7RggA==", + "dev": true, + "requires": { + "core-util-is": "^1.0.2", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cp-file": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-9.0.0.tgz", + "integrity": "sha512-hNEM3AuZz84zOPgL8ozJ057+DjuD6LpwyYNiyTsLDU71Ke2E3KV909xhGYLK7WQ0srJRwzs/1+bSE1pk/HRC6w==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + }, + "cpy": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-8.1.1.tgz", + "integrity": "sha512-vqHT+9o67sMwJ5hUd/BAOYeemkU+MuFRsK2c36Xc3eefQpAsp1kAsyDxEDcc5JS1+y9l/XHPrIsVTcyGGmkUUQ==", + "dev": true, + "requires": { + "arrify": "^2.0.1", + "cp-file": "^7.0.0", + "globby": "^9.2.0", + "has-glob": "^1.0.0", + "junk": "^3.1.0", + "nested-error-stacks": "^2.1.0", + "p-all": "^2.1.0", + "p-filter": "^2.1.0", + "p-map": "^3.0.0" + }, + "dependencies": { + "cp-file": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", + "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cuid": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", + "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==" + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/destr/-/destr-1.0.0.tgz", + "integrity": "sha512-uw0zD4688l00hDftUP76EWyweDtOB/0mVd/8GvmwecYD+Akw5Z4v43pw4onVsz4rC/BsfnZnDMG2rdTGhVIODA==" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "dev": true + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emittery": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz", + "integrity": "sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz", + "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.1.3", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-plugin-adonis": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/eslint-plugin-adonis/-/eslint-plugin-adonis-1.0.15.tgz", + "integrity": "sha512-IIyDodzFqVCjE2tz6NuRAME0mFrv5/2SR+4GbM+AwU2dKJHSVFxANzZipxsbtTl3/OgFH2TDbOI9a1WH1cAuBA==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "^3.10.1", + "@typescript-eslint/parser": "^3.10.1" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "execa": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-redact": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-2.1.0.tgz", + "integrity": "sha512-0LkHpTLyadJavq9sRzzyqIoMZemWli77K2/MGOkafrR64B9ItrvZ9aT+jluvNDsv0YEHjSNhlMBtbokuoqii4A==" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-type": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-15.0.1.tgz", + "integrity": "sha512-0LieQlSA3bWUdErNrxzxfI4rhsvNAVPBO06R8pTc1hp9SE6nhqlVyvhcaXoMmtXkBTPnQenbMPLW9X76hH76oQ==", + "requires": { + "readable-web-to-node-stream": "^2.0.0", + "strtok3": "^6.0.3", + "token-types": "^2.0.0", + "typedarray-to-buffer": "^3.1.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatstr": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", + "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getopts": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", + "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", + "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", + "dev": true, + "requires": { + "is-glob": "^3.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "haye": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/haye/-/haye-2.0.2.tgz", + "integrity": "sha512-C+jeFipAuwLLmQziwQrXuHzUIihDzqoLpCpwDWYFQVCIyqi5ZvC+4YtzawPTmd1tIKo0ULf+4P0Mw8irUdXIpg==" + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "jest-worker": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "joycon": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", + "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", + "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==" + }, + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==" + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "listify": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/listify/-/listify-1.0.3.tgz", + "integrity": "sha512-083swF7iH7bx8666zdzBColpgEuy46HjN3r1isD4zV6Ix7FuHfb/2/WVnl4CH8hjuoWeFF7P5KkKNXUnJCFEJg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==" + }, + "macroable": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/macroable/-/macroable-5.0.3.tgz", + "integrity": "sha512-kCa/ZUNDPb0DbOu4U81WUmw9YKaWris/z7fNlo7NsTMeIGaj+JvD3o5aaztY7JEATFaa27ezImXx2LhJDFwcqA==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz", + "integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA==", + "dev": true + }, + "marked-terminal": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.0.tgz", + "integrity": "sha512-5KllfAOW02WS6hLRQ7cNvGOxvKW1BKuXELH4EtbWfyWgxQhROoMxEvuQ/3fTgkNjledR0J48F4HbapvYp1zWkQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.1", + "cardinal": "^2.1.1", + "chalk": "^4.0.0", + "cli-table": "^0.3.1", + "node-emoji": "^1.10.0", + "supports-hyperlinks": "^2.1.0" + } + }, + "matchit": { + "version": "git+https://github.com/thetutlage/matchit.git#ac664638960ec11987053394b5229c277e07e11e", + "from": "git+https://github.com/thetutlage/matchit.git", + "requires": { + "@arr/every": "^1.0.0" + } + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "mem": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.1.tgz", + "integrity": "sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true + }, + "mrm-core": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/mrm-core/-/mrm-core-4.1.2.tgz", + "integrity": "sha512-+fV5KYzb44TLNUHOs/6Lbjcjl9vay6XfGgUKPAXjWqB0E0b9NXn/HAO0ldmQSyl4wqlEAJUi9u2ktmdt4SBFAQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "comment-json": "^2.2.0", + "detect-indent": "^6.0.0", + "editorconfig": "^0.15.3", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "js-yaml": "^3.13.1", + "kleur": "^3.0.3", + "listify": "^1.0.0", + "lodash": "^4.17.15", + "minimist": "^1.2.0", + "prop-ini": "^0.0.2", + "readme-badger": "^0.3.0", + "semver": "^6.3.0", + "smpltmpl": "^1.0.2", + "split-lines": "^2.0.0", + "strip-bom": "^4.0.0", + "validate-npm-package-name": "^3.0.0", + "webpack-merge": "^4.2.2" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multiparty": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz", + "integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==", + "requires": { + "http-errors": "~1.8.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "dependencies": { + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + } + } + }, + "mustache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz", + "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "dev": true, + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } + } + }, + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-all": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-all/-/p-all-2.1.0.tgz", + "integrity": "sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "peek-readable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.0.tgz", + "integrity": "sha512-KGuODSTV6hcgdZvDrIDBUkN0utcAVj1LL7FfGbM0viKTtCHmtZcuEJ+lGqsp0fTFkGqesdtemV2yUSMeyy3ddA==" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pino": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.6.1.tgz", + "integrity": "sha512-DOgm7rn6ctBkBYemHXSLj7+j3o3U1q1FWBXbHcprur8mA93QcJSycEkEqhqKiFB9Mx/3Qld2FGr6+9yfQza0kA==", + "requires": { + "fast-redact": "^2.0.0", + "fast-safe-stringify": "^2.0.7", + "flatstr": "^1.0.12", + "pino-std-serializers": "^2.4.2", + "quick-format-unescaped": "^4.0.1", + "sonic-boom": "^1.0.2" + } + }, + "pino-pretty": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.2.1.tgz", + "integrity": "sha512-WyO/n6c6T2gj0ioYGFUFbrvyUoERK37Lu0liLxMIJnp1YaaG+XZBU2TAQB0yVJNb+7T+oDh9t8HGMzk00jy+tw==", + "dev": true, + "requires": { + "@hapi/bourne": "^2.0.0", + "args": "^5.0.1", + "chalk": "^4.0.0", + "dateformat": "^3.0.3", + "fast-safe-stringify": "^2.0.7", + "jmespath": "^0.15.0", + "joycon": "^2.2.5", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "split2": "^3.1.1", + "strip-json-comments": "^3.1.1" + } + }, + "pino-std-serializers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz", + "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==" + }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prop-ini": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/prop-ini/-/prop-ini-0.0.2.tgz", + "integrity": "sha1-ZzOny1JCrKsr5C5gdYPYEksXKls=", + "dev": true, + "requires": { + "extend": "^3.0.0" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + }, + "quick-format-unescaped": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", + "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readable-web-to-node-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz", + "integrity": "sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA==" + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "readme-badger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/readme-badger/-/readme-badger-0.3.0.tgz", + "integrity": "sha512-+sMOLSs1imZUISZ2Rhz7qqVd77QtpcAPbGeIraFdgJmijb04YtdlPjGNBvDChTNtLbeQ6JNGQy3pOgslWfaP3g==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-all": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", + "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=" + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "smpltmpl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/smpltmpl/-/smpltmpl-1.0.2.tgz", + "integrity": "sha512-Hq23NNgeZigOzIiX1dkb6W3gFn2/XQj43KhPxu65IMieG/gIwf/lQb1IudjYv0c/5LwJeS/mPayYzyo+8WJMxQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sonic-boom": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.3.0.tgz", + "integrity": "sha512-4nX6OYvOYr6R76xfQKi6cZpTO3YSWe/vd+QdIfoH0lBy0MnPkeAbb2rRWgmgADkXUeCKPwO1FZAKlAVWAadELw==", + "requires": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "split-lines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/split-lines/-/split-lines-2.0.0.tgz", + "integrity": "sha512-gaIdhbqxkB5/VflPXsJwZvEzh/kdwiRPF9iqpkxX4us+lzB8INedFwjCyo6vwuz5x2Ddlnav2zh270CEjCG8mA==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strtok3": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.0.4.tgz", + "integrity": "sha512-rqWMKwsbN9APU47bQTMEYTPcwdpKDtmf1jVhHzNW2cL1WqAxaM9iBb9t5P2fj+RV2YsErUWgQzHD5JwV0uCTEQ==", + "requires": { + "@tokenizer/token": "^0.1.1", + "@types/debug": "^4.1.5", + "peek-readable": "^3.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "token-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-2.0.0.tgz", + "integrity": "sha512-WWvu8sGK8/ZmGusekZJJ5NM6rRVTTDO7/bahz4NGiSDb/XsmdYBn6a1N/bymUHuWYTWeuLUg98wUzvE4jPdCZw==", + "requires": { + "@tokenizer/token": "^0.1.0", + "ieee754": "^1.1.13" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "dependencies": { + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + } + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "validator": { + "version": "13.1.17", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.17.tgz", + "integrity": "sha512-zL5QBoemJ3jYFb2/j38y7ljhwYGXVLUp8H6W1nVxadnAOvUOytec+L7BHh1oBQ82/TzWXHd+GSaxUWp4lROkLg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "youch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/youch/-/youch-2.1.0.tgz", + "integrity": "sha512-9tgseOwdvMoOGKYJGla6YKi8lUQLko4y5hD5nb0B/CjYHgZr2DfmDiQzc1zWW9acAwy0/2g0fbxY7oQ6E9olGA==", + "dev": true, + "requires": { + "cookie": "^0.4.1", + "mustache": "^4.0.1", + "stack-trace": "0.0.10" + } + }, + "youch-terminal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/youch-terminal/-/youch-terminal-1.0.1.tgz", + "integrity": "sha512-ich0hA83ZqotuckziwNzaLgCqQMb1WjoWzfO6gZquGHv9WuuGvjF246HvLt6Eeax7EQdv8jFXxJKxKF34m5m+A==", + "dev": true, + "requires": { + "kleur": "^4.1.3" + } + } + } +} diff --git a/server-new/package.json b/server-new/package.json new file mode 100644 index 0000000..e84b52c --- /dev/null +++ b/server-new/package.json @@ -0,0 +1,29 @@ +{ + "name": "leaguestats-api", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "node ace build", + "dev": "npm run start", + "start": "node ace serve --watch", + "lint": "eslint . --ext=.ts" + }, + "devDependencies": { + "@adonisjs/assembler": "^2.1.5", + "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", + "proxy-addr": "^2.0.6", + "reflect-metadata": "^0.1.13", + "source-map-support": "^0.5.19" + } +} diff --git a/server-new/providers/AppProvider.ts b/server-new/providers/AppProvider.ts new file mode 100644 index 0000000..624be77 --- /dev/null +++ b/server-new/providers/AppProvider.ts @@ -0,0 +1,22 @@ +import { IocContract } from '@adonisjs/fold' + +export default class AppProvider { + constructor (protected $container: IocContract) { + } + + public register () { + // Register your own bindings + } + + public boot () { + // IoC container is ready + } + + public shutdown () { + // Cleanup, since app is going down + } + + public ready () { + // App is ready + } +} diff --git a/server-new/server.ts b/server-new/server.ts new file mode 100644 index 0000000..da26bac --- /dev/null +++ b/server-new/server.ts @@ -0,0 +1,22 @@ +/* +|-------------------------------------------------------------------------- +| AdonisJs Server +|-------------------------------------------------------------------------- +| +| The contents in this file is meant to bootstrap the AdonisJs application +| and start the HTTP server to accept incoming connections. You must avoid +| making this file dirty and instead make use of `lifecycle hooks` provided +| by AdonisJs service providers for custom code. +| +*/ + +import 'reflect-metadata' +import sourceMapSupport from 'source-map-support' +import { Ignitor } from '@adonisjs/core/build/src/Ignitor' + +sourceMapSupport.install({ handleUncaughtExceptions: false }) + +new Ignitor(__dirname) + .httpServer() + .start() + .catch(console.error) diff --git a/server-new/start/kernel.ts b/server-new/start/kernel.ts new file mode 100644 index 0000000..258e9f0 --- /dev/null +++ b/server-new/start/kernel.ts @@ -0,0 +1,44 @@ +/* +|-------------------------------------------------------------------------- +| Application middleware +|-------------------------------------------------------------------------- +| +| This file is used to define middleware for HTTP requests. You can register +| middleware as a `closure` or an IoC container binding. The bindings are +| preferred, since they keep this file clean. +| +*/ + +import Server from '@ioc:Adonis/Core/Server' + +/* +|-------------------------------------------------------------------------- +| Global middleware +|-------------------------------------------------------------------------- +| +| An array of global middleware, that will be executed in the order they +| are defined for all HTTP requests. +| +*/ +Server.middleware.register([ + 'Adonis/Core/BodyParserMiddleware', +]) + +/* +|-------------------------------------------------------------------------- +| Named middleware +|-------------------------------------------------------------------------- +| +| Named middleware are defined a key-value pair. The value is the namespace +| or middleware function and key is the alias. Later you can use these +| alias on individual routes. For example: +| +| { auth: 'Adonis/Auth/Middleware' } +| +| and then use it as follows +| +| Route.get('dashboard', 'UserController.dashboard').middleware('auth') +| +*/ +Server.middleware.registerNamed({ +}) diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts new file mode 100644 index 0000000..66b2ba9 --- /dev/null +++ b/server-new/start/routes.ts @@ -0,0 +1,25 @@ +/* +|-------------------------------------------------------------------------- +| Routes +|-------------------------------------------------------------------------- +| +| This file is dedicated for defining HTTP routes. A single file is enough +| for majority of projects, however you can define routes in different +| files and just make sure to import them inside this file. For example +| +| Define routes in following two files +| ├── start/routes/cart.ts +| ├── start/routes/customer.ts +| +| and then import them inside `start/routes/index.ts` as follows +| +| import './cart' +| import './customer' +| +*/ + +import Route from '@ioc:Adonis/Core/Route' + +Route.get('/', async () => { + return { hello: 'world' } +}) diff --git a/server-new/tsconfig.json b/server-new/tsconfig.json new file mode 100644 index 0000000..f10e94c --- /dev/null +++ b/server-new/tsconfig.json @@ -0,0 +1,29 @@ +{ + "include": [ + "**/*" + ], + "exclude": [ + "node_modules", + "build" + ], + "extends": "./node_modules/adonis-preset-ts/tsconfig", + "compilerOptions": { + "outDir": "build", + "rootDir": "./", + "sourceMap": true, + "paths": { + "App/*": [ + "./app/*" + ], + "Contracts/*": [ + "./contracts/*" + ], + "Database/*": [ + "./database/*" + ] + }, + "types": [ + "@adonisjs/core" + ] + } +} From bd3bd7c445822243c29c04dbe14d7f249745fcad Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 15:00:07 +0200 Subject: [PATCH 02/33] chore: add mongodb adonis package --- server-new/.adonisrc.json | 6 +- server-new/.env.example | 3 + server-new/config/mongodb.ts | 14 +++ server-new/package-lock.json | 162 +++++++++++++++++++++++++++++++++-- server-new/package.json | 1 + server-new/start/routes.ts | 6 ++ server-new/tsconfig.json | 3 +- 7 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 server-new/config/mongodb.ts diff --git a/server-new/.adonisrc.json b/server-new/.adonisrc.json index 310b011..cf9d81c 100644 --- a/server-new/.adonisrc.json +++ b/server-new/.adonisrc.json @@ -2,7 +2,8 @@ "typescript": true, "commands": [ "./commands", - "@adonisjs/core/build/commands" + "@adonisjs/core/build/commands", + "@zakodium/adonis-mongodb/lib/commands" ], "exceptionHandlerNamespace": "App/Exceptions/Handler", "aliases": { @@ -17,7 +18,8 @@ ], "providers": [ "./providers/AppProvider", - "@adonisjs/core" + "@adonisjs/core", + "@zakodium/adonis-mongodb" ], "metaFiles": [ ".env", diff --git a/server-new/.env.example b/server-new/.env.example index ecbc796..a79f72b 100644 --- a/server-new/.env.example +++ b/server-new/.env.example @@ -2,3 +2,6 @@ PORT= HOST= NODE_ENV= APP_KEY= + +MONGODB_URL=mongodb://localhost:27017 +MONGODB_DATABASE=leaguestats diff --git a/server-new/config/mongodb.ts b/server-new/config/mongodb.ts new file mode 100644 index 0000000..331d2e7 --- /dev/null +++ b/server-new/config/mongodb.ts @@ -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 diff --git a/server-new/package-lock.json b/server-new/package-lock.json index 9d3b03d..ec58360 100644 --- a/server-new/package-lock.json +++ b/server-new/package-lock.json @@ -457,6 +457,14 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz", "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" }, + "@types/bson": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.2.tgz", + "integrity": "sha512-+uWmsejEHfmSjyyM/LkrP0orfE2m5Mx9Xel4tXNeqi1ldK5XMQcDsFkBmLDtuyKUbxj2jGDo0H240fbCRJZo7Q==", + "requires": { + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -509,6 +517,15 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/mongodb": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.26.tgz", + "integrity": "sha512-p0X2VJgIBNHfNBdZdzzG8eQ/3bf6mQoXDT0UhVyVEdSzXEa1+2pFcwGvEZp72sjztyBwfRKlgrXMjCVavLcuGg==", + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, "@types/node": { "version": "14.11.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", @@ -616,6 +633,19 @@ "eslint-visitor-keys": "^1.1.0" } }, + "@zakodium/adonis-mongodb": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zakodium/adonis-mongodb/-/adonis-mongodb-0.3.0.tgz", + "integrity": "sha512-nGdtCGg8FMDc8uoueudx2VrHHBtBW/9i5zxenTxu1tAs7Y3LEDncxTr9ROXTW2YAfXBWOW0XAzcLInBIxaY4qQ==", + "requires": { + "@poppinss/utils": "^2.5.6", + "@types/mongodb": "3.5.26", + "cli-table3": "^0.6.0", + "lodash": "^4.17.20", + "mongodb": "^3.6.1", + "pluralize": "^8.0.0" + } + }, "abstract-logging": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.0.tgz", @@ -981,6 +1011,53 @@ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1000,6 +1077,11 @@ "fill-range": "^7.0.1" } }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -1268,8 +1350,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cp-file": { "version": "9.0.0", @@ -1404,6 +1485,11 @@ } } }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2474,8 +2560,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -2604,8 +2689,7 @@ "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.toarray": { "version": "4.4.0", @@ -2716,6 +2800,12 @@ "mimic-fn": "^3.0.0" } }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2901,6 +2991,19 @@ "minimist": "^1.2.5" } }, + "mongodb": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz", + "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, "mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -3413,6 +3516,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -3576,6 +3684,27 @@ "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=" }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + }, + "dependencies": { + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -3621,6 +3750,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -3963,6 +4101,15 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, "split-lines": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/split-lines/-/split-lines-2.0.0.tgz", @@ -4365,8 +4512,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "v8-compile-cache": { "version": "2.1.1", diff --git a/server-new/package.json b/server-new/package.json index e84b52c..1eeb677 100644 --- a/server-new/package.json +++ b/server-new/package.json @@ -22,6 +22,7 @@ "@adonisjs/ace": "^6.9.4", "@adonisjs/core": "^5.0.0-preview-rc-1.12", "@adonisjs/fold": "^6.4.1", + "@zakodium/adonis-mongodb": "^0.3.0", "proxy-addr": "^2.0.6", "reflect-metadata": "^0.1.13", "source-map-support": "^0.5.19" diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts index 66b2ba9..35f23d8 100644 --- a/server-new/start/routes.ts +++ b/server-new/start/routes.ts @@ -19,7 +19,13 @@ */ import Route from '@ioc:Adonis/Core/Route' +import mongodb from '@ioc:Mongodb/Database' Route.get('/', async () => { return { hello: 'world' } }) + +Route.get('mongo', async () => { + const match = await (await mongodb.connection().collection('matches')).findOne({}) + return { test: match } +}) diff --git a/server-new/tsconfig.json b/server-new/tsconfig.json index f10e94c..e7762a8 100644 --- a/server-new/tsconfig.json +++ b/server-new/tsconfig.json @@ -23,7 +23,8 @@ ] }, "types": [ - "@adonisjs/core" + "@adonisjs/core", + "@zakodium/adonis-mongodb" ] } } From 143ab1b4bea961377d55f8601a3cf0156937bd16 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 16:04:42 +0200 Subject: [PATCH 03/33] feat: add migrations to create indexes --- server-new/mongodb/migrations/1601816721286_match.ts | 9 +++++++++ server-new/mongodb/migrations/1601819828861_summoner.ts | 8 ++++++++ .../mongodb/migrations/1601819837856_detailed_match.ts | 8 ++++++++ 3 files changed, 25 insertions(+) create mode 100644 server-new/mongodb/migrations/1601816721286_match.ts create mode 100644 server-new/mongodb/migrations/1601819828861_summoner.ts create mode 100644 server-new/mongodb/migrations/1601819837856_detailed_match.ts diff --git a/server-new/mongodb/migrations/1601816721286_match.ts b/server-new/mongodb/migrations/1601816721286_match.ts new file mode 100644 index 0000000..1187d86 --- /dev/null +++ b/server-new/mongodb/migrations/1601816721286_match.ts @@ -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') + } +} diff --git a/server-new/mongodb/migrations/1601819828861_summoner.ts b/server-new/mongodb/migrations/1601819828861_summoner.ts new file mode 100644 index 0000000..87045a4 --- /dev/null +++ b/server-new/mongodb/migrations/1601819828861_summoner.ts @@ -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') + } +} diff --git a/server-new/mongodb/migrations/1601819837856_detailed_match.ts b/server-new/mongodb/migrations/1601819837856_detailed_match.ts new file mode 100644 index 0000000..4c86664 --- /dev/null +++ b/server-new/mongodb/migrations/1601819837856_detailed_match.ts @@ -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') + } +} From a865283b1de48b518debb14eb60ccf4b11d8b231 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 16:14:10 +0200 Subject: [PATCH 04/33] chore: add redis adonis package --- server-new/.adonisrc.json | 3 +- server-new/.env.example | 11 ++++-- server-new/config/redis.ts | 48 +++++++++++++++++++++++ server-new/contracts/redis.ts | 12 ++++++ server-new/package-lock.json | 73 ++++++++++++++++++++++++++++++++++- server-new/package.json | 1 + server-new/tsconfig.json | 3 +- 7 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 server-new/config/redis.ts create mode 100644 server-new/contracts/redis.ts diff --git a/server-new/.adonisrc.json b/server-new/.adonisrc.json index cf9d81c..d85bc35 100644 --- a/server-new/.adonisrc.json +++ b/server-new/.adonisrc.json @@ -19,7 +19,8 @@ "providers": [ "./providers/AppProvider", "@adonisjs/core", - "@zakodium/adonis-mongodb" + "@zakodium/adonis-mongodb", + "@adonisjs/redis" ], "metaFiles": [ ".env", diff --git a/server-new/.env.example b/server-new/.env.example index a79f72b..4f8f6bb 100644 --- a/server-new/.env.example +++ b/server-new/.env.example @@ -1,7 +1,12 @@ -PORT= -HOST= -NODE_ENV= +PORT=3333 +HOST=0.0.0.0 +NODE_ENV=development APP_KEY= MONGODB_URL=mongodb://localhost:27017 MONGODB_DATABASE=leaguestats + +REDIS_CONNECTION=local +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_PASSWORD= diff --git a/server-new/config/redis.ts b/server-new/config/redis.ts new file mode 100644 index 0000000..d3522b1 --- /dev/null +++ b/server-new/config/redis.ts @@ -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 diff --git a/server-new/contracts/redis.ts b/server-new/contracts/redis.ts new file mode 100644 index 0000000..c04c625 --- /dev/null +++ b/server-new/contracts/redis.ts @@ -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, + } +} diff --git a/server-new/package-lock.json b/server-new/package-lock.json index ec58360..e4153ad 100644 --- a/server-new/package-lock.json +++ b/server-new/package-lock.json @@ -212,6 +212,16 @@ "jest-worker": "^26.1.0" } }, + "@adonisjs/redis": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@adonisjs/redis/-/redis-4.1.2.tgz", + "integrity": "sha512-B9ceEgTAcb3kZVnImfcFaTgmgAIaiVGqEuW959Tp3OGjCo4/IxcEMKqm2fpcQS+SqbZTwosSo/0rNhohAp1YDQ==", + "requires": { + "@poppinss/utils": "^2.5.2", + "@types/ioredis": "^4.17.3", + "ioredis": "^4.17.3" + } + }, "@adonisjs/sink": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@adonisjs/sink/-/sink-3.0.3.tgz", @@ -500,6 +510,14 @@ "@types/node": "*" } }, + "@types/ioredis": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.17.4.tgz", + "integrity": "sha512-kb5+thmQJ7HHyOAnCOeqRJlF2fyvadHghnLLLKZzCNyShStJeIQtNGGDjA30gWqj6UFSDAWBfGEMKrFDrGfvzQ==", + "requires": { + "@types/node": "*" + } + }, "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -1250,6 +1268,11 @@ } } }, + "cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" + }, "co-compose": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/co-compose/-/co-compose-6.0.3.tgz", @@ -1427,7 +1450,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -2405,6 +2427,22 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ioredis": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.17.3.tgz", + "integrity": "sha512-iRvq4BOYzNFkDnSyhx7cmJNOi1x/HWYe+A4VXHBu4qpwJaGT1Mp+D2bVGJntH9K/Z/GeOM/Nprb8gB3bmitz1Q==", + "requires": { + "cluster-key-slot": "^1.1.0", + "debug": "^4.1.1", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "redis-commands": "1.5.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.0.1" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2691,6 +2729,16 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -3646,6 +3694,24 @@ "esprima": "~4.0.0" } }, + "redis-commands": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", + "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -4146,6 +4212,11 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "standard-as-callback": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", + "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", diff --git a/server-new/package.json b/server-new/package.json index 1eeb677..862ec20 100644 --- a/server-new/package.json +++ b/server-new/package.json @@ -22,6 +22,7 @@ "@adonisjs/ace": "^6.9.4", "@adonisjs/core": "^5.0.0-preview-rc-1.12", "@adonisjs/fold": "^6.4.1", + "@adonisjs/redis": "^4.1.2", "@zakodium/adonis-mongodb": "^0.3.0", "proxy-addr": "^2.0.6", "reflect-metadata": "^0.1.13", diff --git a/server-new/tsconfig.json b/server-new/tsconfig.json index e7762a8..32adf9c 100644 --- a/server-new/tsconfig.json +++ b/server-new/tsconfig.json @@ -24,7 +24,8 @@ }, "types": [ "@adonisjs/core", - "@zakodium/adonis-mongodb" + "@zakodium/adonis-mongodb", + "@adonisjs/redis" ] } } From ca235b521e1994d1ae360ce54e7c6cbbe1e0b407 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 17:30:04 +0200 Subject: [PATCH 05/33] feat: migrate Jax Service to TypeScript --- server-new/.env.example | 2 + server-new/app/Services/Jax/JaxConfig.ts | 21 ++ server-new/app/Services/Jax/index.ts | 4 + .../app/Services/Jax/src/CDragonRequest.ts | 52 +++++ .../Jax/src/Endpoints/CDragonEndpoint.ts | 30 +++ .../Jax/src/Endpoints/LeagueEndpoint.ts | 23 ++ .../Jax/src/Endpoints/MatchEndpoint.ts | 25 +++ .../Jax/src/Endpoints/MatchListEndpoint.ts | 23 ++ .../Jax/src/Endpoints/SpectatorEndpoint.ts | 23 ++ .../Jax/src/Endpoints/SummonerEndpoint.ts | 23 ++ server-new/app/Services/Jax/src/Jax.ts | 35 +++ server-new/app/Services/Jax/src/JaxRequest.ts | 76 +++++++ server-new/package-lock.json | 199 +++++++++++++++++- server-new/package.json | 2 + server-new/start/routes.ts | 6 + 15 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 server-new/app/Services/Jax/JaxConfig.ts create mode 100644 server-new/app/Services/Jax/index.ts create mode 100644 server-new/app/Services/Jax/src/CDragonRequest.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts create mode 100644 server-new/app/Services/Jax/src/Jax.ts create mode 100644 server-new/app/Services/Jax/src/JaxRequest.ts diff --git a/server-new/.env.example b/server-new/.env.example index 4f8f6bb..4c04fbf 100644 --- a/server-new/.env.example +++ b/server-new/.env.example @@ -10,3 +10,5 @@ REDIS_CONNECTION=local REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= + +API_KEY=RGAPI-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx diff --git a/server-new/app/Services/Jax/JaxConfig.ts b/server-new/app/Services/Jax/JaxConfig.ts new file mode 100644 index 0000000..928758b --- /dev/null +++ b/server-new/app/Services/Jax/JaxConfig.ts @@ -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, + }, +} diff --git a/server-new/app/Services/Jax/index.ts b/server-new/app/Services/Jax/index.ts new file mode 100644 index 0000000..3f02a21 --- /dev/null +++ b/server-new/app/Services/Jax/index.ts @@ -0,0 +1,4 @@ +import Jax from './src/Jax' +import { JAX_CONFIG } from './JaxConfig' + +export = new Jax(JAX_CONFIG) diff --git a/server-new/app/Services/Jax/src/CDragonRequest.ts b/server-new/app/Services/Jax/src/CDragonRequest.ts new file mode 100644 index 0000000..c60328a --- /dev/null +++ b/server-new/app/Services/Jax/src/CDragonRequest.ts @@ -0,0 +1,52 @@ +import { promisify } from 'util' +import got from 'got' +import Logger from '@ioc:Adonis/Core/Logger' +import Redis from '@ioc:Adonis/Addons/Redis' +import { JaxConfig } from '../JaxConfig' + +export default class CDragonRequest { + private config: JaxConfig + private endpoint: string + private cacheTime: number + private retries: number + private sleep: { (ms: number): Promise; (ms: number, value: T): Promise } + + constructor (config: JaxConfig, endpoint: string, cacheTime: number) { + this.config = config + this.endpoint = endpoint + this.cacheTime = cacheTime + this.retries = config.requestOptions.retriesBeforeAbort + + this.sleep = promisify(setTimeout) + } + + // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/items.json + // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perks.json + // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json + // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json + + public async execute () { + const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}` + + const requestCached = await Redis.get(url) + if (requestCached) { + return JSON.parse(requestCached) + } + + try { + const response = await got(url) + + await Redis.set(url, response.body, 'EX', this.cacheTime) + return JSON.parse(response.body) + } catch (error) { + this.retries-- + + Logger.error('CDragon Error : ', error) + + if (this.retries > 0) { + await this.sleep(this.config.requestOptions.delayBeforeRetry) + return this.execute() + } + } + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts new file mode 100644 index 0000000..87b5c00 --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts @@ -0,0 +1,30 @@ +import { JaxConfig } from '../../JaxConfig' +import CDragonRequest from '../CDragonRequest' + +export default class CDragonEndpoint { + private config: JaxConfig + + constructor (config: JaxConfig) { + this.config = config + } + + public champions () { + return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute() + } + + public items () { + return new CDragonRequest(this.config, 'items.json', 36000).execute() + } + + public perks () { + return new CDragonRequest(this.config, 'perks.json', 36000).execute() + } + + public perkstyles () { + return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute() + } + + public summonerSpells () { + return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts new file mode 100644 index 0000000..be4ec7c --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts @@ -0,0 +1,23 @@ +import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +export default class LeagueEndpoint { + private config: JaxConfig + private limiter: RiotRateLimiter + + constructor (config: JaxConfig, limiter: RiotRateLimiter) { + this.config = config + this.limiter = limiter + } + + public summonerID (summonerID:number, region: string) { + return new JaxRequest( + region, + this.config, + `league/v4/entries/by-summoner/${summonerID}`, + this.limiter, + 300 + ).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts new file mode 100644 index 0000000..26cc39a --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts @@ -0,0 +1,25 @@ +import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +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) { + return new JaxRequest( + region, + this.config, + `match/v4/matches/${matchID}`, + this.limiter, + 1500 + ).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts new file mode 100644 index 0000000..95f2f84 --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts @@ -0,0 +1,23 @@ +import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +export default class MatchlistEndpoint { + private config: JaxConfig + private limiter: RiotRateLimiter + + constructor (config: JaxConfig, limiter: RiotRateLimiter) { + this.config = config + this.limiter = limiter + } + + public accountID (accountID: number, region: string, beginIndex = 0) { + return new JaxRequest( + region, + this.config, + `match/v4/matchlists/by-account/${accountID}?beginIndex=${beginIndex}`, + this.limiter, + 0 + ).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts new file mode 100644 index 0000000..e2a9b01 --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts @@ -0,0 +1,23 @@ +import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +export default class SpectatorEndpoint { + private config: JaxConfig + private limiter: RiotRateLimiter + + constructor (config: JaxConfig, limiter: RiotRateLimiter) { + this.config = config + this.limiter = limiter + } + + public summonerID (summonerID: number, region: string) { + return new JaxRequest( + region, + this.config, + `spectator/v4/active-games/by-summoner/${summonerID}`, + this.limiter, + 0 + ).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts new file mode 100644 index 0000000..d7950db --- /dev/null +++ b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts @@ -0,0 +1,23 @@ +import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import { JaxConfig } from '../../JaxConfig' +import JaxRequest from '../JaxRequest' + +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) { + return new JaxRequest( + region, + this.config, + `summoner/v4/summoners/by-name/${encodeURI(summonerName)}`, + this.limiter, + 36000 + ).execute() + } +} diff --git a/server-new/app/Services/Jax/src/Jax.ts b/server-new/app/Services/Jax/src/Jax.ts new file mode 100644 index 0000000..7ec3f9a --- /dev/null +++ b/server-new/app/Services/Jax/src/Jax.ts @@ -0,0 +1,35 @@ +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' + +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, + }) + 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) + } +} diff --git a/server-new/app/Services/Jax/src/JaxRequest.ts b/server-new/app/Services/Jax/src/JaxRequest.ts new file mode 100644 index 0000000..1f53f71 --- /dev/null +++ b/server-new/app/Services/Jax/src/JaxRequest.ts @@ -0,0 +1,76 @@ +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 '@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; (ms: number, value: T): Promise } + + 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, + }, + }, + }) + + if (this.cacheTime > 0) { + await Redis.setex(url, this.cacheTime, JSON.stringify(resp)) + } + return 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.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() + } + } + } +} diff --git a/server-new/package-lock.json b/server-new/package-lock.json index e4153ad..cab44e7 100644 --- a/server-new/package-lock.json +++ b/server-new/package-lock.json @@ -360,6 +360,18 @@ "strip-json-comments": "^3.1.1" } }, + "@fightmegg/riot-rate-limiter": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@fightmegg/riot-rate-limiter/-/riot-rate-limiter-0.0.11.tgz", + "integrity": "sha512-VWS3R+BgDUEbBTOXgj7YxaX+AoL7u0n3vqaxhoengEWaoBE4xV6kijNCJmOEzmbjew4v+4f8quKqB7KvhRsGMw==", + "requires": { + "bottleneck": "^2.19.5", + "debug": "^4.2.0", + "ioredis": "^4.17.3", + "node-fetch": "^2.6.1", + "path-to-regexp": "^6.2.0" + } + }, "@hapi/bourne": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", @@ -462,6 +474,19 @@ "resolve-from": "^5.0.0" } }, + "@sindresorhus/is": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz", + "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==" + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@tokenizer/token": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz", @@ -475,6 +500,17 @@ "@types/node": "*" } }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -510,6 +546,11 @@ "@types/node": "*" } }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, "@types/ioredis": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.17.4.tgz", @@ -524,6 +565,14 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, "@types/luxon": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.25.0.tgz", @@ -567,6 +616,14 @@ "@types/node": "*" } }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/sonic-boom": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@types/sonic-boom/-/sonic-boom-0.7.0.tgz", @@ -1076,6 +1133,11 @@ } } }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1158,6 +1220,25 @@ "unset-value": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -1268,6 +1349,14 @@ } } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "cluster-key-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", @@ -1460,12 +1549,32 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -2138,7 +2247,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -2224,6 +2332,24 @@ } } }, + "got": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.7.0.tgz", + "integrity": "sha512-7en2XwH2MEqOsrK0xaKhbWibBoZqy+f1RSUoIeF1BLcnf+pyQdDsljWMfmOh+QKJwuvDIiKx38GtPh5wFdGGjg==", + "requires": { + "@sindresorhus/is": "^3.1.1", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -2340,6 +2466,11 @@ "resolved": "https://registry.npmjs.org/haye/-/haye-2.0.2.tgz", "integrity": "sha512-C+jeFipAuwLLmQziwQrXuHzUIihDzqoLpCpwDWYFQVCIyqi5ZvC+4YtzawPTmd1tIKo0ULf+4P0Mw8irUdXIpg==" }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -2352,6 +2483,15 @@ "toidentifier": "1.0.0" } }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -2650,6 +2790,11 @@ "esprima": "^4.0.0" } }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2677,6 +2822,14 @@ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", "dev": true }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2745,6 +2898,11 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -2994,6 +3152,11 @@ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", "dev": true }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3209,12 +3372,22 @@ "lodash.toarray": "^4.4.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -3352,6 +3525,11 @@ } } }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -3476,6 +3654,11 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-to-regexp": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", + "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==" + }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -3603,7 +3786,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -3771,6 +3953,11 @@ } } }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -3782,6 +3969,14 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", diff --git a/server-new/package.json b/server-new/package.json index 862ec20..85fd56c 100644 --- a/server-new/package.json +++ b/server-new/package.json @@ -23,7 +23,9 @@ "@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", "source-map-support": "^0.5.19" diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts index 35f23d8..df493eb 100644 --- a/server-new/start/routes.ts +++ b/server-new/start/routes.ts @@ -20,6 +20,7 @@ import Route from '@ioc:Adonis/Core/Route' import mongodb from '@ioc:Mongodb/Database' +import Jax from 'App/Services/Jax' Route.get('/', async () => { return { hello: 'world' } @@ -29,3 +30,8 @@ Route.get('mongo', async () => { const match = await (await mongodb.connection().collection('matches')).findOne({}) return { test: match } }) + +Route.get('jax', async () => { + const summoner = await Jax.Summoner.summonerName('LeagueStats GG', 'euw1') + return { player: summoner } +}) From 326647256861b6a48258d49794ccc6e504ed1204 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 22:05:16 +0200 Subject: [PATCH 06/33] feat: start to type Jax response --- .../Jax/src/Endpoints/LeagueEndpoint.ts | 26 ++++++++++++++++++- .../Jax/src/Endpoints/SpectatorEndpoint.ts | 2 +- .../Jax/src/Endpoints/SummonerEndpoint.ts | 13 +++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts index be4ec7c..5a71bb6 100644 --- a/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts @@ -2,6 +2,30 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' 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 @@ -11,7 +35,7 @@ export default class LeagueEndpoint { this.limiter = limiter } - public summonerID (summonerID:number, region: string) { + public summonerID (summonerID: string, region: string): Promise { return new JaxRequest( region, this.config, diff --git a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts index e2a9b01..f187e70 100644 --- a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts @@ -11,7 +11,7 @@ export default class SpectatorEndpoint { this.limiter = limiter } - public summonerID (summonerID: number, region: string) { + public summonerID (summonerID: string, region: string) { return new JaxRequest( region, this.config, diff --git a/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts index d7950db..e10655f 100644 --- a/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts @@ -2,6 +2,17 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' 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 @@ -11,7 +22,7 @@ export default class SummonerEndpoint { this.limiter = limiter } - public summonerName (summonerName: string, region: string) { + public summonerName (summonerName: string, region: string): Promise { return new JaxRequest( region, this.config, From 58b26d583869749ae8ce49d9da875126664d050a Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 4 Oct 2020 22:05:44 +0200 Subject: [PATCH 07/33] feat: start summoner/basic endpoint --- .../app/Controllers/Http/MatchesController.ts | 4 + .../Controllers/Http/SummonersController.ts | 79 +++++++++++++++++++ server-new/app/Services/SummonerService.ts | 79 +++++++++++++++++++ server-new/app/helpers.ts | 50 ++++++++++++ server-new/contracts/league.ts | 5 ++ server-new/start/routes.ts | 10 +++ 6 files changed, 227 insertions(+) create mode 100644 server-new/app/Controllers/Http/MatchesController.ts create mode 100644 server-new/app/Controllers/Http/SummonersController.ts create mode 100644 server-new/app/Services/SummonerService.ts create mode 100644 server-new/app/helpers.ts create mode 100644 server-new/contracts/league.ts diff --git a/server-new/app/Controllers/Http/MatchesController.ts b/server-new/app/Controllers/Http/MatchesController.ts new file mode 100644 index 0000000..f6fa260 --- /dev/null +++ b/server-new/app/Controllers/Http/MatchesController.ts @@ -0,0 +1,4 @@ +// import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' + +export default class MatchesController { +} diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts new file mode 100644 index 0000000..b07aa89 --- /dev/null +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -0,0 +1,79 @@ +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import mongodb from '@ioc:Mongodb/Database' +import Jax from 'App/Services/Jax' +import SummonerService from 'App/Services/SummonerService' + +export default class SummonersController { + // private async getSeasons (puuid) { + // let seasons = await MatchRepository.seasons(puuid) + // return seasons.length ? seasons.map(s => s._id) : [10] + // } + + /** + * POST: get basic summoner data + */ + public async basic ({ request, response }: HttpContextContract) { + 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: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 + // const summonerDB = await Summoner.findOrCreate( + // { puuid: account.puuid }, + // { puuid: account.puuid } + // ) + + const summonersCollection = await mongodb.connection().collection('summoners') + const summonerDB = await summonersCollection.findOne({ puuid: account.puuid }) + if(!summonerDB) { + await summonersCollection.insertOne({ 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() + await summonersCollection.updateOne({ puuid: account.puuid }, summonerDB) + } catch (error) { + console.log('username not found') + console.log(error) + return response.json(null) + } + + console.timeEnd('all') + return response.json(finalJSON) + } +} diff --git a/server-new/app/Services/SummonerService.ts b/server-new/app/Services/SummonerService.ts new file mode 100644 index 0000000..b432f60 --- /dev/null +++ b/server-new/app/Services/SummonerService.ts @@ -0,0 +1,79 @@ +'use strict' + +import Jax from './Jax' +import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' +import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint' + +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) { + 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:any) { + const names = summonerDB.names ? summonerDB.names : [] + + if (!names.find((n: any) => 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) { + 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 + } +} + +export default new SummonerService() diff --git a/server-new/app/helpers.ts b/server-new/app/helpers.ts new file mode 100644 index 0000000..00cf0bf --- /dev/null +++ b/server-new/app/helpers.ts @@ -0,0 +1,50 @@ +/** + * 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: 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 Roles according to a specific order + * @param a first role + * @param b second role + */ + sortTeamByRole (a:any, b:any) { + const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] + return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) + }, +} diff --git a/server-new/contracts/league.ts b/server-new/contracts/league.ts new file mode 100644 index 0000000..9b77485 --- /dev/null +++ b/server-new/contracts/league.ts @@ -0,0 +1,5 @@ +declare module '@ioc:Adonis/League' { + interface Account { + + } +} diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts index df493eb..3364f8c 100644 --- a/server-new/start/routes.ts +++ b/server-new/start/routes.ts @@ -35,3 +35,13 @@ Route.get('jax', async () => { const summoner = await Jax.Summoner.summonerName('LeagueStats GG', 'euw1') return { player: summoner } }) + +Route.post('/summoner/basic', 'SummonersController.basic') +Route.post('/summoner/overview', 'SummonersController.overview') +Route.post('/summoner/champions', 'SummonersController.champions') +Route.post('/summoner/records', 'SummonersController.records') +Route.post('/summoner/live', 'SummonersController.liveMatchDetails') + +Route.post('/match', 'MatchesController.index') +Route.post('/match/details', 'MatchesController.show') +Route.post('/match/details/ranks', 'MatchesController.showRanks') From e35492840ea863e4a6948a35177ce16a8ebcaf54 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 11:16:21 +0200 Subject: [PATCH 08/33] feat: more work on summoner/basic endpoint --- .../Controllers/Http/SummonersController.ts | 15 +- .../app/Repositories/MatchRepository.ts | 43 ++++++ .../Jax/src/Endpoints/MatchListEndpoint.ts | 21 ++- server-new/app/Services/MatchService.ts | 136 ++++++++++++++++++ server-new/app/Services/SummonerService.ts | 7 +- server-new/app/helpers.ts | 50 +++---- server-new/contracts/league.ts | 11 +- server-new/start/routes.ts | 7 + 8 files changed, 252 insertions(+), 38 deletions(-) create mode 100644 server-new/app/Repositories/MatchRepository.ts create mode 100644 server-new/app/Services/MatchService.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index b07aa89..5a28151 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -1,16 +1,20 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import { SummonerModel } from '@ioc:Adonis/League' import mongodb from '@ioc:Mongodb/Database' +import MatchRepository from 'App/Repositories/MatchRepository' import Jax from 'App/Services/Jax' +import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' export default class SummonersController { - // private async getSeasons (puuid) { - // let seasons = await MatchRepository.seasons(puuid) - // return seasons.length ? seasons.map(s => s._id) : [10] - // } + private async getSeasons (puuid: string) { + let 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') @@ -41,9 +45,10 @@ export default class SummonersController { // ) const summonersCollection = await mongodb.connection().collection('summoners') - const summonerDB = await summonersCollection.findOne({ puuid: account.puuid }) + let summonerDB:SummonerModel|null = await summonersCollection.findOne({ puuid: account.puuid }) if(!summonerDB) { await summonersCollection.insertOne({ puuid: account.puuid }) + summonerDB = {puuid: account.puuid } } // Summoner names diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts new file mode 100644 index 0000000..fc6c4cc --- /dev/null +++ b/server-new/app/Repositories/MatchRepository.ts @@ -0,0 +1,43 @@ +import mongodb from '@ioc:Mongodb/Database' + +class MatchRepository { + private season?: number + + constructor () { + // TODO: keep matches collection in the repo instance + } + + /** + * Basic matchParams used in a lot of requests + * @param puuid of the summoner + */ + private _matchParams (puuid: string) { + return { + summoner_puuid: puuid, + result: { $not: { $eq: 'Remake' } }, + gamemode: { $nin: [800, 810, 820, 830, 840, 850] }, + season: this.season ? this.season : { $exists: true }, + } + } + /** + * Get Summoner's played seasons + * @param puuid of the summoner + */ + public async seasons (puuid: string) { + this.season = undefined + const matchesCollections = await mongodb.connection().collection('matches') + + return matchesCollections.aggregate([ + { + $match: { + ...this._matchParams(puuid), + }, + }, + { + $group: { _id: '$season' }, + }, + ]).toArray() + } +} + +export default new MatchRepository() diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts index 95f2f84..9cc978c 100644 --- a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts @@ -2,6 +2,25 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' 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 @@ -11,7 +30,7 @@ export default class MatchlistEndpoint { this.limiter = limiter } - public accountID (accountID: number, region: string, beginIndex = 0) { + public accountID (accountID: string, region: string, beginIndex = 0): Promise { return new JaxRequest( region, this.config, diff --git a/server-new/app/Services/MatchService.ts b/server-new/app/Services/MatchService.ts new file mode 100644 index 0000000..5cc580e --- /dev/null +++ b/server-new/app/Services/MatchService.ts @@ -0,0 +1,136 @@ +import Jax from './Jax' +// import Logger from '@ioc:Adonis/Core/Logger' +import { getSeasonNumber } from 'App/helpers' +import { MatchReferenceDto } from './Jax/src/Endpoints/MatchListEndpoint' +import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint' +import { SummonerModel } from '@ioc:Adonis/League' + +class MatchService { + /** + * Add 100 matches at a time to MatchList until the stopFetching condition is true + * @param account of the summoner + * @param stopFetching condition to stop fetching the MatchList + */ + private async _fetchMatchListUntil (account: SummonerDTO, stopFetching: any) { + let matchList: MatchReferenceDto[] = [] + let alreadyIn = false + let index = 0 + do { + let { matches: newMatchList } = await Jax.Matchlist.accountID(account.accountId, account.region as string, index) + // Error while fetching Riot API + if (!newMatchList) { + matchList = matchList.map(m => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + return matchList + } + matchList = [...matchList, ...newMatchList] + alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList) + // If the match is made in another region : we stop fetching + if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) { + alreadyIn = true + } + index += 100 + } while (!alreadyIn) + + // Remove matches from MatchList made in another region and tutorial games + const tutorialModes = [2000, 2010, 2020] + matchList = matchList + .filter(m => { + const sameRegion = m.platformId.toLowerCase() === account.region + const notATutorialGame = !tutorialModes.includes(m.queue) + + return sameRegion && notATutorialGame + }) + .map(m => { + m.seasonMatch = getSeasonNumber(m.timestamp) + return m + }) + + return matchList + } + /** + * Update the full MatchList of the summoner (min. 4 months) + * @param account of the summoner + * @param summonerDB summoner in the database + */ + public async updateMatchList (account: SummonerDTO, summonerDB: SummonerModel) { + console.time('matchList') + + // Summoner has already been searched : we already have a MatchList and we need to update it + if (summonerDB.matchList) { + // Get MatchList + const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => { + return summonerDB.matchList!.some(m => m.gameId === newMatchList[newMatchList.length - 1].gameId) + }) + // Update Summoner's MatchList + for (const match of matchList.reverse()) { + if (!summonerDB.matchList.some(m => m.gameId === match.gameId)) { + summonerDB.matchList.unshift(match) + } + } + } else { // First search of the Summoner + const today = Date.now() + // Get MatchList + const matchList = await this._fetchMatchListUntil(account, (newMatchList: MatchReferenceDto[]) => { + return (newMatchList.length !== 100 || today - newMatchList[newMatchList.length - 1].timestamp > 10368000000) + }) + // Create Summoner's MatchList in Database + summonerDB.matchList = matchList + } + console.timeEnd('matchList') + } + + /** + * Fetch list of matches for a specific Summoner + * @param account of the summoner + * @param gameIds of the matches to fetch + * @param summonerDB summoner in the database + */ + // public async getMatches (account, gameIds, summonerDB) { + // console.time('getMatches') + + // let matchesDetails = [] + // const matchesToGetFromRiot = [] + // for (let i = 0; i < gameIds.length; ++i) { + // const matchSaved = await summonerDB.matches().where({ gameId: gameIds[i] }).first() + // if (matchSaved) { + // matchesDetails.push(matchSaved) + // } else { + // matchesToGetFromRiot.push(gameIds[i]) + // } + // } + + // const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, account.region)) + // let matchesFromApi = await Promise.all(requests) + + // /* If we have to store some matches in the db */ + // if (matchesFromApi.length !== 0) { + // // Try to see why matches are sometimes undefined + // matchesFromApi.filter(m => { + // if (m === undefined) { + // Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m) + // } + // }) + + // // Transform raw matches data + // await BasicMatchTransformer.transform(matchesFromApi, { account }) + + // /* Save all matches from Riot Api in db */ + // for (const match of matchesFromApi) { + // await summonerDB.matches().create(match) + // match.newMatch = true + // } + // matchesDetails = [...matchesDetails, ...matchesFromApi] + // } + + // /* Sort matches */ + // matchesDetails.sort((a, b) => (a.date < b.date) ? 1 : -1) + // console.timeEnd('getMatches') + + // return matchesDetails + // } +} + +export default new MatchService() diff --git a/server-new/app/Services/SummonerService.ts b/server-new/app/Services/SummonerService.ts index b432f60..0798e2b 100644 --- a/server-new/app/Services/SummonerService.ts +++ b/server-new/app/Services/SummonerService.ts @@ -1,8 +1,7 @@ -'use strict' - import Jax from './Jax' import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint' +import { SummonerModel } from '@ioc:Adonis/League' class SummonerService { private uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER'] @@ -46,10 +45,10 @@ class SummonerService { * @param account of the summoner * @param summonerDB summoner in the database */ - public getAllSummonerNames (account: SummonerDTO, summonerDB:any) { + public getAllSummonerNames (account: SummonerDTO, summonerDB:SummonerModel) { const names = summonerDB.names ? summonerDB.names : [] - if (!names.find((n: any) => n.name === account.name)) { + if (!names.find(n => n.name === account.name)) { names.push({ name: account.name, date: new Date(), diff --git a/server-new/app/helpers.ts b/server-new/app/helpers.ts index 00cf0bf..8246741 100644 --- a/server-new/app/helpers.ts +++ b/server-new/app/helpers.ts @@ -1,7 +1,7 @@ /** * League of Legends queues with defined role for each summoner */ -const queuesWithRole = [ +export const queuesWithRole = [ 0, // Custom 400, // Draft 420, // Solo/Duo @@ -13,7 +13,7 @@ const queuesWithRole = [ /** * League of Legends seasons timestamps */ -const seasons = { +export const seasons = { 0: 9, 1578628800000: 10, } @@ -21,30 +21,26 @@ const seasons = { /** * League of Legends all support item ids */ -const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864] +export const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 3860, 3862, 3863, 3864] -module.exports = { - queuesWithRole, - seasons, - supportItems, - /** - * Get season number for a match - */ - getSeasonNumber (timestamp: 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 Roles according to a specific order - * @param a first role - * @param b second role - */ - sortTeamByRole (a:any, b:any) { - const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] - return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) - }, +/** + * Get season number for a match + * @param timestamp + */ +export function getSeasonNumber (timestamp: 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 Roles according to a specific order + * @param a first role + * @param b second role + */ +export function sortTeamByRole (a:any, b:any) { + const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] + return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) } diff --git a/server-new/contracts/league.ts b/server-new/contracts/league.ts index 9b77485..8c4b36c 100644 --- a/server-new/contracts/league.ts +++ b/server-new/contracts/league.ts @@ -1,5 +1,14 @@ declare module '@ioc:Adonis/League' { - interface Account { + import { MatchReferenceDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint' + interface SummonerModel { + puuid: string, + matchList?: MatchReferenceDto[], + names?: SummonerNames[] + } + + interface SummonerNames { + name: string, + date: Date } } diff --git a/server-new/start/routes.ts b/server-new/start/routes.ts index 3364f8c..f1ed35f 100644 --- a/server-new/start/routes.ts +++ b/server-new/start/routes.ts @@ -36,6 +36,13 @@ Route.get('jax', async () => { return { player: summoner } }) +Route.get('test', async () => { + const summonersCollection = await mongodb.connection().collection('summoners') + const summonerDB = await summonersCollection.findOne({ puuid: 1234 }) + + return { player: summonerDB } +}) + Route.post('/summoner/basic', 'SummonersController.basic') Route.post('/summoner/overview', 'SummonersController.overview') Route.post('/summoner/champions', 'SummonersController.champions') From 25af97d4a627e2a4e92371a7bb59a702083c57ff Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 12:40:02 +0200 Subject: [PATCH 09/33] refactor: create Summoner Mongo Model --- .../Controllers/Http/SummonersController.ts | 11 ++++------ server-new/app/Models/Summoner.ts | 21 +++++++++++++++++++ server-new/app/Services/MatchService.ts | 2 +- server-new/app/Services/SummonerService.ts | 2 +- server-new/contracts/league.ts | 11 ---------- 5 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 server-new/app/Models/Summoner.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index 5a28151..be41827 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -1,6 +1,5 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' -import { SummonerModel } from '@ioc:Adonis/League' -import mongodb from '@ioc:Mongodb/Database' +import Summoner from 'App/Models/Summoner' import MatchRepository from 'App/Repositories/MatchRepository' import Jax from 'App/Services/Jax' import MatchService from 'App/Services/MatchService' @@ -44,11 +43,9 @@ export default class SummonersController { // { puuid: account.puuid } // ) - const summonersCollection = await mongodb.connection().collection('summoners') - let summonerDB:SummonerModel|null = await summonersCollection.findOne({ puuid: account.puuid }) + let summonerDB = await Summoner.findOne({ puuid: account.puuid }) if(!summonerDB) { - await summonersCollection.insertOne({ puuid: account.puuid }) - summonerDB = {puuid: account.puuid } + summonerDB = await Summoner.create({ puuid: account.puuid }) } // Summoner names @@ -71,7 +68,7 @@ export default class SummonersController { // SAVE IN DB // await summonerDB.save() - await summonersCollection.updateOne({ puuid: account.puuid }, summonerDB) + await summonerDB.save() } catch (error) { console.log('username not found') console.log(error) diff --git a/server-new/app/Models/Summoner.ts b/server-new/app/Models/Summoner.ts new file mode 100644 index 0000000..f56d9cb --- /dev/null +++ b/server-new/app/Models/Summoner.ts @@ -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[] +} diff --git a/server-new/app/Services/MatchService.ts b/server-new/app/Services/MatchService.ts index 5cc580e..2b1aa8f 100644 --- a/server-new/app/Services/MatchService.ts +++ b/server-new/app/Services/MatchService.ts @@ -3,7 +3,7 @@ import Jax from './Jax' import { getSeasonNumber } from 'App/helpers' import { MatchReferenceDto } from './Jax/src/Endpoints/MatchListEndpoint' import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint' -import { SummonerModel } from '@ioc:Adonis/League' +import { SummonerModel } from 'App/Models/Summoner' class MatchService { /** diff --git a/server-new/app/Services/SummonerService.ts b/server-new/app/Services/SummonerService.ts index 0798e2b..dea8713 100644 --- a/server-new/app/Services/SummonerService.ts +++ b/server-new/app/Services/SummonerService.ts @@ -1,7 +1,7 @@ import Jax from './Jax' import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' import { LeagueEntryDTO } from './Jax/src/Endpoints/LeagueEndpoint' -import { SummonerModel } from '@ioc:Adonis/League' +import { SummonerModel } from 'App/Models/Summoner' class SummonerService { private uniqueLeagues = ['CHALLENGER', 'GRANDMASTER', 'MASTER'] diff --git a/server-new/contracts/league.ts b/server-new/contracts/league.ts index 8c4b36c..144c28e 100644 --- a/server-new/contracts/league.ts +++ b/server-new/contracts/league.ts @@ -1,14 +1,3 @@ declare module '@ioc:Adonis/League' { - import { MatchReferenceDto } from 'App/Services/Jax/src/Endpoints/MatchlistEndpoint' - interface SummonerModel { - puuid: string, - matchList?: MatchReferenceDto[], - names?: SummonerNames[] - } - - interface SummonerNames { - name: string, - date: Date - } } From 3bbfab926035cb158c967535e88793416a563404 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 19:49:03 +0200 Subject: [PATCH 10/33] refactor: use validator for summoner/basic endpoint --- .../Controllers/Http/SummonersController.ts | 23 +++----- .../app/Repositories/MatchRepository.ts | 20 +++++-- .../app/Validators/SummonerBasicValidator.ts | 55 +++++++++++++++++++ 3 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 server-new/app/Validators/SummonerBasicValidator.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index be41827..6ceccbd 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -4,10 +4,15 @@ import MatchRepository from 'App/Repositories/MatchRepository' import Jax from 'App/Services/Jax' import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' +import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator' export default class SummonersController { + /** + * Get all played seasons for a summoner + * @param puuid of the summoner + */ private async getSeasons (puuid: string) { - let seasons = await MatchRepository.seasons(puuid) + const seasons = await MatchRepository.seasons(puuid) return seasons.length ? seasons.map(s => s._id) : [10] } @@ -17,15 +22,7 @@ export default class SummonersController { */ public async basic ({ request, response }: HttpContextContract) { 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 { summoner, region} = await request.validate(SummonerBasicValidator) const finalJSON:any = {} try { @@ -38,11 +35,6 @@ export default class SummonersController { finalJSON.account = account // Summoner in DB - // const summonerDB = await Summoner.findOrCreate( - // { puuid: account.puuid }, - // { puuid: account.puuid } - // ) - let summonerDB = await Summoner.findOne({ puuid: account.puuid }) if(!summonerDB) { summonerDB = await Summoner.create({ puuid: account.puuid }) @@ -67,7 +59,6 @@ export default class SummonersController { finalJSON.ranked = await SummonerService.getRanked(account, region) // SAVE IN DB - // await summonerDB.save() await summonerDB.save() } catch (error) { console.log('username not found') diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts index fc6c4cc..fdb2913 100644 --- a/server-new/app/Repositories/MatchRepository.ts +++ b/server-new/app/Repositories/MatchRepository.ts @@ -1,17 +1,26 @@ import mongodb from '@ioc:Mongodb/Database' +import { Collection } from 'mongodb' class MatchRepository { private season?: number + private collection: Collection constructor () { - // TODO: keep matches collection in the repo instance + this.getCollection() + } + + /** + * Get MongoDB matches collection + */ + private async getCollection () { + this.collection = await mongodb.connection().collection('matches') } /** * Basic matchParams used in a lot of requests * @param puuid of the summoner */ - private _matchParams (puuid: string) { + private matchParams (puuid: string) { return { summoner_puuid: puuid, result: { $not: { $eq: 'Remake' } }, @@ -19,18 +28,17 @@ class MatchRepository { season: this.season ? this.season : { $exists: true }, } } + /** * Get Summoner's played seasons * @param puuid of the summoner */ public async seasons (puuid: string) { this.season = undefined - const matchesCollections = await mongodb.connection().collection('matches') - - return matchesCollections.aggregate([ + return this.collection.aggregate([ { $match: { - ...this._matchParams(puuid), + ...this.matchParams(puuid), }, }, { diff --git a/server-new/app/Validators/SummonerBasicValidator.ts b/server-new/app/Validators/SummonerBasicValidator.ts new file mode 100644 index 0000000..82abb43 --- /dev/null +++ b/server-new/app/Validators/SummonerBasicValidator.ts @@ -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 = {} +} From 3862043980513841794b5b111295de1931dd06ac Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 20:47:16 +0200 Subject: [PATCH 11/33] feat: create Match and DetailedMatch Models --- server-new/app/Models/DetailedMatch.ts | 57 +++++++++++ server-new/app/Models/Match.ts | 126 +++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 server-new/app/Models/DetailedMatch.ts create mode 100644 server-new/app/Models/Match.ts diff --git a/server-new/app/Models/DetailedMatch.ts b/server-new/app/Models/DetailedMatch.ts new file mode 100644 index 0000000..9a7caa2 --- /dev/null +++ b/server-new/app/Models/DetailedMatch.ts @@ -0,0 +1,57 @@ +import { Model } from '@ioc:Mongodb/Model' +import { Champion, ParticipantDetails } from 'App/Models/Match' + +export interface DetailedMatchModel { + gameId: string, + 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 +} + +interface Ban { + championID: number, + pickTurn: number, + champion: Champion +} + +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: string + public season: number + public blueTeam: Team + public redTeam: Team + public map: number + public gamemode: number + public date: number + public region: string + public time: number +} diff --git a/server-new/app/Models/Match.ts b/server-new/app/Models/Match.ts new file mode 100644 index 0000000..776b3bc --- /dev/null +++ b/server-new/app/Models/Match.ts @@ -0,0 +1,126 @@ +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, +} + +export interface ParticipantDetails { + name: string, + summonerId: string, + champion: Champion, + role: string, + primaryRune: string, + secondaryRune: string, + level: number, + items: Item[], + firstSum: SummonerSpell | number, + secondSum: SummonerSpell | number, + stats: Stats, + percentStats?: PercentStats + rank?: Rank +} + +export interface Champion { + id: number, + name: string, + alias: string, + roles: string[], + icon: string +} + +interface SummonerSpell { + name: string, + description: string, + icon: string +} + +interface Rank { + tier: string, + shortName: string +} + +interface ParticipantBasic { + account_id: string, + name: string, + role: string, + champion: Champion +} + +interface Item { + image: string, + name: string, + description: string, + price: number +} + +interface Stats { + kills: number, + deaths: number, + assists: number, + minions: number, + vision: number, + gold: number, + dmgChamp: number, + dmgObj: number, + dmgTaken: number, + kda: number, + realKda: number, + criticalStrike: number, + killingSpree: number, + doubleKills: number, + tripleKills: number, + quadraKills: number, + pentaKills: number, + heal: number, + towers: number, + longestLiving: number, + kp: number, +} + +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 +} From 8184f17fbb1f358078aabc287aa09aa49f3d49c5 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Mon, 5 Oct 2020 21:33:17 +0200 Subject: [PATCH 12/33] feat: add summoner/champions endpoint --- .../Controllers/Http/SummonersController.ts | 19 +++- .../app/Repositories/MatchRepository.ts | 97 ++++++++++++++++--- .../Validators/SummonerChampionValidator.ts | 54 +++++++++++ server-new/providers/AppProvider.ts | 5 +- 4 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 server-new/app/Validators/SummonerChampionValidator.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index 6ceccbd..12b8694 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -5,6 +5,7 @@ import Jax from 'App/Services/Jax' import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator' +import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator' export default class SummonersController { /** @@ -22,8 +23,8 @@ export default class SummonersController { */ public async basic ({ request, response }: HttpContextContract) { console.time('all') - const { summoner, region} = await request.validate(SummonerBasicValidator) - const finalJSON:any = {} + const { summoner, region } = await request.validate(SummonerBasicValidator) + const finalJSON: any = {} try { const account = await SummonerService.getAccount(summoner, region) @@ -36,7 +37,7 @@ export default class SummonersController { // Summoner in DB let summonerDB = await Summoner.findOne({ puuid: account.puuid }) - if(!summonerDB) { + if (!summonerDB) { summonerDB = await Summoner.create({ puuid: account.puuid }) } @@ -69,4 +70,16 @@ export default class SummonersController { console.timeEnd('all') return response.json(finalJSON) } + + /** + * POST: get champions view summoner data + * @param ctx + */ + public async champions ({ request, response }) { + 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) + } } diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts index fdb2913..33d539d 100644 --- a/server-new/app/Repositories/MatchRepository.ts +++ b/server-new/app/Repositories/MatchRepository.ts @@ -2,39 +2,114 @@ import mongodb from '@ioc:Mongodb/Database' import { Collection } from 'mongodb' class MatchRepository { - private season?: number private collection: Collection constructor () { this.getCollection() } - /** - * Get MongoDB matches collection - */ - private async getCollection () { - this.collection = await mongodb.connection().collection('matches') - } - /** * Basic matchParams used in a lot of requests * @param puuid of the summoner */ - private matchParams (puuid: string) { + private matchParams (puuid: string, season?: number) { return { summoner_puuid: puuid, result: { $not: { $eq: 'Remake' } }, 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 + * @param puuid + * @param matchParams + * @param intermediateSteps + * @param groupId + * @param groupParams + * @param finalSteps + */ + private async aggregate ( + puuid: string, + matchParams: object, + intermediateSteps: any[], + groupId: any, + groupParams: object, + finalSteps: any[], + season?: number, + ) { + return this.collection.aggregate([ + { + $match: { + ...this.matchParams(puuid, season), + ...matchParams, + }, + }, + ...intermediateSteps, + { + $group: { + _id: groupId, + count: { $sum: 1 }, + wins: { + $sum: { + $cond: [{ $eq: ['$result', 'Win'] }, 1, 0], + }, + }, + losses: { + $sum: { + $cond: [{ $eq: ['$result', 'Fail'] }, 1, 0], + }, + }, + ...groupParams, + }, + }, + ...finalSteps, + ]).toArray() + } + + /** + * Get MongoDB matches collection + */ + public async getCollection () { + if (!this.collection) { + this.collection = await mongodb.connection().collection('matches') + } + } + + /** + * Get Summoner's complete statistics for the all played champs + * @param puuid of the summoner + * @param queue of the matches to fetch, if not set: get all matches + * @param season of the matches to fetch, if not set: get all seasons + */ + public async championCompleteStats (puuid: string, queue?: number, season?: number) { + const matchParams = queue ? { gamemode: { $eq: Number(queue) } } : {} + const groupParams = { + time: { $sum: '$time' }, + gameLength: { $avg: '$time' }, + date: { $max: '$date' }, + champion: { $first: '$champion' }, + kills: { $sum: '$stats.kills' }, + deaths: { $sum: '$stats.deaths' }, + assists: { $sum: '$stats.assists' }, + minions: { $avg: '$stats.minions' }, + gold: { $avg: '$stats.gold' }, + dmgChamp: { $avg: '$stats.dmgChamp' }, + dmgTaken: { $avg: '$stats.dmgTaken' }, + kp: { $avg: '$stats.kp' }, + } + const finalSteps = [ + { $sort: { 'count': -1, 'champion.name': 1 } }, + ] + return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season) + } + /** * Get Summoner's played seasons * @param puuid of the summoner */ public async seasons (puuid: string) { - this.season = undefined return this.collection.aggregate([ { $match: { diff --git a/server-new/app/Validators/SummonerChampionValidator.ts b/server-new/app/Validators/SummonerChampionValidator.ts new file mode 100644 index 0000000..31c1ed9 --- /dev/null +++ b/server-new/app/Validators/SummonerChampionValidator.ts @@ -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 = {} +} diff --git a/server-new/providers/AppProvider.ts b/server-new/providers/AppProvider.ts index 624be77..8408dbe 100644 --- a/server-new/providers/AppProvider.ts +++ b/server-new/providers/AppProvider.ts @@ -8,8 +8,11 @@ export default class AppProvider { // Register your own bindings } - public boot () { + public async boot () { // IoC container is ready + + // Load Match Collections + await import('App/Repositories/MatchRepository') } public shutdown () { From 8e272093c9318635ad8908c9c610b758cfb544d0 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 17:47:27 +0200 Subject: [PATCH 13/33] feat: add summoner/records endpoint --- .../Controllers/Http/SummonersController.ts | 13 +++ .../app/Repositories/MatchRepository.ts | 100 ++++++++++++++++++ .../app/Validators/SummonerRecordValidator.ts | 53 ++++++++++ 3 files changed, 166 insertions(+) create mode 100644 server-new/app/Validators/SummonerRecordValidator.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index 12b8694..5e16950 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -6,6 +6,7 @@ import MatchService from 'App/Services/MatchService' import SummonerService from 'App/Services/SummonerService' import SummonerBasicValidator from 'App/Validators/SummonerBasicValidator' import SummonerChampionValidator from 'App/Validators/SummonerChampionValidator' +import SummonerRecordValidator from 'App/Validators/SummonerRecordValidator' export default class SummonersController { /** @@ -82,4 +83,16 @@ export default class SummonersController { console.timeEnd('championsRequest') return response.json(championStats) } + + /** + * POST: get records view summoner data + * @param ctx + */ + public async records ({ request, response }) { + console.time('recordsRequest') + const { puuid, season } = await request.validate(SummonerRecordValidator) + const records = await MatchRepository.records(puuid, season) + console.timeEnd('recordsRequest') + return response.json(records) + } } diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts index 33d539d..41bece3 100644 --- a/server-new/app/Repositories/MatchRepository.ts +++ b/server-new/app/Repositories/MatchRepository.ts @@ -105,6 +105,106 @@ class MatchRepository { return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season) } + /** + * Get Summoner's all records + * @param puuid of the summoner + * @param season of the matches to fetch, if null get all seasons + */ + public async records (puuid: string, season?: number) { + const records = await this.collection.aggregate([ + { + $match: { + ...this.matchParams(puuid, season), + }, + }, + { + $group: { + _id: null, + maxKills: { $max: '$stats.kills' }, + maxDeaths: { $max: '$stats.deaths' }, + maxAssists: { $max: '$stats.assists' }, + maxGold: { $max: '$stats.gold' }, + maxTime: { $max: '$time' }, + maxMinions: { $max: '$stats.minions' }, + maxKda: { $max: '$stats.realKda' }, + maxDmgTaken: { $max: '$stats.dmgTaken' }, + maxDmgChamp: { $max: '$stats.dmgChamp' }, + maxDmgObj: { $max: '$stats.dmgObj' }, + maxKp: { $max: '$stats.kp' }, + maxVision: { $max: '$stats.vision' }, + maxCriticalStrike: { $max: '$stats.criticalStrike' }, + maxLiving: { $max: '$stats.longestLiving' }, + maxHeal: { $max: '$stats.heal' }, + maxTowers: { $max: '$stats.towers' }, + maxKillingSpree: { $max: '$stats.killingSpree' }, + maxDouble: { $max: '$stats.doubleKills' }, + maxTriple: { $max: '$stats.tripleKills' }, + maxQuadra: { $max: '$stats.quadraKills' }, + maxPenta: { $max: '$stats.pentaKills' }, + docs: { + '$push': { + 'champion': '$champion', + 'gameId': '$gameId', + 'kills': '$stats.kills', + 'deaths': '$stats.deaths', + 'assists': '$stats.assists', + 'gold': '$stats.gold', + 'time': '$time', + 'minions': '$stats.minions', + 'kda': '$stats.realKda', + 'dmgTaken': '$stats.dmgTaken', + 'dmgChamp': '$stats.dmgChamp', + 'dmgObj': '$stats.dmgObj', + 'kp': '$stats.kp', + 'vision': '$stats.vision', + 'criticalStrike': '$stats.criticalStrike', + 'longestLiving': '$stats.longestLiving', + 'heal': '$stats.heal', + 'towers': '$stats.towers', + 'killingSpree': '$stats.killingSpree', + 'doubleKills': '$stats.doubleKills', + 'tripleKills': '$stats.tripleKills', + 'quadraKills': '$stats.quadraKills', + 'pentaKills': '$stats.pentaKills', + 'result': '$result', + 'date': '$date', + 'gamemode': '$gamemode', + }, + }, + }, + }, + { + $project: { + _id: 0, + /* eslint-disable max-len */ + maxKills: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kills', '$maxKills'] } } }, 0] }, + maxDeaths: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.deaths', '$maxDeaths'] } } }, 0] }, + maxAssists: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.assists', '$maxAssists'] } } }, 0] }, + maxGold: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.gold', '$maxGold'] } } }, 0] }, + maxTime: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.time', '$maxTime'] } } }, 0] }, + maxMinions: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.minions', '$maxMinions'] } } }, 0] }, + maxKda: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kda', '$maxKda'] } } }, 0] }, + maxDmgTaken: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgTaken', '$maxDmgTaken'] } } }, 0] }, + maxDmgChamp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgChamp', '$maxDmgChamp'] } } }, 0] }, + maxDmgObj: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.dmgObj', '$maxDmgObj'] } } }, 0] }, + maxKp: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.kp', '$maxKp'] } } }, 0] }, + maxVision: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.vision', '$maxVision'] } } }, 0] }, + maxCriticalStrike: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.criticalStrike', '$maxCriticalStrike'] } } }, 0] }, + maxLiving: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.longestLiving', '$maxLiving'] } } }, 0] }, + maxHeal: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.heal', '$maxHeal'] } } }, 0] }, + maxTowers: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.towers', '$maxTowers'] } } }, 0] }, + maxKillingSpree: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.killingSpree', '$maxKillingSpree'] } } }, 0] }, + maxDouble: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.doubleKills', '$maxDouble'] } } }, 0] }, + maxTriple: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.tripleKills', '$maxTriple'] } } }, 0] }, + maxQuadra: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.quadraKills', '$maxQuadra'] } } }, 0] }, + maxPenta: { $arrayElemAt: [{ $filter: { input: '$docs', cond: { $eq: ['$$this.pentaKills', '$maxPenta'] } } }, 0] }, + }, + }, + ]).toArray() + + return records[0] + } + /** * Get Summoner's played seasons * @param puuid of the summoner diff --git a/server-new/app/Validators/SummonerRecordValidator.ts b/server-new/app/Validators/SummonerRecordValidator.ts new file mode 100644 index 0000000..5485409 --- /dev/null +++ b/server-new/app/Validators/SummonerRecordValidator.ts @@ -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 = {} +} From b565a0bc5523214b06fd9d04589ef8dec777b5e5 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 18:43:04 +0200 Subject: [PATCH 14/33] feat: add RoleIdentificationService --- .../app/Services/RoleIdentiticationService.ts | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 server-new/app/Services/RoleIdentiticationService.ts diff --git a/server-new/app/Services/RoleIdentiticationService.ts b/server-new/app/Services/RoleIdentiticationService.ts new file mode 100644 index 0000000..d031381 --- /dev/null +++ b/server-new/app/Services/RoleIdentiticationService.ts @@ -0,0 +1,275 @@ +import Redis from '@ioc:Adonis/Addons/Redis' +import got from 'got/dist/source' + +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 { + private _getPermutations (array: number[]) { + const result: number[][] = [] + + for (let i = 0; i < array.length; i++) { + const rest = this._getPermutations(array.slice(0, i).concat(array.slice(i + 1))) + + if (!rest.length) { + result.push([array[i]]) + } else { + for (let j = 0; j < rest.length; j++) { + result.push([array[i]].concat(rest[j])) + } + } + } + return result + } + + private _calculateMetric (championPositions: ChampionsRates, bestPositions: RoleComposition) { + return Object.entries(bestPositions).reduce((agg, [position, champion]) => { + return agg + (championPositions[champion][position] || 0) + }, 0) / Object.keys(bestPositions).length + } + + 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 + let bestPositions: RoleComposition = { + 'TOP': composition[0], + 'JUNGLE': composition[1], + 'MIDDLE': composition[2], + 'BOTTOM': composition[3], + 'UTILITY': composition[4], + } + + let bestMetric = this._calculateMetric(championPositions, bestPositions) + let secondBestMetric = -Infinity + let secondBestPositions: RoleComposition | null = null + + // Figure out which champions and positions we need to fill + const knownChampions = [top, jungle, middle, adc, support].filter(Boolean) + const unknownChampions = composition.filter(champ => !knownChampions.includes(champ)) + const unknownPositions = Object.entries({ + 'TOP': top, 'JUNGLE': jungle, 'MIDDLE': middle, 'BOTTOM': adc, 'UTILITY': support, + }) + .filter(pos => !pos[1]) + .map(pos => pos[0]) + + const testComposition: RoleComposition = { + 'TOP': top, + 'JUNGLE': jungle, + 'MIDDLE': middle, + 'BOTTOM': adc, + 'UTILITY': support, + } + + // Iterate over the positions we need to fill and record how well each composition "performs" + for (const champs of this._getPermutations(unknownChampions)) { + for (let [i, position] of unknownPositions.entries()) { + testComposition[position] = champs[i] + } + + const metric = this._calculateMetric(championPositions, testComposition) + + if (metric > bestMetric) { + secondBestMetric = bestMetric + secondBestPositions = bestPositions + bestMetric = metric + bestPositions = { ...testComposition } + } + + if (bestMetric > metric && metric > secondBestMetric) { + secondBestMetric = metric + secondBestPositions = { ...testComposition } + } + } + + const bestPlayPercents: ChampionComposition = {} + for (const [position, champion] of Object.entries(bestPositions)) { + bestPlayPercents[champion] = championPositions[champion][position] + } + + let secondBestPlayPercents: ChampionComposition | null = null + if (secondBestPositions !== null) { + secondBestPlayPercents = {} + for (const [position, champion] of Object.entries(secondBestPositions)) { + secondBestPlayPercents[champion] = championPositions[champion][position] + } + } + + if (JSON.stringify(secondBestPositions) === JSON.stringify(bestPositions)) { + secondBestPositions = null + secondBestPlayPercents = null + secondBestMetric = -Infinity + } + + return { bestPositions, bestMetric, secondBestPositions } + } + + /** + * Get the CDN data of the champion playrates by role + */ + public async pullData () { + const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json' + + // Check if cached + const requestCached = await Redis.get(url) + if (requestCached) { + return JSON.parse(requestCached) + } + + const data = {} + const { body }: { body: ApiRoleResponse } = await got(url, { responseType: 'json' }) + + for (const [championId, roles] of Object.entries(body.data)) { + const playRates = {} + + for (const [position, rates] of Object.entries(roles)) { + playRates[position.toUpperCase()] = rates['playRate'] + } + + for (const position of ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'UTILITY']) { + if (playRates[position] === undefined) { + playRates[position] = 0 + } + } + + data[championId] = playRates + } + + // Cache result + await Redis.set(url, JSON.stringify(data), 'EX', 36000) + + return data + } + + /** + * Get roles for the 5 players of a team + * @param championPositions + * @param composition + * @param jungle + * @param support + */ + public getRoles (championPositions: ChampionsRates, composition: number[], jungle?: number, support?: number) { + // Set composition champion playrate to 0% if not present in the json data + for (const compChamp of composition) { + if (championPositions[compChamp]) { + continue + } + + championPositions[compChamp] = { + MIDDLE: 0, + UTILITY: 0, + TOP: 0, + JUNGLE: 0, + BOTTOM: 0, + } + } + + const identified: RoleComposition = {} + let positions: RoleComposition = {} + let secondaryPositions: RoleComposition | null = null + let secondaryMetric = -Infinity + + if (jungle) { + identified['JUNGLE'] = jungle + } + + if (support) { + identified['UTILITY'] = support + } + + while (Object.keys(identified).length < composition.length - 1) { + let { bestPositions, bestMetric: metric, secondBestPositions: sbp } = + this._getPositions(championPositions, composition, + identified.TOP, identified.JUNGLE, identified.MIDDLE, identified.BOTTOM, identified.UTILITY + ) + + positions = bestPositions + + if (sbp !== null) { + let currentMetric = this._calculateMetric(championPositions, { ...sbp }) + + if (secondaryPositions === null) { + secondaryPositions = sbp + secondaryMetric = currentMetric + } else if (metric > currentMetric && currentMetric > secondaryMetric) { + secondaryMetric = currentMetric + secondaryPositions = sbp + } + } + + // Done! Grab the results. + const positionsWithMetric = {} + for (const [position, champion] of Object.entries(positions)) { + if (Object.keys(identified).includes(position) || champion === jungle || champion === support) { + continue + } + positionsWithMetric[position] = { + champion, + 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) => { + return positionsWithMetric[posA].metric > positionsWithMetric[posB].metric ? posA : posB + }) + + const best = [bestPosition, positionsWithMetric[bestPosition].champion] + identified[best[0]] = best[1] + } + + // Rename UTILITY to SUPPORT + const { + UTILITY: SUPPORT, + ...rest + } = positions + + return { ...rest, SUPPORT } + } +} + +export default new RoleIdentificationService() From 7d74309d1bec6c312451269f9d9d069888e16bc3 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 19:08:16 +0200 Subject: [PATCH 15/33] feat: add type to Jax Match Endpoint --- .../Jax/src/Endpoints/MatchEndpoint.ts | 209 +++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts index 26cc39a..c3676b8 100644 --- a/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts @@ -2,6 +2,213 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' 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 @@ -13,7 +220,7 @@ export default class MatchEndpoint { this.get = this.get.bind(this) } - public get (matchID: number, region: string) { + public get (matchID: number, region: string): Promise { return new JaxRequest( region, this.config, From aa4659b53ec3bd19b142e5eea22b0164078af6b8 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 22:02:34 +0200 Subject: [PATCH 16/33] chore: increase eslint max-len rule --- server-new/.eslintrc.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server-new/.eslintrc.json b/server-new/.eslintrc.json index fc3312c..8fae5d1 100644 --- a/server-new/.eslintrc.json +++ b/server-new/.eslintrc.json @@ -1,5 +1,13 @@ { "extends": [ "plugin:adonis/typescriptApp" - ] -} + ], + "rules": { + "max-len": [ + "error", + { + "code": 130 + } + ] + } +} \ No newline at end of file From 54a0e0747423a7af709bb6ec93f1f96baa95965a Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 22:03:24 +0200 Subject: [PATCH 17/33] feat: create Match, BasicMatch and LiveMatch Transformers --- server-new/app/Models/Match.ts | 40 +-- .../app/Services/RoleIdentiticationService.ts | 15 +- server-new/app/Services/SummonerService.ts | 22 +- .../app/Transformers/BasicMatchTransformer.ts | 83 +++++ .../app/Transformers/LiveMatchTransformer.ts | 72 ++++ .../app/Transformers/MatchTransformer.ts | 314 ++++++++++++++++++ server-new/app/helpers.ts | 4 +- 7 files changed, 522 insertions(+), 28 deletions(-) create mode 100644 server-new/app/Transformers/BasicMatchTransformer.ts create mode 100644 server-new/app/Transformers/LiveMatchTransformer.ts create mode 100644 server-new/app/Transformers/MatchTransformer.ts diff --git a/server-new/app/Models/Match.ts b/server-new/app/Models/Match.ts index 776b3bc..a899e8d 100644 --- a/server-new/app/Models/Match.ts +++ b/server-new/app/Models/Match.ts @@ -20,10 +20,10 @@ export interface ParticipantDetails { summonerId: string, champion: Champion, role: string, - primaryRune: string, - secondaryRune: string, + primaryRune: string | null, + secondaryRune: string | null, level: number, - items: Item[], + items: (Item | null)[], firstSum: SummonerSpell | number, secondSum: SummonerSpell | number, stats: Stats, @@ -39,32 +39,32 @@ export interface Champion { icon: string } -interface SummonerSpell { +export interface SummonerSpell { name: string, description: string, icon: string } -interface Rank { +export interface Rank { tier: string, shortName: string } -interface ParticipantBasic { +export interface ParticipantBasic { account_id: string, name: string, role: string, champion: Champion } -interface Item { +export interface Item { image: string, name: string, description: string, price: number } -interface Stats { +export interface Stats { kills: number, deaths: number, assists: number, @@ -74,21 +74,21 @@ interface Stats { dmgChamp: number, dmgObj: number, dmgTaken: number, - kda: 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, + criticalStrike?: number, + killingSpree?: number, + doubleKills?: number, + tripleKills?: number, + quadraKills?: number, + pentaKills?: number, + heal?: number, + towers?: number, + longestLiving?: number, + kp: number | string, } -interface PercentStats { +export interface PercentStats { minions: number, vision: number, gold: string, diff --git a/server-new/app/Services/RoleIdentiticationService.ts b/server-new/app/Services/RoleIdentiticationService.ts index d031381..f2a2ae2 100644 --- a/server-new/app/Services/RoleIdentiticationService.ts +++ b/server-new/app/Services/RoleIdentiticationService.ts @@ -1,6 +1,14 @@ import Redis from '@ioc:Adonis/Addons/Redis' import got from 'got/dist/source' +export interface FinalRoleComposition { + 'TOP'?: number, + 'JUNGLE'?: number, + 'MIDDLE'?: number, + 'BOTTOM'?: number, + 'SUPPORT'?: number, +} + export interface RoleComposition { 'TOP'?: number, 'JUNGLE'?: number, @@ -186,7 +194,12 @@ class RoleIdentificationService { * @param jungle * @param support */ - public getRoles (championPositions: ChampionsRates, composition: number[], jungle?: number, support?: number) { + public getRoles ( + championPositions: ChampionsRates, + composition: number[], + jungle?: number, + support?: number + ): FinalRoleComposition { // Set composition champion playrate to 0% if not present in the json data for (const compChamp of composition) { if (championPositions[compChamp]) { diff --git a/server-new/app/Services/SummonerService.ts b/server-new/app/Services/SummonerService.ts index dea8713..02a3fdc 100644 --- a/server-new/app/Services/SummonerService.ts +++ b/server-new/app/Services/SummonerService.ts @@ -3,6 +3,17 @@ 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 } @@ -11,7 +22,7 @@ class SummonerService { * Helper to transform League Data from the Riot API * @param league raw data of the league from Riot API */ - private getleagueData (league?: LeagueEntryDTO) { + private getleagueData (league?: LeagueEntryDTO): LeagueEntryByQueue | null { if (!league) { return null } @@ -45,7 +56,7 @@ class SummonerService { * @param account of the summoner * @param summonerDB summoner in the database */ - public getAllSummonerNames (account: SummonerDTO, summonerDB:SummonerModel) { + public getAllSummonerNames (account: SummonerDTO, summonerDB: SummonerModel) { const names = summonerDB.names ? summonerDB.names : [] if (!names.find(n => n.name === account.name)) { @@ -64,12 +75,11 @@ class SummonerService { * @param account * @param region */ - public async getRanked (account: SummonerDTO, region: string) { + public async getRanked (account: SummonerDTO, region: string): Promise { 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, + 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 } diff --git a/server-new/app/Transformers/BasicMatchTransformer.ts b/server-new/app/Transformers/BasicMatchTransformer.ts new file mode 100644 index 0000000..74e2d33 --- /dev/null +++ b/server-new/app/Transformers/BasicMatchTransformer.ts @@ -0,0 +1,83 @@ +import { MatchModel, ParticipantBasic } from 'App/Models/Match' +import { MatchDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint' +import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' +import MatchTransformer from 'App/Transformers/MatchTransformer' + +class BasicMatchTransformer extends MatchTransformer { + /** + * Transform raw data for 1 match + * @param match + * @param account + */ + private transformOneMatch (match: MatchDto, account: SummonerDTO) { + // Global data about the match + const globalInfos = super.getGameInfos(match) + + const identity = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId) + const player = match.participants[identity!.participantId - 1] + + let win = match.teams.find((t) => t.teamId === player.teamId)!.win + + // Match less than 5min + if (match.gameDuration < 300) { + win = 'Remake' + } + + // Player data + const playerData = super.getPlayerData(match, player, false) + + // Teams data + const allyTeam:ParticipantBasic[] = [] + const enemyTeam:ParticipantBasic[] = [] + for (let summoner of match.participantIdentities) { + const allData = match.participants[summoner.participantId - 1] + const playerInfos = { + account_id: summoner.player.currentAccountId, + name: summoner.player.summonerName, + role: super.getRoleName(allData.timeline, match.queueId), + champion: super.getChampion(allData.championId), + } + + if (allData.teamId === player.teamId) { + allyTeam.push(playerInfos) + } else { + enemyTeam.push(playerInfos) + } + } + + // Roles + super.getMatchRoles(match, allyTeam, enemyTeam, player.teamId, playerData) + + return { + account_id: identity!.player.currentAccountId, + summoner_puuid: account.puuid, + gameId: match.gameId, + result: win, + allyTeam, + enemyTeam, + ...globalInfos, + ...playerData, + } + } + + /** + * Transform raw data from Riot API + * @param matches data from Riot API, Array of match or a single match + * @param ctx context + */ + public async transform (matches: MatchDto[] | MatchDto, { account }: { account: SummonerDTO }) { + await super.getContext() + + if (Array.isArray(matches)) { + const finalMatches:MatchModel[] = [] + matches.forEach((match, index) => { + finalMatches[index] = this.transformOneMatch(match, account) + }) + return finalMatches + } else { + return this.transformOneMatch(matches, account) as MatchModel + } + } +} + +export default new BasicMatchTransformer() diff --git a/server-new/app/Transformers/LiveMatchTransformer.ts b/server-new/app/Transformers/LiveMatchTransformer.ts new file mode 100644 index 0000000..9d4018f --- /dev/null +++ b/server-new/app/Transformers/LiveMatchTransformer.ts @@ -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() diff --git a/server-new/app/Transformers/MatchTransformer.ts b/server-new/app/Transformers/MatchTransformer.ts new file mode 100644 index 0000000..83e0966 --- /dev/null +++ b/server-new/app/Transformers/MatchTransformer.ts @@ -0,0 +1,314 @@ +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 { Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats } from 'App/Models/Match' +import RoleIdentificationService from 'App/Services/RoleIdentiticationService' + +export interface PlayerRole { + champion: number, + jungle?: boolean, + support?: boolean, +} + +export default abstract class MatchTransformer { + protected champions: any + protected items: any + protected perks: any + protected perkstyles: any + protected summonerSpells: any + protected championRoles: any + protected sortTeamByRole: (a: ParticipantBasic, b: ParticipantBasic) => number + /** + * Get global Context with CDragon Data + */ + public async getContext () { + const items = await Jax.CDragon.items() + const champions = await Jax.CDragon.champions() + const perks = await Jax.CDragon.perks() + const perkstyles = await Jax.CDragon.perkstyles() + const summonerSpells = await Jax.CDragon.summonerSpells() + const championRoles = await RoleIdentificationService.pullData().catch(() => { }) + + this.champions = champions + this.items = items + this.perks = perks + this.perkstyles = perkstyles.styles + this.summonerSpells = summonerSpells + this.championRoles = championRoles + this.sortTeamByRole = sortTeamByRole + } + + /** + * Get champion specific data + * @param id of the champion + */ + public getChampion (id: number) { + const champion = { ...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()} + ` + delete champion.squarePortraitPath + return champion + } + + /** + * Get global data about the match + */ + public getGameInfos (match: MatchDto) { + return { + map: match.mapId, + gamemode: match.queueId, + date: match.gameCreation, + region: match.platformId.toLowerCase(), + season: getSeasonNumber(match.gameCreation), + time: match.gameDuration, + } + } + + /** + * Get player specific data during the match + * @param match + * @param player + * @param detailed : detailed or not stats + * @param teamStats : if detailed, the teamStats argument is mandatory + */ + public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats: any = {}) { + const identity = match.participantIdentities.find(p => p.participantId === player.participantId) + const name = identity!.player.summonerName + const champion = this.getChampion(player.championId) + const role = this.getRoleName(player.timeline, match.queueId) + const level = player.stats.champLevel + + // Regular stats / Full match stats + const stats: Stats = { + kills: player.stats.kills, + deaths: player.stats.deaths, + assists: player.stats.assists, + minions: player.stats.totalMinionsKilled + player.stats.neutralMinionsKilled, + vision: player.stats.visionScore, + gold: player.stats.goldEarned, + dmgChamp: player.stats.totalDamageDealtToChampions, + dmgObj: player.stats.damageDealtToObjectives, + dmgTaken: player.stats.totalDamageTaken, + kp: 0, + kda: 0, + realKda: 0, + } + + if (stats.kills + stats.assists !== 0 && stats.deaths === 0) { + stats.kda = '∞' + stats.realKda = stats.kills + stats.assists + } else { + stats.kda = +(stats.deaths === 0 ? 0 : ((stats.kills + stats.assists) / stats.deaths)).toFixed(2) + stats.realKda = stats.kda + } + + // Percent stats / Per minute stats : only for detailed match + let percentStats: PercentStats + if (detailed) { + percentStats = { + minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2), + vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2), + gold: +(player.stats.goldEarned * 100 / teamStats.gold).toFixed(1) + '%', + dmgChamp: +(player.stats.totalDamageDealtToChampions * 100 / teamStats.dmgChamp).toFixed(1) + '%', + dmgObj: +(teamStats.dmgObj ? player.stats.damageDealtToObjectives * 100 / teamStats.dmgObj : 0).toFixed(1) + '%', + dmgTaken: +(player.stats.totalDamageTaken * 100 / teamStats.dmgTaken).toFixed(1) + '%', + } + + stats.kp = teamStats.kills === 0 ? '0%' : +((stats.kills + stats.assists) * 100 / teamStats.kills).toFixed(1) + '%' + } else { + const totalKills = match.participants.reduce((prev, current) => { + if (current.teamId !== player.teamId) { + return prev + } + return prev + current.stats.kills + }, 0) + + stats.criticalStrike = player.stats.largestCriticalStrike + stats.killingSpree = player.stats.largestKillingSpree + stats.doubleKills = player.stats.doubleKills + stats.tripleKills = player.stats.tripleKills + stats.quadraKills = player.stats.quadraKills + stats.pentaKills = player.stats.pentaKills + stats.heal = player.stats.totalHeal + stats.towers = player.stats.turretKills + stats.longestLiving = player.stats.longestTimeSpentLiving + stats.kp = totalKills === 0 ? 0 : +((stats.kills + stats.assists) * 100 / totalKills).toFixed(1) + } + + let primaryRune: string | null = null + let secondaryRune: string | null = null + if (player.stats.perkPrimaryStyle) { + ({ primaryRune, secondaryRune } = this.getPerksImages(player.stats.perk0, player.stats.perkSubStyle)) + } + + const items: (Item | null)[] = [] + for (let i = 0; i < 6; i++) { + const id = player.stats['item' + i] + if (id === 0) { + items.push(null) + continue + } + + const item = this.items.find((i: any) => i.id === id) + const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase() + + items.push({ + image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`, + name: item.name, + description: item.description, + price: item.priceTotal, + }) + } + + const firstSum = player.spell1Id + const secondSum = player.spell2Id + + const playerData: ParticipantDetails = { + name, + summonerId: identity!.player.summonerId, + champion, + role, + primaryRune, + secondaryRune, + level, + items, + firstSum, + secondSum, + stats, + } + if (detailed) { + playerData.percentStats = percentStats! + } + + return playerData + } + + /** + * Return the icons of the primary rune and secondary category + * @param perk0 primary perks id + * @param perkSubStyle secondary perks category + */ + public getPerksImages (perk0: number, perkSubStyle: number) { + const firstRune = this.perks.find((p: any) => p.id === perk0) + const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase() + const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}` + + const secondRuneStyle = this.perkstyles.find((p: any) => p.id === perkSubStyle) + + const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null + const secondaryRune = secondRuneStyleUrl ? + `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${secondRuneStyleUrl}` + : '' + + return { primaryRune, secondaryRune } + } + + /** + * Return the lane of the summoner according to timeline + * @param timeline from Riot Api + * @param gamemode of the match to check if a role is needed + */ + public getRoleName (timeline: ParticipantTimelineDto, gamemode: number) { + if (!queuesWithRole.includes(gamemode)) { + return 'NONE' + } + + if (timeline.lane === 'BOTTOM' && timeline.role.includes('SUPPORT')) { + return 'SUPPORT' + } + + return timeline.lane + } + + /** + * Return the 5 roles of a team based on champions + * @param team 5 champions + smite from a team + */ + public getTeamRoles (team: PlayerRole[]) { + const teamJunglers = team.filter(p => p.jungle && !p.support) + const jungle = teamJunglers.length === 1 ? teamJunglers[0].champion : undefined + const teamSupports = team.filter(p => p.support && !p.jungle) + const support = teamSupports.length === 1 ? teamSupports[0].champion : undefined + + return RoleIdentificationService.getRoles(this.championRoles, team.map(p => p.champion), jungle, support) + } + + /** + * Update roles for a team if Riot's ones are badly identified + * @param team 5 players data of the team + * @param champs 5 champions + smite from the team + * @param playerData data of the searched player, only for basic matches + */ + public updateTeamRoles (team: ParticipantBasic[], champs: PlayerRole[], playerData?: ParticipantDetails) { + // const actualRoles = [...new Set(team.map(p => p.role))] + // if (actualRoles.length === 5) { + // return + // } + + const identifiedChamps = this.getTeamRoles(champs) + for (const summoner of team) { + summoner.role = Object.entries(identifiedChamps).find(([, champion]) => summoner.champion.id === champion)![0] + + if (playerData && summoner.champion.id === playerData.champion.id) { + playerData.role = summoner.role + } + } + } + + /** + * + * @param match from Riot Api + * @param allyTeam 5 players of the first team + * @param enemyTeam 5 players of the second team + * @param allyTeamId team id of the searched player, only for basic matches + * @param playerData data of the searched player, only for basic matches + */ + public getMatchRoles ( + match: MatchDto, + allyTeam: ParticipantBasic[], + enemyTeam: ParticipantBasic[], + allyTeamId = 100, + playerData?: ParticipantDetails + ) { + if (!this.championRoles || !queuesWithRole.includes(match.queueId)) { + return + } + + let allyChamps: PlayerRole[] = [] + let enemyChamps: PlayerRole[] = [] + match.participants.map(p => { + const items = [p.stats.item0, p.stats.item1, p.stats.item2, p.stats.item3, p.stats.item4, p.stats.item5] + const playerRole = { + champion: p.championId, + jungle: p.spell1Id === 11 || p.spell2Id === 11, + support: supportItems.some(suppItem => items.includes(suppItem)), + } + p.teamId === allyTeamId ? allyChamps.push(playerRole) : enemyChamps.push(playerRole) + }) + + this.updateTeamRoles(allyTeam, allyChamps, playerData) + this.updateTeamRoles(enemyTeam, enemyChamps) + + allyTeam.sort(this.sortTeamByRole) + enemyTeam.sort(this.sortTeamByRole) + } + + /** + * Get Summoner Spell Data from CDragon + * @param id of the summonerSpell + */ + public getSummonerSpell (id: number) { + if (id === 0) { + return null + } + const spell = this.summonerSpells.find((s: any) => s.id === id) + const spellName = spell.iconPath.split('/assets/')[1].toLowerCase() + return { + name: spell.name, + description: spell.description, + icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`, + } + } +} diff --git a/server-new/app/helpers.ts b/server-new/app/helpers.ts index 8246741..9fa9548 100644 --- a/server-new/app/helpers.ts +++ b/server-new/app/helpers.ts @@ -1,3 +1,5 @@ +import { ParticipantBasic } from './Models/Match' + /** * League of Legends queues with defined role for each summoner */ @@ -40,7 +42,7 @@ export function getSeasonNumber (timestamp: number) { * @param a first role * @param b second role */ -export function sortTeamByRole (a:any, b:any) { +export function sortTeamByRole (a:ParticipantBasic, b:ParticipantBasic) { const sortingArr = ['TOP', 'JUNGLE', 'MIDDLE', 'BOTTOM', 'SUPPORT'] return sortingArr.indexOf(a.role) - sortingArr.indexOf(b.role) } From 2dbf4ba73a73ae0136ad6fa73bdbbea0bc125454 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Wed, 7 Oct 2020 22:03:36 +0200 Subject: [PATCH 18/33] feat: add summoners/live endpoint --- .../Controllers/Http/SummonersController.ts | 27 +++++++++- .../Jax/src/Endpoints/SpectatorEndpoint.ts | 54 +++++++++++++++++++ .../app/Validators/SummonerLiveValidator.ts | 53 ++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 server-new/app/Validators/SummonerLiveValidator.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index 5e16950..ee07cf7 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -4,8 +4,10 @@ import MatchRepository from 'App/Repositories/MatchRepository' import Jax from 'App/Services/Jax' import MatchService from 'App/Services/MatchService' 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 SummonerRecordValidator from 'App/Validators/SummonerRecordValidator' export default class SummonersController { @@ -76,7 +78,7 @@ export default class SummonersController { * POST: get champions view summoner data * @param ctx */ - public async champions ({ request, response }) { + 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) @@ -88,11 +90,32 @@ export default class SummonersController { * POST: get records view summoner data * @param ctx */ - public async records ({ request, response }) { + 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) + } } diff --git a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts index f187e70..2f98114 100644 --- a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts @@ -1,7 +1,61 @@ import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +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 diff --git a/server-new/app/Validators/SummonerLiveValidator.ts b/server-new/app/Validators/SummonerLiveValidator.ts new file mode 100644 index 0000000..1a4e215 --- /dev/null +++ b/server-new/app/Validators/SummonerLiveValidator.ts @@ -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 = {} +} From 58ade06bc08b7d04b1c03b803e14bae7253c7090 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Thu, 8 Oct 2020 09:51:12 +0200 Subject: [PATCH 19/33] feat: add summoner/overview endpoint --- .../Controllers/Http/SummonersController.ts | 41 ++++++- server-new/app/Models/Match.ts | 1 + .../app/Repositories/MatchRepository.ts | 112 ++++++++++++++++++ server-new/app/Services/MatchService.ts | 92 +++++++------- server-new/app/Services/StatsService.ts | 48 ++++++++ .../app/Transformers/BasicMatchTransformer.ts | 28 ++--- .../Validators/SummonerOverviewValidator.ts | 55 +++++++++ 7 files changed, 320 insertions(+), 57 deletions(-) create mode 100644 server-new/app/Services/StatsService.ts create mode 100644 server-new/app/Validators/SummonerOverviewValidator.ts diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index ee07cf7..a094ea6 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -3,11 +3,13 @@ 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 { @@ -15,7 +17,7 @@ export default class SummonersController { * Get all played seasons for a summoner * @param puuid of the summoner */ - private async getSeasons (puuid: string) { + private async getSeasons (puuid: string): Promise { const seasons = await MatchRepository.seasons(puuid) return seasons.length ? seasons.map(s => s._id) : [10] } @@ -74,6 +76,43 @@ export default class SummonersController { 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') + console.log(finalJSON) + return response.json(finalJSON) + } + /** * POST: get champions view summoner data * @param ctx diff --git a/server-new/app/Models/Match.ts b/server-new/app/Models/Match.ts index a899e8d..85c2525 100644 --- a/server-new/app/Models/Match.ts +++ b/server-new/app/Models/Match.ts @@ -13,6 +13,7 @@ export interface MatchModel extends ParticipantDetails { region: string, season: number, time: number, + newMatch?: boolean, } export interface ParticipantDetails { diff --git a/server-new/app/Repositories/MatchRepository.ts b/server-new/app/Repositories/MatchRepository.ts index 41bece3..31e2a72 100644 --- a/server-new/app/Repositories/MatchRepository.ts +++ b/server-new/app/Repositories/MatchRepository.ts @@ -77,6 +77,36 @@ class MatchRepository { } } + /** + * Get Summoner's statistics for the N most played champions + * @param puuid of the summoner + * @param limit number of champions to fetch + * @param season + */ + public async championStats (puuid: string, limit = 5, season?: number) { + const groupParams = { + champion: { $first: '$champion' }, + kills: { $sum: '$stats.kills' }, + deaths: { $sum: '$stats.deaths' }, + assists: { $sum: '$stats.assists' }, + } + const finalSteps = [ + { $sort: { 'count': -1, 'champion.name': 1 } }, + { $limit: limit }, + ] + return this.aggregate(puuid, {}, [], '$champion.id', groupParams, finalSteps, season) + } + + /** + * Get Summoner's statistics for all played champion classes + * @param puuid of the summoner + * @param season + */ + public async championClassStats (puuid: string, season?: number) { + const groupId = { '$arrayElemAt': ['$champion.roles', 0] } + return this.aggregate(puuid, {}, [], groupId, {}, [], season) + } + /** * Get Summoner's complete statistics for the all played champs * @param puuid of the summoner @@ -105,6 +135,33 @@ class MatchRepository { return this.aggregate(puuid, matchParams, [], '$champion.id', groupParams, finalSteps, season) } + /** + * Get Summoner's statistics for all played modes + * @param puuid of the summoner + * @param season + */ + public async gamemodeStats (puuid: string, season?: number) { + return this.aggregate(puuid, {}, [], '$gamemode', {}, [], season) + } + + /** + * Get global Summoner's statistics + * @param puuid of the summoner + * @param season + */ + public async globalStats (puuid: string, season?: number) { + const groupParams = { + time: { $sum: '$time' }, + kills: { $sum: '$stats.kills' }, + deaths: { $sum: '$stats.deaths' }, + assists: { $sum: '$stats.assists' }, + minions: { $sum: '$stats.minions' }, + vision: { $sum: '$stats.vision' }, + kp: { $avg: '$stats.kp' }, + } + return this.aggregate(puuid, {}, [], null, groupParams, [], season) + } + /** * Get Summoner's all records * @param puuid of the summoner @@ -205,6 +262,28 @@ class MatchRepository { return records[0] } + /** + * Get Summoner's statistics for the 5 differnt roles + * @param puuid of the summoner + * @param season + */ + public async roleStats (puuid: string, season?: number) { + const matchParams = { + role: { $not: { $eq: 'NONE' } }, + } + const finalSteps = [ + { + $project: { + role: '$_id', + count: '$count', + wins: '$wins', + losses: '$losses', + }, + }, + ] + return this.aggregate(puuid, matchParams, [], '$role', {}, finalSteps, season) + } + /** * Get Summoner's played seasons * @param puuid of the summoner @@ -221,6 +300,39 @@ class MatchRepository { }, ]).toArray() } + + /** + * Get Summoner's mates list + * @param puuid of the summoner + * @param season + */ + public async mates (puuid: string, season?: number) { + const intermediateSteps = [ + { $sort: { 'gameId': -1 } }, + { $unwind: '$allyTeam' }, + ] + const groupParams = { + account_id: { $first: '$account_id' }, + name: { $first: '$allyTeam.name' }, + mateId: { $first: '$allyTeam.account_id' }, + } + const finalSteps = [ + { + '$addFields': { + 'idEq': { '$eq': ['$mateId', '$account_id'] }, + }, + }, + { + $match: { + 'idEq': false, + 'count': { $gte: 2 }, + }, + }, + { $sort: { 'count': -1, 'name': 1 } }, + { $limit: 15 }, + ] + return this.aggregate(puuid, {}, intermediateSteps, '$allyTeam.account_id', groupParams, finalSteps, season) + } } export default new MatchRepository() diff --git a/server-new/app/Services/MatchService.ts b/server-new/app/Services/MatchService.ts index 2b1aa8f..a4dc3fb 100644 --- a/server-new/app/Services/MatchService.ts +++ b/server-new/app/Services/MatchService.ts @@ -1,9 +1,12 @@ import Jax from './Jax' -// import Logger from '@ioc:Adonis/Core/Logger' +import Logger from '@ioc:Adonis/Core/Logger' import { getSeasonNumber } from 'App/helpers' import { MatchReferenceDto } from './Jax/src/Endpoints/MatchListEndpoint' import { SummonerDTO } from './Jax/src/Endpoints/SummonerEndpoint' 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 { /** @@ -84,53 +87,62 @@ class MatchService { /** * Fetch list of matches for a specific Summoner - * @param account of the summoner - * @param gameIds of the matches to fetch - * @param summonerDB summoner in the database + * @param puuid + * @param accountId + * @param region + * @param gameIds + * @param summonerDB */ - // public async getMatches (account, gameIds, summonerDB) { - // console.time('getMatches') + public async getMatches (puuid: string, accountId: string, region: string, gameIds: number[], summonerDB: SummonerModel) { + console.time('getMatches') - // let matchesDetails = [] - // const matchesToGetFromRiot = [] - // for (let i = 0; i < gameIds.length; ++i) { - // const matchSaved = await summonerDB.matches().where({ gameId: gameIds[i] }).first() - // if (matchSaved) { - // matchesDetails.push(matchSaved) - // } else { - // matchesToGetFromRiot.push(gameIds[i]) - // } - // } + let matchesDetails: MatchModel[] = [] + 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) { + const matchSaved = await matchesCollection.findOne({ + summoner_puuid: puuid, + gameId: gameIds[i], + }) + if (matchSaved) { + console.log('match saved') + console.log(matchSaved) + matchesDetails.push(matchSaved) + } else { + matchesToGetFromRiot.push(gameIds[i]) + } + } - // const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, account.region)) - // let matchesFromApi = await Promise.all(requests) + const requests = matchesToGetFromRiot.map(gameId => Jax.Match.get(gameId, region)) + let matchesFromApi = await Promise.all(requests) - // /* If we have to store some matches in the db */ - // if (matchesFromApi.length !== 0) { - // // Try to see why matches are sometimes undefined - // matchesFromApi.filter(m => { - // if (m === undefined) { - // Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m) - // } - // }) + /* If we have to store some matches in the db */ + if (matchesFromApi.length !== 0) { + // Try to see why matches are sometimes undefined + matchesFromApi.filter(m => { + if (m === undefined) { + Logger.info(`Match undefined, summoner: ${summonerDB.puuid}`, m) + } + }) - // // Transform raw matches data - // await BasicMatchTransformer.transform(matchesFromApi, { account }) + // Transform raw matches data + const transformedMatches = await BasicMatchTransformer.transform(matchesFromApi, { puuid, accountId }) - // /* Save all matches from Riot Api in db */ - // for (const match of matchesFromApi) { - // await summonerDB.matches().create(match) - // match.newMatch = true - // } - // matchesDetails = [...matchesDetails, ...matchesFromApi] - // } + /* Save all matches from Riot Api in db */ + for (const match of transformedMatches) { + await Match.create(match) + match.newMatch = true + } + matchesDetails = [...matchesDetails, ...transformedMatches] + } - // /* Sort matches */ - // matchesDetails.sort((a, b) => (a.date < b.date) ? 1 : -1) - // console.timeEnd('getMatches') + /* Sort matches */ + matchesDetails.sort((a, b) => (a.date < b.date) ? 1 : -1) + console.timeEnd('getMatches') - // return matchesDetails - // } + return matchesDetails + } } export default new MatchService() diff --git a/server-new/app/Services/StatsService.ts b/server-new/app/Services/StatsService.ts new file mode 100644 index 0000000..c22b5f1 --- /dev/null +++ b/server-new/app/Services/StatsService.ts @@ -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() diff --git a/server-new/app/Transformers/BasicMatchTransformer.ts b/server-new/app/Transformers/BasicMatchTransformer.ts index 74e2d33..59d07b0 100644 --- a/server-new/app/Transformers/BasicMatchTransformer.ts +++ b/server-new/app/Transformers/BasicMatchTransformer.ts @@ -1,19 +1,19 @@ import { MatchModel, ParticipantBasic } from 'App/Models/Match' import { MatchDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint' -import { SummonerDTO } from 'App/Services/Jax/src/Endpoints/SummonerEndpoint' import MatchTransformer from 'App/Transformers/MatchTransformer' class BasicMatchTransformer extends MatchTransformer { /** * Transform raw data for 1 match * @param match - * @param account + * @param puuid + * @param accountId */ - private transformOneMatch (match: MatchDto, account: SummonerDTO) { + private transformOneMatch (match: MatchDto, puuid: string, accountId: string): MatchModel { // Global data about the match const globalInfos = super.getGameInfos(match) - const identity = match.participantIdentities.find((p) => p.player.currentAccountId === account.accountId) + const identity = match.participantIdentities.find((p) => p.player.currentAccountId === accountId) const player = match.participants[identity!.participantId - 1] let win = match.teams.find((t) => t.teamId === player.teamId)!.win @@ -50,7 +50,7 @@ class BasicMatchTransformer extends MatchTransformer { return { account_id: identity!.player.currentAccountId, - summoner_puuid: account.puuid, + summoner_puuid: puuid, gameId: match.gameId, result: win, allyTeam, @@ -62,21 +62,17 @@ class BasicMatchTransformer extends MatchTransformer { /** * Transform raw data from Riot API - * @param matches data from Riot API, Array of match or a single match + * @param matches data from Riot API, Array of matches * @param ctx context */ - public async transform (matches: MatchDto[] | MatchDto, { account }: { account: SummonerDTO }) { + public async transform (matches: MatchDto[], { puuid, accountId }: { puuid: string, accountId: string }) { await super.getContext() - if (Array.isArray(matches)) { - const finalMatches:MatchModel[] = [] - matches.forEach((match, index) => { - finalMatches[index] = this.transformOneMatch(match, account) - }) - return finalMatches - } else { - return this.transformOneMatch(matches, account) as MatchModel - } + const finalMatches:MatchModel[] = [] + matches.forEach((match, index) => { + finalMatches[index] = this.transformOneMatch(match, puuid, accountId) + }) + return finalMatches } } diff --git a/server-new/app/Validators/SummonerOverviewValidator.ts b/server-new/app/Validators/SummonerOverviewValidator.ts new file mode 100644 index 0000000..8320125 --- /dev/null +++ b/server-new/app/Validators/SummonerOverviewValidator.ts @@ -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 = {} +} From f00f13b0a4764a84a9b19fbacad77ca772d6f935 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sat, 10 Oct 2020 22:05:09 +0200 Subject: [PATCH 20/33] fix(transformers): remove new line from champion icon url --- server-new/app/Transformers/MatchTransformer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/server-new/app/Transformers/MatchTransformer.ts b/server-new/app/Transformers/MatchTransformer.ts index 83e0966..17b3dbc 100644 --- a/server-new/app/Transformers/MatchTransformer.ts +++ b/server-new/app/Transformers/MatchTransformer.ts @@ -43,11 +43,9 @@ export default abstract class MatchTransformer { * @param id of the champion */ public getChampion (id: number) { - const champion = { ...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 champion = { ...this.champions.find((c: any) => c.id === id) } + champion.icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/' + + `${champion.squarePortraitPath.split('/assets/')[1].toLowerCase()}` delete champion.squarePortraitPath return champion } From a6988533e86079c5f5fc718f7b1201e3939c9a6f Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sat, 10 Oct 2020 22:06:12 +0200 Subject: [PATCH 21/33] refactor(front): update summoner/overview payload --- client/src/plugins/axios.js | 2 +- client/src/store/modules/summoner.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/plugins/axios.js b/client/src/plugins/axios.js index 8411999..cdc79c0 100644 --- a/client/src/plugins/axios.js +++ b/client/src/plugins/axios.js @@ -6,7 +6,7 @@ export const axios = axiosHttp axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' 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 axiosSource = CancelToken.source() diff --git a/client/src/store/modules/summoner.js b/client/src/store/modules/summoner.js index c30233b..e001c06 100644 --- a/client/src/store/modules/summoner.js +++ b/client/src/store/modules/summoner.js @@ -179,8 +179,16 @@ export const actions = { const newMatches = createMatchData(resp.data.matches) commit('MATCHES_FOUND', { newMatches, stats: resp.data.stats }) }, - async overviewRequest({ commit }) { - const resp = await axios(({ url: 'summoner/overview', data: { account: state.basic.account }, method: 'POST' })).catch(() => { }) + async overviewRequest({ commit, rootState }) { + 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(resp.data) resp.data.matches = createMatchData(resp.data.matchesDetails) From 3550ec283229b38b8cc96925734fc84c14d9aceb Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sat, 10 Oct 2020 22:39:13 +0200 Subject: [PATCH 22/33] refactor: use old riot rate limiter for now --- .../Controllers/Http/SummonersController.ts | 1 - .../Jax/src/Endpoints/LeagueEndpoint.ts | 3 +- .../Jax/src/Endpoints/MatchEndpoint.ts | 3 +- .../Jax/src/Endpoints/MatchListEndpoint.ts | 3 +- .../Jax/src/Endpoints/SpectatorEndpoint.ts | 3 +- .../Jax/src/Endpoints/SummonerEndpoint.ts | 3 +- server-new/app/Services/Jax/src/Jax.ts | 11 +- server-new/app/Services/Jax/src/JaxRequest.ts | 37 +- server-new/app/Services/MatchService.ts | 8 +- server-new/package-lock.json | 532 +++++++++++++++++- server-new/package.json | 2 + 11 files changed, 567 insertions(+), 39 deletions(-) diff --git a/server-new/app/Controllers/Http/SummonersController.ts b/server-new/app/Controllers/Http/SummonersController.ts index a094ea6..4e87f66 100644 --- a/server-new/app/Controllers/Http/SummonersController.ts +++ b/server-new/app/Controllers/Http/SummonersController.ts @@ -109,7 +109,6 @@ export default class SummonersController { await summonerDB.save() console.timeEnd('overview') - console.log(finalJSON) return response.json(finalJSON) } diff --git a/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts index 5a71bb6..1eb2d83 100644 --- a/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/LeagueEndpoint.ts @@ -1,4 +1,5 @@ -import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' import { JaxConfig } from '../../JaxConfig' import JaxRequest from '../JaxRequest' diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts index c3676b8..586fe73 100644 --- a/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/MatchEndpoint.ts @@ -1,4 +1,5 @@ -import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' import { JaxConfig } from '../../JaxConfig' import JaxRequest from '../JaxRequest' diff --git a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts index 9cc978c..19aacb6 100644 --- a/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/MatchListEndpoint.ts @@ -1,4 +1,5 @@ -import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' import { JaxConfig } from '../../JaxConfig' import JaxRequest from '../JaxRequest' diff --git a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts index 2f98114..78d7f36 100644 --- a/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/SpectatorEndpoint.ts @@ -1,4 +1,5 @@ -import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +// 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' diff --git a/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts index e10655f..6c24120 100644 --- a/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/SummonerEndpoint.ts @@ -1,4 +1,5 @@ -import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' import { JaxConfig } from '../../JaxConfig' import JaxRequest from '../JaxRequest' diff --git a/server-new/app/Services/Jax/src/Jax.ts b/server-new/app/Services/Jax/src/Jax.ts index 7ec3f9a..ff901a1 100644 --- a/server-new/app/Services/Jax/src/Jax.ts +++ b/server-new/app/Services/Jax/src/Jax.ts @@ -5,7 +5,9 @@ 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 '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' +import { STRATEGY } from 'riot-ratelimiter/dist/RateLimiter' export default class Jax { public key: string @@ -20,8 +22,13 @@ export default class Jax { constructor (config:JaxConfig) { this.key = config.key + // this.limiter = new RiotRateLimiter({ + // debug: true, + // retryCount: 0, + // }) this.limiter = new RiotRateLimiter({ - debug: true, + debug: false, + strategy: STRATEGY.SPREAD, }) this.config = config diff --git a/server-new/app/Services/Jax/src/JaxRequest.ts b/server-new/app/Services/Jax/src/JaxRequest.ts index 1f53f71..1fb5614 100644 --- a/server-new/app/Services/Jax/src/JaxRequest.ts +++ b/server-new/app/Services/Jax/src/JaxRequest.ts @@ -2,7 +2,8 @@ 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 '@fightmegg/riot-rate-limiter' +import RiotRateLimiter from 'riot-ratelimiter' +// import { RiotRateLimiter } from '@fightmegg/riot-rate-limiter' export default class JaxRequest { private region: string @@ -36,35 +37,43 @@ export default class JaxRequest { } try { - const resp = await this.limiter.execute({ + // const resp = await this.limiter.execute({ + // url, + // options: { + // headers: { + // 'X-Riot-Token': this.config.key, + // }, + // }, + // }) + + const resp:any = await this.limiter.executing({ url, - options: { - headers: { - 'X-Riot-Token': this.config.key, - }, - }, + token: this.config.key, + resolveWithFullResponse: false, }) if (this.cacheTime > 0) { - await Redis.setex(url, this.cacheTime, JSON.stringify(resp)) + await Redis.setex(url, this.cacheTime, resp) } - return resp - } catch ({ statusCode, ...rest }) { + return JSON.parse(resp) + } catch ({ status, ...rest }) { this.retries-- - if (statusCode !== 500 && statusCode !== 503 && statusCode !== 504) { + 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('summoner/v4/summoners/by-name') && + !this.endpoint.includes('match/v4/matchlists/by-account') ) { - Logger.error(`JaxRequest Error ${statusCode} : `, rest) + Logger.error(`JaxRequest Error ${status}: `, rest) } return } console.log('====================================') - console.log(statusCode) + console.log(status) console.log('====================================') if (this.retries > 0) { diff --git a/server-new/app/Services/MatchService.ts b/server-new/app/Services/MatchService.ts index a4dc3fb..d9bae00 100644 --- a/server-new/app/Services/MatchService.ts +++ b/server-new/app/Services/MatchService.ts @@ -19,7 +19,7 @@ class MatchService { let alreadyIn = false let index = 0 do { - let { matches: newMatchList } = await Jax.Matchlist.accountID(account.accountId, account.region as string, index) + let newMatchList = await Jax.Matchlist.accountID(account.accountId, account.region as string, index) // Error while fetching Riot API if (!newMatchList) { matchList = matchList.map(m => { @@ -28,8 +28,8 @@ class MatchService { }) return matchList } - matchList = [...matchList, ...newMatchList] - alreadyIn = newMatchList.length === 0 || stopFetching(newMatchList) + matchList = [...matchList, ...newMatchList.matches] + alreadyIn = newMatchList.matches.length === 0 || stopFetching(newMatchList.matches) // If the match is made in another region : we stop fetching if (matchList[matchList.length - 1].platformId.toLowerCase() !== account.region) { alreadyIn = true @@ -106,8 +106,6 @@ class MatchService { gameId: gameIds[i], }) if (matchSaved) { - console.log('match saved') - console.log(matchSaved) matchesDetails.push(matchSaved) } else { matchesToGetFromRiot.push(gameIds[i]) diff --git a/server-new/package-lock.json b/server-new/package-lock.json index cab44e7..a040744 100644 --- a/server-new/package-lock.json +++ b/server-new/package-lock.json @@ -767,7 +767,6 @@ "version": "6.12.5", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -938,6 +937,19 @@ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -950,6 +962,11 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -966,6 +983,16 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -1080,6 +1107,14 @@ } } }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", @@ -1133,6 +1168,11 @@ } } }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, "bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -1267,6 +1307,11 @@ "redeyed": "~2.1.0" } }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -1403,6 +1448,14 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -1523,6 +1576,14 @@ "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==" }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -1616,6 +1677,11 @@ } } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", @@ -1665,6 +1731,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -1721,6 +1796,14 @@ "ansi-colors": "^4.1.1" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1949,8 +2032,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -2038,11 +2120,15 @@ } } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "2.2.7", @@ -2084,8 +2170,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -2183,6 +2268,21 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -2262,6 +2362,14 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -2355,6 +2463,20 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2466,6 +2588,11 @@ "resolved": "https://registry.npmjs.org/haye/-/haye-2.0.2.tgz", "integrity": "sha512-C+jeFipAuwLLmQziwQrXuHzUIihDzqoLpCpwDWYFQVCIyqi5ZvC+4YtzawPTmd1tIKo0ULf+4P0Mw8irUdXIpg==" }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -2483,6 +2610,16 @@ "toidentifier": "1.0.0" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "http2-wrapper": { "version": "1.0.0-beta.5.2", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", @@ -2608,6 +2745,11 @@ } } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2752,6 +2894,11 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "jest-worker": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", @@ -2790,16 +2937,25 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -2807,6 +2963,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, "jsonfile": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", @@ -2816,6 +2977,17 @@ "universalify": "^1.0.0" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -2868,6 +3040,29 @@ "integrity": "sha512-083swF7iH7bx8666zdzBColpgEuy46HjN3r1isD4zV6Ix7FuHfb/2/WVnl4CH8hjuoWeFF7P5KkKNXUnJCFEJg==", "dev": true }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3377,6 +3572,24 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3397,6 +3610,11 @@ "path-key": "^3.0.0" } }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3619,6 +3837,14 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3654,6 +3880,11 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, "path-to-regexp": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", @@ -3681,6 +3912,11 @@ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-3.1.0.tgz", "integrity": "sha512-KGuODSTV6hcgdZvDrIDBUkN0utcAVj1LL7FfGbM0viKTtCHmtZcuEJ+lGqsp0fTFkGqesdtemV2yUSMeyy3ddA==" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -3782,6 +4018,11 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -3794,8 +4035,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.9.4", @@ -3833,6 +4073,85 @@ "unpipe": "1.0.0" } }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "dependencies": { + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -3927,6 +4246,59 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "request-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + } + }, "require-all": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", @@ -3953,6 +4325,14 @@ } } }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-alpn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", @@ -3992,6 +4372,40 @@ "glob": "^7.1.3" } }, + "riot-ratelimiter": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/riot-ratelimiter/-/riot-ratelimiter-0.1.5.tgz", + "integrity": "sha512-tzqPCDWzHHrUv7bh0/OHCNVTu45GoJqGEaawbQy0NHgs8K18SXXI9qAFYw8UgQvCdhcwfWFUKlUJ1KMSRK0M+g==", + "requires": { + "bluebird": "~3.5.0", + "request-promise": "~4.2.1", + "runtime-engine-check": "^1.0.0", + "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "runtime-engine-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/runtime-engine-check/-/runtime-engine-check-1.0.0.tgz", + "integrity": "sha1-hf2+8ssk1VED6w6/izJe64DlXUo=", + "requires": { + "read-pkg-up": "^2.0.0", + "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4371,6 +4785,34 @@ "memory-pager": "^1.0.2" } }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" + }, "split-lines": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/split-lines/-/split-lines-2.0.0.tgz", @@ -4401,6 +4843,22 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -4438,6 +4896,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -4624,6 +5087,15 @@ "ieee754": "^1.1.13" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -4639,6 +5111,19 @@ "tslib": "^1.8.1" } }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4758,7 +5243,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -4780,12 +5264,26 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "v8-compile-cache": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "validate-npm-package-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", @@ -4805,6 +5303,16 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "webpack-merge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", diff --git a/server-new/package.json b/server-new/package.json index 85fd56c..cf6face 100644 --- a/server-new/package.json +++ b/server-new/package.json @@ -28,6 +28,8 @@ "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" } } From b0456158623947cfed4fca7951cc746aca139cbc Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sat, 10 Oct 2020 22:41:30 +0200 Subject: [PATCH 23/33] refactor(front): update summoners/live payload --- client/src/store/modules/summoner.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/src/store/modules/summoner.js b/client/src/store/modules/summoner.js index e001c06..744a250 100644 --- a/client/src/store/modules/summoner.js +++ b/client/src/store/modules/summoner.js @@ -155,9 +155,14 @@ export const actions = { }, async liveMatchRequest({ commit, rootState }) { commit('LIVE_LOADING') - const region = rootState.regionsList[rootState.settings.region] - - const resp = await axios(({ url: 'summoner/live', data: { account: state.basic.account, region }, method: 'POST' })).catch(() => { }) + const resp = await axios(({ + url: 'summoner/live', + data: { + id: state.basic.account.id, + region: rootState.regionsList[rootState.settings.region] + }, + method: 'POST' + })).catch(() => { }) console.log('---LIVE---') console.log(resp.data) From 749e9eda05f0212fd1b685122dece6896747a071 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 11 Oct 2020 16:35:23 +0200 Subject: [PATCH 24/33] feat: more matches endpoint --- client/src/store/modules/summoner.js | 14 ++++- .../app/Controllers/Http/MatchesController.ts | 29 +++++++++- .../app/Validators/MatchesIndexValidator.ts | 57 +++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 server-new/app/Validators/MatchesIndexValidator.ts diff --git a/client/src/store/modules/summoner.js b/client/src/store/modules/summoner.js index 744a250..306d2f7 100644 --- a/client/src/store/modules/summoner.js +++ b/client/src/store/modules/summoner.js @@ -172,13 +172,21 @@ export const actions = { commit('SUMMONER_NOT_PLAYING') } }, - async moreMatches({ commit }) { + async moreMatches({ commit, rootState }) { 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 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(resp.data) const newMatches = createMatchData(resp.data.matches) diff --git a/server-new/app/Controllers/Http/MatchesController.ts b/server-new/app/Controllers/Http/MatchesController.ts index f6fa260..9a74267 100644 --- a/server-new/app/Controllers/Http/MatchesController.ts +++ b/server-new/app/Controllers/Http/MatchesController.ts @@ -1,4 +1,31 @@ -// import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +import Summoner from 'App/Models/Summoner' +import MatchService from 'App/Services/MatchService' +import StatsService from 'App/Services/StatsService' +import MatchesIndexValidator from 'App/Validators/MatchesIndexValidator' export default class MatchesController { + /** + * 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, + }) + } } diff --git a/server-new/app/Validators/MatchesIndexValidator.ts b/server-new/app/Validators/MatchesIndexValidator.ts new file mode 100644 index 0000000..26c8dc5 --- /dev/null +++ b/server-new/app/Validators/MatchesIndexValidator.ts @@ -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 = {} +} From 0fffc8127f00551ea0b69a52a55ce00f23911647 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 11 Oct 2020 17:31:16 +0200 Subject: [PATCH 25/33] feat: add match details endpoints --- .../app/Controllers/Http/MatchesController.ts | 81 ++++++++++++++ server-new/app/Models/DetailedMatch.ts | 10 +- server-new/app/Models/Match.ts | 20 ++-- .../Transformers/DetailedMatchTransformer.ts | 101 ++++++++++++++++++ .../app/Transformers/MatchTransformer.ts | 16 +-- .../app/Validators/DetailedMatchValidator.ts | 53 +++++++++ server-new/app/helpers.ts | 12 +-- 7 files changed, 266 insertions(+), 27 deletions(-) create mode 100644 server-new/app/Transformers/DetailedMatchTransformer.ts create mode 100644 server-new/app/Validators/DetailedMatchValidator.ts diff --git a/server-new/app/Controllers/Http/MatchesController.ts b/server-new/app/Controllers/Http/MatchesController.ts index 9a74267..2cc41e9 100644 --- a/server-new/app/Controllers/Http/MatchesController.ts +++ b/server-new/app/Controllers/Http/MatchesController.ts @@ -1,10 +1,34 @@ 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 @@ -28,4 +52,61 @@ export default class MatchesController { 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({ 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, + }) + } } diff --git a/server-new/app/Models/DetailedMatch.ts b/server-new/app/Models/DetailedMatch.ts index 9a7caa2..f4e9e3b 100644 --- a/server-new/app/Models/DetailedMatch.ts +++ b/server-new/app/Models/DetailedMatch.ts @@ -2,7 +2,7 @@ import { Model } from '@ioc:Mongodb/Model' import { Champion, ParticipantDetails } from 'App/Models/Match' export interface DetailedMatchModel { - gameId: string, + gameId: number, season: number, blueTeam: Team, redTeam: Team, @@ -26,10 +26,10 @@ interface Team { towers: number } -interface Ban { - championID: number, +export interface Ban { + championId: number, pickTurn: number, - champion: Champion + champion: Champion } interface TeamStats { @@ -45,7 +45,7 @@ interface TeamStats { export default class DetailedMatch extends Model implements DetailedMatchModel { public static collectionName = 'detailed_matches' - public gameId: string + public gameId: number public season: number public blueTeam: Team public redTeam: Team diff --git a/server-new/app/Models/Match.ts b/server-new/app/Models/Match.ts index 85c2525..489b2e8 100644 --- a/server-new/app/Models/Match.ts +++ b/server-new/app/Models/Match.ts @@ -25,19 +25,19 @@ export interface ParticipantDetails { secondaryRune: string | null, level: number, items: (Item | null)[], - firstSum: SummonerSpell | number, - secondSum: SummonerSpell | number, + firstSum: SummonerSpell | number | null, + secondSum: SummonerSpell | number | null, stats: Stats, percentStats?: PercentStats - rank?: Rank + rank?: Rank | null } -export interface Champion { - id: number, - name: string, - alias: string, - roles: string[], - icon: string +export interface Champion { + id: number | T, + name: string | U, + alias?: string, + roles?: string[], + icon?: string } export interface SummonerSpell { @@ -48,7 +48,7 @@ export interface SummonerSpell { export interface Rank { tier: string, - shortName: string + shortName: string | number } export interface ParticipantBasic { diff --git a/server-new/app/Transformers/DetailedMatchTransformer.ts b/server-new/app/Transformers/DetailedMatchTransformer.ts new file mode 100644 index 0000000..cbd085a --- /dev/null +++ b/server-new/app/Transformers/DetailedMatchTransformer.ts @@ -0,0 +1,101 @@ +import { Ban, DetailedMatchModel } from 'App/Models/DetailedMatch' +import { Champion } from 'App/Models/Match' +import { MatchDto, TeamStatsDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint' +import MatchTransformer from './MatchTransformer' + +/** + * DetailedMatchTransformer class + * + * @class DetailedMatchTransformer + */ +class DetailedMatchTransformer extends MatchTransformer { + /** + * Get all data of one team + * @param match raw match data from Riot API + * @param team raw team data from Riot API + */ + private getTeamData (match: MatchDto, team: TeamStatsDto) { + let win = team.win + if (match.gameDuration < 300) { + win = 'Remake' + } + + // Global stats of the team + const teamPlayers = match.participants.filter(p => p.teamId === team.teamId) + const teamStats = teamPlayers.reduce((prev, cur) => { + prev.kills += cur.stats.kills + prev.deaths += cur.stats.deaths + prev.assists += cur.stats.assists + prev.gold += cur.stats.goldEarned + prev.dmgChamp += cur.stats.totalDamageDealtToChampions + prev.dmgObj += cur.stats.damageDealtToObjectives + prev.dmgTaken += cur.stats.totalDamageTaken + return prev + }, { kills: 0, deaths: 0, assists: 0, gold: 0, dmgChamp: 0, dmgObj: 0, dmgTaken: 0 }) + + // Bans + const bans: Ban[] = [] + if (team.bans) { + for (const ban of team.bans) { + const champion: Champion = (ban.championId === -1) + ? { id: null, name: null } + : super.getChampion(ban.championId) + + bans.push({ + ...ban, + champion, + }) + } + } + + // Players + let players = teamPlayers + .map(p => super.getPlayerData(match, p, true, teamStats)) + .map(p => { + p.firstSum = super.getSummonerSpell(p.firstSum as number) + p.secondSum = super.getSummonerSpell(p.secondSum as number) + return p + }) + .sort(this.sortTeamByRole) + + return { + bans, + barons: team.baronKills, + color: team.teamId === 100 ? 'Blue' : 'Red', + dragons: team.dragonKills, + inhibitors: team.inhibitorKills, + players, + result: win, + riftHerald: team.riftHeraldKills, + teamStats, + towers: team.towerKills, + } + } + + /** + * Transform raw data from Riot API + * @param match data from Riot API + */ + public async transform (match: MatchDto): Promise { + await super.getContext() + + // Global data + const globalInfos = super.getGameInfos(match) + + // Teams + const firstTeam = this.getTeamData(match, match.teams[0]) + const secondTeam = this.getTeamData(match, match.teams[1]) + + // Roles + super.getMatchRoles(match, firstTeam.players, secondTeam.players) + + return { + gameId: match.gameId, + blueTeam: firstTeam.color === 'Blue' ? firstTeam : secondTeam, + redTeam: firstTeam.color === 'Blue' ? secondTeam : firstTeam, + ...globalInfos, + } + } +} + +export default new DetailedMatchTransformer() diff --git a/server-new/app/Transformers/MatchTransformer.ts b/server-new/app/Transformers/MatchTransformer.ts index 17b3dbc..c006cfc 100644 --- a/server-new/app/Transformers/MatchTransformer.ts +++ b/server-new/app/Transformers/MatchTransformer.ts @@ -1,7 +1,7 @@ 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 { Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats } from 'App/Models/Match' +import { Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats, SummonerSpell } from 'App/Models/Match' import RoleIdentificationService from 'App/Services/RoleIdentiticationService' export interface PlayerRole { @@ -17,7 +17,7 @@ export default abstract class MatchTransformer { protected perkstyles: any protected summonerSpells: any protected championRoles: any - protected sortTeamByRole: (a: ParticipantBasic, b: ParticipantBasic) => number + protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number /** * Get global Context with CDragon Data */ @@ -239,7 +239,11 @@ export default abstract class MatchTransformer { * @param champs 5 champions + smite from the team * @param playerData data of the searched player, only for basic matches */ - public updateTeamRoles (team: ParticipantBasic[], champs: PlayerRole[], playerData?: ParticipantDetails) { + public updateTeamRoles ( + team: ParticipantBasic[] | ParticipantDetails[], + champs: PlayerRole[], + playerData?: ParticipantDetails + ) { // const actualRoles = [...new Set(team.map(p => p.role))] // if (actualRoles.length === 5) { // return @@ -265,8 +269,8 @@ export default abstract class MatchTransformer { */ public getMatchRoles ( match: MatchDto, - allyTeam: ParticipantBasic[], - enemyTeam: ParticipantBasic[], + allyTeam: ParticipantBasic[] | ParticipantDetails[], + enemyTeam: ParticipantBasic[] | ParticipantDetails[], allyTeamId = 100, playerData?: ParticipantDetails ) { @@ -297,7 +301,7 @@ export default abstract class MatchTransformer { * Get Summoner Spell Data from CDragon * @param id of the summonerSpell */ - public getSummonerSpell (id: number) { + public getSummonerSpell (id: number): SummonerSpell | null { if (id === 0) { return null } diff --git a/server-new/app/Validators/DetailedMatchValidator.ts b/server-new/app/Validators/DetailedMatchValidator.ts new file mode 100644 index 0000000..98b7f69 --- /dev/null +++ b/server-new/app/Validators/DetailedMatchValidator.ts @@ -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 = {} +} diff --git a/server-new/app/helpers.ts b/server-new/app/helpers.ts index 9fa9548..378eaf7 100644 --- a/server-new/app/helpers.ts +++ b/server-new/app/helpers.ts @@ -1,4 +1,4 @@ -import { ParticipantBasic } from './Models/Match' +import { ParticipantBasic, ParticipantDetails } from './Models/Match' /** * League of Legends queues with defined role for each summoner @@ -29,7 +29,7 @@ export const supportItems = [3850, 3851, 3853, 3854, 3855, 3857, 3858, 3859, 386 * Get season number for a match * @param timestamp */ -export function getSeasonNumber (timestamp: number) { +export function getSeasonNumber (timestamp: number): number { const arrSeasons = Object.keys(seasons).map(k => Number(k)) arrSeasons.push(timestamp) arrSeasons.sort() @@ -38,11 +38,11 @@ export function getSeasonNumber (timestamp: number) { } /** - * Sort array of Roles according to a specific order - * @param a first role - * @param b second role + * Sort array of Players by roles according to a specific order + * @param a first player + * @param b second player */ -export function sortTeamByRole (a:ParticipantBasic, b:ParticipantBasic) { +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) } From 8769d9b5bc7a4f1c9b9a9c70174ac93f45ca18aa Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 11 Oct 2020 17:57:43 +0200 Subject: [PATCH 26/33] refactor: add types for all CDragon endpoints --- .../app/Services/Jax/src/CDragonRequest.ts | 1 + .../Jax/src/Endpoints/CDragonEndpoint.ts | 84 +++++++++++++++++-- .../app/Transformers/MatchTransformer.ts | 55 ++++++------ 3 files changed, 111 insertions(+), 29 deletions(-) diff --git a/server-new/app/Services/Jax/src/CDragonRequest.ts b/server-new/app/Services/Jax/src/CDragonRequest.ts index c60328a..753dc6a 100644 --- a/server-new/app/Services/Jax/src/CDragonRequest.ts +++ b/server-new/app/Services/Jax/src/CDragonRequest.ts @@ -24,6 +24,7 @@ export default 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/perkstyles.json // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json + // https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json public async execute () { const url = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/${this.endpoint}` diff --git a/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts b/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts index 87b5c00..12b4721 100644 --- a/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts +++ b/server-new/app/Services/Jax/src/Endpoints/CDragonEndpoint.ts @@ -1,6 +1,80 @@ 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 @@ -8,23 +82,23 @@ export default class CDragonEndpoint { this.config = config } - public champions () { + public async champions (): Promise { return new CDragonRequest(this.config, 'champion-summary.json', 36000).execute() } - public items () { + public async items (): Promise { return new CDragonRequest(this.config, 'items.json', 36000).execute() } - public perks () { + public async perks (): Promise { return new CDragonRequest(this.config, 'perks.json', 36000).execute() } - public perkstyles () { + public async perkstyles (): Promise { return new CDragonRequest(this.config, 'perkstyles.json', 36000).execute() } - public summonerSpells () { + public async summonerSpells (): Promise { return new CDragonRequest(this.config, 'summoner-spells.json', 36000).execute() } } diff --git a/server-new/app/Transformers/MatchTransformer.ts b/server-new/app/Transformers/MatchTransformer.ts index c006cfc..f70b054 100644 --- a/server-new/app/Transformers/MatchTransformer.ts +++ b/server-new/app/Transformers/MatchTransformer.ts @@ -1,8 +1,9 @@ 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 { Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats, SummonerSpell } from 'App/Models/Match' +import { Champion, Item, ParticipantBasic, ParticipantDetails, PercentStats, Stats, SummonerSpell } from 'App/Models/Match' import RoleIdentificationService from 'App/Services/RoleIdentiticationService' +import { ChampionDTO, ItemDTO, PerkDTO, PerkStyleDTO, SummonerSpellDTO } from 'App/Services/Jax/src/Endpoints/CDragonEndpoint' export interface PlayerRole { champion: number, @@ -11,11 +12,11 @@ export interface PlayerRole { } export default abstract class MatchTransformer { - protected champions: any - protected items: any - protected perks: any - protected perkstyles: any - protected summonerSpells: any + protected champions: ChampionDTO[] + protected items: ItemDTO[] + protected perks: PerkDTO[] + protected perkstyles: PerkStyleDTO[] + protected summonerSpells: SummonerSpellDTO[] protected championRoles: any protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number /** @@ -42,12 +43,18 @@ export default abstract class MatchTransformer { * Get champion specific data * @param id of the champion */ - public getChampion (id: number) { - const champion = { ...this.champions.find((c: any) => c.id === id) } - champion.icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/' - + `${champion.squarePortraitPath.split('/assets/')[1].toLowerCase()}` - delete champion.squarePortraitPath - return champion + public getChampion (id: number): Champion { + const originalChampionData = this.champions.find(c => c.id === id) + const icon = 'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/' + + originalChampionData!.squarePortraitPath.split('/assets/')[1].toLowerCase() + + return { + icon, + id: originalChampionData!.id, + name: originalChampionData!.name, + alias: originalChampionData!.alias, + roles: originalChampionData!.roles, + } } /** @@ -149,14 +156,14 @@ export default abstract class MatchTransformer { continue } - const item = this.items.find((i: any) => i.id === id) - const itemUrl = item.iconPath.split('/assets/')[1].toLowerCase() + const item = this.items.find(i => i.id === id) + const itemUrl = item!.iconPath.split('/assets/')[1].toLowerCase() items.push({ image: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${itemUrl}`, - name: item.name, - description: item.description, - price: item.priceTotal, + name: item!.name, + description: item!.description, + price: item!.priceTotal, }) } @@ -189,11 +196,11 @@ export default abstract class MatchTransformer { * @param perkSubStyle secondary perks category */ public getPerksImages (perk0: number, perkSubStyle: number) { - const firstRune = this.perks.find((p: any) => p.id === perk0) - const firstRuneUrl = firstRune.iconPath.split('/assets/')[1].toLowerCase() + const firstRune = this.perks.find(p => p.id === perk0) + const firstRuneUrl = firstRune!.iconPath.split('/assets/')[1].toLowerCase() const primaryRune = `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${firstRuneUrl}` - const secondRuneStyle = this.perkstyles.find((p: any) => p.id === perkSubStyle) + const secondRuneStyle = this.perkstyles.find(p => p.id === perkSubStyle) const secondRuneStyleUrl = secondRuneStyle ? secondRuneStyle.iconPath.split('/assets/')[1].toLowerCase() : null const secondaryRune = secondRuneStyleUrl ? @@ -305,11 +312,11 @@ export default abstract class MatchTransformer { if (id === 0) { return null } - const spell = this.summonerSpells.find((s: any) => s.id === id) - const spellName = spell.iconPath.split('/assets/')[1].toLowerCase() + const spell = this.summonerSpells.find(s => s.id === id) + const spellName = spell!.iconPath.split('/assets/')[1].toLowerCase() return { - name: spell.name, - description: spell.description, + name: spell!.name, + description: spell!.description, icon: `https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/${spellName}`, } } From 02360b4e9276b904db4cd909ce1a8471269349cb Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 11 Oct 2020 18:13:14 +0200 Subject: [PATCH 27/33] refactor(role-identification): add types to ChampionPlayRate --- server-new/app/Models/DetailedMatch.ts | 4 +-- .../app/Services/RoleIdentiticationService.ts | 32 ++++++++++++------- .../app/Transformers/BasicMatchTransformer.ts | 6 ++-- .../Transformers/DetailedMatchTransformer.ts | 5 +-- .../app/Transformers/MatchTransformer.ts | 10 +++--- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/server-new/app/Models/DetailedMatch.ts b/server-new/app/Models/DetailedMatch.ts index f4e9e3b..f591061 100644 --- a/server-new/app/Models/DetailedMatch.ts +++ b/server-new/app/Models/DetailedMatch.ts @@ -29,10 +29,10 @@ interface Team { export interface Ban { championId: number, pickTurn: number, - champion: Champion + champion: Champion } -interface TeamStats { +export interface TeamStats { kills: number, deaths: number, assists: number, diff --git a/server-new/app/Services/RoleIdentiticationService.ts b/server-new/app/Services/RoleIdentiticationService.ts index f2a2ae2..c350db6 100644 --- a/server-new/app/Services/RoleIdentiticationService.ts +++ b/server-new/app/Services/RoleIdentiticationService.ts @@ -1,20 +1,30 @@ import Redis from '@ioc:Adonis/Addons/Redis' import got from 'got/dist/source' +export interface ChampionsPlayRate { + [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, + TOP?: number, + JUNGLE?: number, + MIDDLE?: number, + BOTTOM?: number, + SUPPORT?: number, } export interface RoleComposition { - 'TOP'?: number, - 'JUNGLE'?: number, - 'MIDDLE'?: number, - 'BOTTOM'?: number, - 'UTILITY'?: number, + TOP?: number, + JUNGLE?: number, + MIDDLE?: number, + BOTTOM?: number, + UTILITY?: number, } export interface ChampionComposition { @@ -153,7 +163,7 @@ class RoleIdentificationService { /** * Get the CDN data of the champion playrates by role */ - public async pullData () { + public async pullData (): Promise { const url = 'http://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json' // Check if cached diff --git a/server-new/app/Transformers/BasicMatchTransformer.ts b/server-new/app/Transformers/BasicMatchTransformer.ts index 59d07b0..b2a376d 100644 --- a/server-new/app/Transformers/BasicMatchTransformer.ts +++ b/server-new/app/Transformers/BasicMatchTransformer.ts @@ -27,8 +27,8 @@ class BasicMatchTransformer extends MatchTransformer { const playerData = super.getPlayerData(match, player, false) // Teams data - const allyTeam:ParticipantBasic[] = [] - const enemyTeam:ParticipantBasic[] = [] + const allyTeam: ParticipantBasic[] = [] + const enemyTeam: ParticipantBasic[] = [] for (let summoner of match.participantIdentities) { const allData = match.participants[summoner.participantId - 1] const playerInfos = { @@ -68,7 +68,7 @@ class BasicMatchTransformer extends MatchTransformer { public async transform (matches: MatchDto[], { puuid, accountId }: { puuid: string, accountId: string }) { await super.getContext() - const finalMatches:MatchModel[] = [] + const finalMatches: MatchModel[] = [] matches.forEach((match, index) => { finalMatches[index] = this.transformOneMatch(match, puuid, accountId) }) diff --git a/server-new/app/Transformers/DetailedMatchTransformer.ts b/server-new/app/Transformers/DetailedMatchTransformer.ts index cbd085a..ad73be0 100644 --- a/server-new/app/Transformers/DetailedMatchTransformer.ts +++ b/server-new/app/Transformers/DetailedMatchTransformer.ts @@ -1,5 +1,4 @@ import { Ban, DetailedMatchModel } from 'App/Models/DetailedMatch' -import { Champion } from 'App/Models/Match' import { MatchDto, TeamStatsDto } from 'App/Services/Jax/src/Endpoints/MatchEndpoint' import MatchTransformer from './MatchTransformer' @@ -37,9 +36,7 @@ class DetailedMatchTransformer extends MatchTransformer { const bans: Ban[] = [] if (team.bans) { for (const ban of team.bans) { - const champion: Champion = (ban.championId === -1) - ? { id: null, name: null } - : super.getChampion(ban.championId) + const champion = (ban.championId === -1) ? { id: null, name: null } : super.getChampion(ban.championId) bans.push({ ...ban, diff --git a/server-new/app/Transformers/MatchTransformer.ts b/server-new/app/Transformers/MatchTransformer.ts index f70b054..ed1bd74 100644 --- a/server-new/app/Transformers/MatchTransformer.ts +++ b/server-new/app/Transformers/MatchTransformer.ts @@ -2,8 +2,9 @@ import { getSeasonNumber, queuesWithRole, sortTeamByRole, supportItems } from 'A 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 from 'App/Services/RoleIdentiticationService' +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' export interface PlayerRole { champion: number, @@ -17,7 +18,7 @@ export default abstract class MatchTransformer { protected perks: PerkDTO[] protected perkstyles: PerkStyleDTO[] protected summonerSpells: SummonerSpellDTO[] - protected championRoles: any + protected championRoles: ChampionsPlayRate protected sortTeamByRole: (a: ParticipantBasic | ParticipantDetails, b: ParticipantBasic | ParticipantDetails) => number /** * Get global Context with CDragon Data @@ -35,7 +36,7 @@ export default abstract class MatchTransformer { this.perks = perks this.perkstyles = perkstyles.styles this.summonerSpells = summonerSpells - this.championRoles = championRoles + this.championRoles = championRoles as ChampionsPlayRate this.sortTeamByRole = sortTeamByRole } @@ -78,7 +79,7 @@ export default abstract class MatchTransformer { * @param detailed : detailed or not stats * @param teamStats : if detailed, the teamStats argument is mandatory */ - public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats: any = {}) { + public getPlayerData (match: MatchDto, player: ParticipantDto, detailed: boolean, teamStats?: TeamStats) { const identity = match.participantIdentities.find(p => p.participantId === player.participantId) const name = identity!.player.summonerName const champion = this.getChampion(player.championId) @@ -112,6 +113,7 @@ export default abstract class MatchTransformer { // Percent stats / Per minute stats : only for detailed match let percentStats: PercentStats if (detailed) { + teamStats = teamStats! percentStats = { minions: +(stats.minions / (match.gameDuration / 60)).toFixed(2), vision: +(stats.vision / (match.gameDuration / 60)).toFixed(2), From 210a47f62d871be8e58a3562d5a06988735c91f0 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Sun, 11 Oct 2020 18:32:33 +0200 Subject: [PATCH 28/33] refactor(client): update error handling to match adonis 5 validators --- client/src/store/modules/summoner.js | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/client/src/store/modules/summoner.js b/client/src/store/modules/summoner.js index 306d2f7..3be5f4a 100644 --- a/client/src/store/modules/summoner.js +++ b/client/src/store/modules/summoner.js @@ -115,32 +115,35 @@ export const actions = { commit('BASIC_REQUEST') try { const resp = await axios(({ url: 'summoner/basic', data: { summoner, region: regionId }, method: 'POST' })) - 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') - + if (!resp.data) { dispatch('notification/add', { type: 'error', message: 'Summoner not found.' }, { 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) { + if (error.response && error.response.status === 422) { + dispatch('notification/add', { + type: 'error', + message: 'Summoner not found.' + }, { root: true }) + } if (error.message !== 'Summoner changed') { commit('SUMMONER_NOT_FOUND') } - console.log(error) } }, championsNotLoaded({ commit }) { From 2b2f46d71cb5fe4dfda4dd7ea270b2b445dadaf4 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Fri, 16 Oct 2020 22:17:46 +0200 Subject: [PATCH 29/33] refactor: add working mongo migrations when ReplicaSet is on --- server-new/mongodb/migrations/1601816721286_match.ts | 2 +- server-new/mongodb/migrations/1601819828861_summoner.ts | 2 +- server-new/mongodb/migrations/1601819837856_detailed_match.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server-new/mongodb/migrations/1601816721286_match.ts b/server-new/mongodb/migrations/1601816721286_match.ts index 1187d86..31dd714 100644 --- a/server-new/mongodb/migrations/1601816721286_match.ts +++ b/server-new/mongodb/migrations/1601816721286_match.ts @@ -2,7 +2,7 @@ import BaseMigration from '@ioc:Mongodb/Migration' export default class MatchMigration extends BaseMigration { public up (): void { - // this.createCollection('matches') + this.createCollection('matches') this.createIndex('matches', 'gameId') this.createIndex('matches', 'summoner_puuid') } diff --git a/server-new/mongodb/migrations/1601819828861_summoner.ts b/server-new/mongodb/migrations/1601819828861_summoner.ts index 87045a4..ee54b3c 100644 --- a/server-new/mongodb/migrations/1601819828861_summoner.ts +++ b/server-new/mongodb/migrations/1601819828861_summoner.ts @@ -2,7 +2,7 @@ import BaseMigration from '@ioc:Mongodb/Migration' export default class SummonerMigration extends BaseMigration { public up (): void { - // this.createCollection('summoners') + this.createCollection('summoners') this.createIndex('summoners', 'puuid') } } diff --git a/server-new/mongodb/migrations/1601819837856_detailed_match.ts b/server-new/mongodb/migrations/1601819837856_detailed_match.ts index 4c86664..a7d4044 100644 --- a/server-new/mongodb/migrations/1601819837856_detailed_match.ts +++ b/server-new/mongodb/migrations/1601819837856_detailed_match.ts @@ -2,7 +2,7 @@ import BaseMigration from '@ioc:Mongodb/Migration' export default class DetailedMatchMigration extends BaseMigration { public up (): void { - // this.createCollection('detailed_matches') + this.createCollection('detailed_matches') this.createIndex('detailed_matches', 'gameId') } } From 77eb091b1168f6678dca71fd6f14f80e996d25d8 Mon Sep 17 00:00:00 2001 From: Valentin Kaelin Date: Fri, 16 Oct 2020 22:20:26 +0200 Subject: [PATCH 30/33] fix(live-game): timer fixed size only when the game has started --- client/src/components/Match/LiveMatch.vue | 7 ++++++- client/src/views/SummonerLive.vue | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/components/Match/LiveMatch.vue b/client/src/components/Match/LiveMatch.vue index b6711a6..82f4e1f 100644 --- a/client/src/components/Match/LiveMatch.vue +++ b/client/src/components/Match/LiveMatch.vue @@ -7,7 +7,12 @@ V S -
{{ displayStartTime }}
+
+ {{ displayStartTime }} +
  • {{ gamemode.type }} {{ gamemode.name }}
    -
    -
    {{ displayStartTime }}
    +
    {{ displayStartTime }}