diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 000000000..cb7e1b94d --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,4 @@ +/node_modules +*.log +.env +.env.test \ No newline at end of file diff --git a/api/Procfile b/api/Procfile new file mode 100644 index 000000000..e8f79ea7b --- /dev/null +++ b/api/Procfile @@ -0,0 +1 @@ +web: npm start \ No newline at end of file diff --git a/api/README.md b/api/README.md new file mode 100644 index 000000000..45ba3fad8 --- /dev/null +++ b/api/README.md @@ -0,0 +1,219 @@ +# RedFox - Teste para desenvolvedor web + +# Back-end + +Este é o back-end da aplicação, construida com cadastro de usuário e CRUD de pokemons +com direito a upload de imagem além de filtro de type, weather, e stat, seguindo boas práticas de programação +e abstraindo toda a lógica em camadas. + +# Tópicos + +- [Tecologias](#techs) +- [Rotas da aplicação](#routes) +- [Rodando a aplicação](#execute) +- [API online](#online) + + +## Tecnologias e bibliotecas utilizadas + +- [NodeJS](https://nodejs.org/en/) +- [KnexJS](http://knexjs.org/) +- [PostgreSQL](https://www.postgresql.org/) +- [Multer](https://www.npmjs.com/package/multer) +- [Sharp](https://sharp.pixelplumbing.com/) + + +## Rotas da aplicação + +### Públicas + +> As rotas públicas não necessitam da auteticação do usuário. + +- ``/api/register`` + +| Data | Resposta | Ação +|-------------|---------------|--------------------- +| name | name | Rota para criação de um usuário, com as credencias corretas +| email | email | +| password | id | +| | token | + + +- ``/api/login`` + +| Data | Resposta | Ação +|-------------|---------------|--------------------- +| email | email | Fazer o login do usuário com email e senha corretos +| password | id | +| | token | + +## Privadas + +> As rotas privadas necessitam da autenticação do usuário, além de que todas as rotas privadas +> utilizam o id do usuário armazenado no token que deve vim nos headers da resuisição, no formato de "Bearer +> token" + + +- ``/api/session/store`` + +| Data | Resposta | Ação +|-----------------|-----------------------------------|--------------------- +| name | Nenhuma reposta é enviada em json | Rota para adicionar um novo pokemon +| typeOne | +| typeTwo | +| imageName | +| weatherOne | +| weatherTwo | +| generation | +| evolutionStage | +| familyId | +| atk | +| def | +| stat | +| raidable | +| hatchable | +| evolved | +| crossGender | +| lengendary | +| acquirable | +| spawns | +| regional | +| shiny | +| nest | +| newField | +| notGettable | +| futureEvolve | + + +- ``/api/session/pokemon/id`` + +| Data | Resposta | Ação +|-------------|---------------------------|--------------------- +| Nenhuma | todos os dados do pokemon | Rota para obter todos os dados de um pokemon em específico + + +- ``api/session/pokemons`` +- ``api/session/pokemons?type={type}`` +- ``api/session/pokemons?weather={weather}`` +- ``api/session/pokemons?min_stat={min_stat}&max_stat={max_stat}`` +- ``api/session/pokemons?above={above}`` + +| Data | Resposta | Ação +|-------------|--------------------------------|--------------------- +| Nenhuma | todos os pokemons e seus dados | Rota para obter todos os pokemons e seus dados, com filtro via query na url da api + + +- ``/api/session/update/id`` + +| Data | Resposta | Ação +|--------------------------|--------------|--------------------- +| Dados a serem atualizaos | Nenhuma | Rota para atualizar os dados de um pokemon, exceto sua imagem + + +- ``/api/session/updateimage/id`` + +| Data | Resposta | Ação +|--------------------------|--------------|--------------------- +| Novo arquivo de imagem | Nenhuma | Rota para atualizar a imagem de um pokemon + + +- ``api/session/dropall`` + +| Data | Resposta |Ação +|-----------|--------------|-------------- +| Nenhuma | Nenhuma | Rota para deletar todos os pokemons do usuário + + +## Executando a API + +Para executar a api em sua máquina siga os passos abaixo. + +- 1 Clone meu repositório em sua máquina + +```sh +git clone git@github.com:edmilson-dk/teste-desenvolvimento-web.git + +# entre na pasta api + +cd teste-desenvolvimento-web/api +``` + +- 2 Após o passo acima, instale as dependências necessárias, para isso é preciso que você tenha o [NodeJS](https://nodejs.org/en/) instalado em sua máquina. + +```sh +npm install + +# ou com yarn + +yarn install +``` + +- 3 Feito a instalação de tudo é hora de configurar o banco de dados [PostgreSQL](https://www.postgresql.org/) +em sua máquina, caso você não o tenha instalado, acesse o site do [PostgreSQL](https://www.postgresql.org/) e siga os passos de instalação em seu sistema operacional, quando instalar inicie o postgresql e entre na linha de comando dele ou em uma interface gráfica que você utilize, para podermos criar nossa database e nosso usuário para a api poder ser utilizada, no meu caso os comandos são os seguintes. + +```sh +# Primeiro entro na linha de comandos do postresql e crio uma database. +❯ sudo -u postgres psql +[sudo] password for dk: +psql (12.6 (Ubuntu 12.6-0ubuntu0.20.04.1)) +Type "help" for help. + +postgres$ CREATE DATABASE "web_teste"; +postgres$ \q; + +# Após isso saiu da linha de comando em si, e entro na database que acabei de criar, para poder criar +# uma role que é um tipo de usuário no postgresql, nela passamos o username que no meu caso eu escolhi +# "web_teste_user" e passo algumas opções necessárias para a conexão além do meu password que botei como +# "webteste123", após isso já podemos conectar nossa api ao banco de dados. + +❯ sudo -i -u postgres psql web_teste +psql (12.6 (Ubuntu 12.6-0ubuntu0.20.04.1)) +Type "help" for help. + +web_teste$ CREATE ROLE web_teste_user CREATEDB LOGIN SUPERUSER PASSWORD 'webteste123'; +postgres$ \q; +``` + +- 4 Após o processo acima, vamos adicionar nossas credências do banco em um arquivo de variaveis de ambiente, +na pasta root do projeto que neste caso é a pasta ``api``, crie o arquivo .env e adicione a mesma marcação que esta presente no arquivo env.example que deixei disponivél no repositório, após fazer a marcação, adicione as credências, o seu .env deve ficar mais ou menos assim. + +```sh +# o banco de dados que estamos utilizando, neste caso pg que significa postgres +DB_CLIENT=pg + +# o mesmo nome da database que você criou +DATABASE=web_teste + +# suas credências da database +DB_USERNAME=web_teste_user +DB_PASSWORD=webteste123 + +# crie uma chave secreta, de preferência criptografada para ser única. +SECRET=abasjkdbkajdo3y4beqwdgas + +# aqui você não mexe +MIGRATIONS=./src/drivers/database/postgres/knex/migrations +``` + +- 5 Agora é só criarmos nossas [Migrations](https://medium.com/@juniorb2s/migrations-o-porque-e-como-usar-12d98c6d9269) para isso apenas execute o comando abaixo. + +```sh +npx knex migrate:latest +``` + +- 6 Por fim é só iniciar nossa api. + +```sh +npm dev + +# ou com yarn + +yarn dev +``` + + +## Veja a aplicação funcionando + +Caso você não queira executar os passos de instalação manualmente, para sua sorte fiz o deploy da aplicação, a url da API é esta ``https://redfox-api.herokuapp.com/``. + +Creator with 💙 by [Edmilson Jesus](https://www.linkedin.com/in/edmilson-jesus-4128711b5) diff --git a/api/env.example b/api/env.example new file mode 100644 index 000000000..7411ad76a --- /dev/null +++ b/api/env.example @@ -0,0 +1,8 @@ +DB_CLIENT= +DATABASE= +DB_USERNAME= +DB_PASSWORD= + +SECRET= + +MIGRATIONS=./src/drivers/database/postgres/knex/migrations \ No newline at end of file diff --git a/api/knexfile.js b/api/knexfile.js new file mode 100644 index 000000000..101292252 --- /dev/null +++ b/api/knexfile.js @@ -0,0 +1,38 @@ +require('dotenv').config(); +const pg = require('pg'); + +pg.defaults.ssl = { + rejectUnauthorized: false, +} + +module.exports = { + development: { + client: process.env.DB_CLIENT, + connection: { + database: process.env.DATABASE, + user: process.env.DB_USERNAME, + password: process.env.DB_PASSWORD, + }, + pool: { + min: 2, + max: 10 + }, + migrations: { + tableName: 'knex_migrations', + directory: process.env.MIGRATIONS + } + }, + production: { + client: process.env.DB_CLIENT, + connection: process.env.DATABASE_URL, + ssl: { rejectUnauthorized: false }, + pool: { + min: 2, + max: 10 + }, + migrations: { + tableName: 'knex_migrations', + directory: process.env.MIGRATIONS + } + }, +}; \ No newline at end of file diff --git a/api/package.json b/api/package.json new file mode 100644 index 000000000..bdfc0641f --- /dev/null +++ b/api/package.json @@ -0,0 +1,27 @@ +{ + "name": "api", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "dev": "npx nodemon src/main/server.js", + "start": "node src/main/server.js" + }, + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "helmet": "^4.4.1", + "joi": "^17.4.0", + "jsonwebtoken": "^8.5.1", + "knex": "^0.95.4", + "multer": "^1.4.2", + "pg": "^8.5.1", + "sharp": "^0.27.2", + "uuid": "^8.3.2" + }, + "devDependencies": { + "nodemon": "^2.0.7" + } +} diff --git a/api/src/application/use-cases/pokemon/PokemonUseCase.js b/api/src/application/use-cases/pokemon/PokemonUseCase.js new file mode 100644 index 000000000..bd86b1a99 --- /dev/null +++ b/api/src/application/use-cases/pokemon/PokemonUseCase.js @@ -0,0 +1,65 @@ +const Pokemon = require("../../../domain/entities/Pokemon"); + +class PokemonUseCase { + constructor({ pokemonRepository }) { + this.pokemonRepository = pokemonRepository; + } + + async create(data) { + const pokemon = new Pokemon({...data}); + + await this.pokemonRepository.create({...pokemon.getValues()}); + return; + } + + async dropPokemon({ pokemonNumber , userId }) { + const row = await this.pokemonRepository.dropPokemon({ pokemonNumber, userId }); + return row; + } + + async dropAllPokemons({ userId }) { + await this.pokemonRepository.dropAllPokemons({ userId }); + return; + } + + async updatePokemon({ pokemonNumber, userId, newData }) { + await this.pokemonRepository.updatePokemon({ pokemonNumber, userId, ...newData}); + return; + } + + async existsPokemon({ name, userId }) { + const exists = await this.pokemonRepository.existsPokemon({ name, userId }); + return exists; + } + + async existsPokemonById({ pokemonNumber, userId }) { + const exists = await this.pokemonRepository.existsPokemonById({ pokemonNumber, userId }); + return exists; + } + + async getAllPokemons({ userId, page, type, weather, minStatTotal, maxStatTotal, aboveStat }) { + const pokemons = await this.pokemonRepository.getAllPokemons({ + userId, type, weather, + minStatTotal, maxStatTotal, + aboveStat, page}); + + return pokemons; + } + + async getOldPokemonImage({ pokemonNumber, userId }) { + const row = await this.pokemonRepository.getOldPokemonImage({ pokemonNumber, userId }); + return row; + } + + async updatePokemonImage({ pokemonNumber, userId, imageName }) { + await this.pokemonRepository.updatePokemonImage({ pokemonNumber, userId, imageName }); + return; + } + + async getPokemon({ pokemonNumber, userId }) { + const pokemon = await this.pokemonRepository.getPokemon({ pokemonNumber, userId }); + return pokemon; + } +} + +module.exports = PokemonUseCase; \ No newline at end of file diff --git a/api/src/application/use-cases/user/UserUseCase.js b/api/src/application/use-cases/user/UserUseCase.js new file mode 100644 index 000000000..fbf2451ec --- /dev/null +++ b/api/src/application/use-cases/user/UserUseCase.js @@ -0,0 +1,26 @@ +const User = require("../../../domain/entities/User"); + +class UserUseCase { + constructor({ userRepository }) { + this.userRepository = userRepository; + } + + async create({ name, password, email }) { + const user = new User({ name, email, password }); + + await this.userRepository.create({...user.getValues()}); + return { id: user.id }; + } + + async existsUserByEmail({ email }) { + const exists = await this.userRepository.existsUserByEmail({ email }); + return exists; + } + + async findUserByEmail({ email }) { + const user = await this.userRepository.findUserByEmail({ email }); + return user; + } +} + +module.exports = UserUseCase; \ No newline at end of file diff --git a/api/src/domain/entities/Pokemon.js b/api/src/domain/entities/Pokemon.js new file mode 100644 index 000000000..ea2556eab --- /dev/null +++ b/api/src/domain/entities/Pokemon.js @@ -0,0 +1,71 @@ +class Pokemon { + constructor({ + userId, name, typeOne, typeTwo, imageName, + weatherOne, weatherTwo, generation, + evolutionStage, familyId, atk, + def, stat, raidable, hatchable, + evolved, crossGender, lengendary, + acquirable, spawns, regional, shiny, + nest, newField, notGettable, futureEvolve + }) { + this.userId = userId; + this.name = name; + this.imageName = imageName; + this.typeOne = typeOne; + this.typeTwo = typeTwo; + this.weatherOne = weatherOne; + this.weatherTwo = weatherTwo; + this.generation = generation; + this.evolutionStage = evolutionStage; + this.familyId = familyId; + this.atk = atk; + this.def = def; + this.stat = stat; + this.raidable = raidable; + this.hatchable = hatchable; + this.evolved = evolved; + this.crossGender = crossGender; + this.lengendary = lengendary; + this.acquirable = acquirable; + this.spawns = spawns; + this.regional = regional; + this.shiny = shiny; + this.nest = nest; + this.newField = newField; + this.notGettable = notGettable; + this.futureEvolve = futureEvolve; + } + + getValues() { + return { + userId: this.userId, + name: this.name, + imageName: this.imageName, + typeOne: this.typeOne, + typeTwo: this.typeTwo, + weatherOne: this.weatherOne, + weatherTwo: this.weatherTwo, + generation: this.generation, + evolutionStage: this.evolutionStage, + familyId: this.familyId, + atk: this.atk, + def: this.def, + stat: this.stat, + raidable: this.raidable, + hatchable: this.hatchable, + evolved: this.evolved, + crossGender: this.crossGender, + lengendary: this.lengendary, + acquirable: this.acquirable, + spawns: this.spawns, + regional: this.regional, + shiny: this.shiny, + nest: this.nest, + newField: this.newField, + notGettable: this.notGettable, + futureEvolve: this.futureEvolve, + } + } +} + +module.exports = Pokemon; \ No newline at end of file diff --git a/api/src/domain/entities/User.js b/api/src/domain/entities/User.js new file mode 100644 index 000000000..beaf96c0b --- /dev/null +++ b/api/src/domain/entities/User.js @@ -0,0 +1,21 @@ +const { v4 } = require("uuid"); + +class User { + constructor({ name, email, password }) { + this.id = v4(); + this.name = name; + this.email = email; + this.password = password; + } + + getValues() { + return { + id: this.id, + name: this.name, + email: this.email, + password: this.password, + } + } +} + +module.exports = User; \ No newline at end of file diff --git a/api/src/drivers/database/postgres/knex/index.js b/api/src/drivers/database/postgres/knex/index.js new file mode 100644 index 000000000..a9cbb2d39 --- /dev/null +++ b/api/src/drivers/database/postgres/knex/index.js @@ -0,0 +1,6 @@ +const environment = process.env.NODE_ENV || 'development'; + +const knexfile = require('../../../../../knexfile')[environment]; +const db = require('knex')(knexfile); + +module.exports = { db }; diff --git a/api/src/drivers/database/postgres/knex/migrations/20210327143044_user_table.js b/api/src/drivers/database/postgres/knex/migrations/20210327143044_user_table.js new file mode 100644 index 000000000..f02794aff --- /dev/null +++ b/api/src/drivers/database/postgres/knex/migrations/20210327143044_user_table.js @@ -0,0 +1,19 @@ +exports.up = function(knex) { + knex.schema.hasTable('users').then(exists => { + if (!exists) { + return knex.schema.createTable('users', table => { + table.string('id').notNullable().unique(); + table.string('name').notNullable(); + table.string('email').notNullable().unique(); + table.string('password').notNullable(); + + table.timestamp('created_at').defaultTo(knex.fn.now()); + table.timestamp('updated_at').defaultTo(knex.fn.now()); + }) + } + }) +}; + +exports.down = function(knex) { + knex.schema.dropTableIfExists('users'); +}; diff --git a/api/src/drivers/database/postgres/knex/migrations/20210327144255_pokemons_table.js b/api/src/drivers/database/postgres/knex/migrations/20210327144255_pokemons_table.js new file mode 100644 index 000000000..c7ff33517 --- /dev/null +++ b/api/src/drivers/database/postgres/knex/migrations/20210327144255_pokemons_table.js @@ -0,0 +1,50 @@ +exports.up = function(knex) { + knex.schema.hasTable('pokemons').then(exists => { + if (!exists) { + return knex.schema.createTable('pokemons', table => { + table.increments('pokemon_number'); + + table.string('name').notNullable(); + table.string('type_one'); + table.string('type_two'); + table.string('weather_one'); + table.string('weather_two'); + table.string('image_name'); + + table.integer('generation').notNullable(); + table.integer('evolution_stage').notNullable(); + table.integer('family_id').notNullable(); + table.integer('stat_total').notNullable(); + table.integer('atk').notNullable(); + table.integer('def').notNullable(); + table.integer('stat').notNullable(); + table.integer('raidable').notNullable(); + table.integer('hatchable').notNullable(); + + table.boolean('evolved').defaultTo(false); + table.boolean('cross_gender').defaultTo(false); + table.boolean('lengendary').defaultTo(false); + table.boolean('acquirable').defaultTo(false); + table.boolean('spawns').defaultTo(false); + table.boolean('regional').defaultTo(false); + table.boolean('shiny').defaultTo(false); + table.boolean('nest').defaultTo(false); + table.boolean('new').defaultTo(false); + table.boolean('not_gettable').defaultTo(false); + table.boolean('future_evolve').defaultTo(false); + + table.timestamp('created_at').defaultTo(knex.fn.now()); + table.timestamp('updated_at').defaultTo(knex.fn.now()); + + table.string('user_id') + .references('users.id') + .notNullable() + .onDelete('CASCADE'); + }); + } + }) +}; + +exports.down = function(knex) { + knex.schema.dropTableIfExists('pokemons'); +}; \ No newline at end of file diff --git a/api/src/drivers/imageStoreSettings/multer/index.js b/api/src/drivers/imageStoreSettings/multer/index.js new file mode 100644 index 000000000..d810c1c39 --- /dev/null +++ b/api/src/drivers/imageStoreSettings/multer/index.js @@ -0,0 +1,22 @@ +const path = require('path'); +const multer = require('multer'); + +const multerConfig = { + storage: new multer.diskStorage({ + destination: path.resolve(__dirname, '..', '..', '..', '..', 'uploads'), + filename: function(req, file, callback){ + const newFileName = `[${req.userId}]-${Date.now().toString()}-${file.originalname}`; + callback(null, newFileName); + } + }), + fileFilter: (req, file, callback) => { + const isAccepted = ['image/png', 'image/jpg', 'image/jpeg'] + .find(validFormat => validFormat == file.mimetype ); + + if(isAccepted) return callback(null, true); + + return callback(null, false); + } +} + +module.exports = { multerConfig }; \ No newline at end of file diff --git a/api/src/drivers/imageStoreSettings/sharp/index.js b/api/src/drivers/imageStoreSettings/sharp/index.js new file mode 100644 index 000000000..47db34dd6 --- /dev/null +++ b/api/src/drivers/imageStoreSettings/sharp/index.js @@ -0,0 +1,28 @@ +const fs = require('fs'); +const sharp = require('sharp'); + +function compressImage(file, size) { + const newPath = file.path.split('.')[0] + '.webp'; + + return sharp(file.path) + .resize(size) + .toFormat('webp') + .webp({ quality: 80 }) + .toBuffer() + .then(data => { + fs.access(file.path, (err) => { + if (!err) { + fs.unlink(file.path, err => { if (err) console.log(err) }); + } + }); + + fs.writeFile(newPath, data, err => { + if(err){ + throw err; + } + }); + return newPath; + }) +} + +module.exports = { compressImage }; \ No newline at end of file diff --git a/api/src/drivers/security/bcrypt.js b/api/src/drivers/security/bcrypt.js new file mode 100644 index 000000000..ae50b3982 --- /dev/null +++ b/api/src/drivers/security/bcrypt.js @@ -0,0 +1,19 @@ +const bcrypt = require('bcryptjs'); + +async function encrypt(password, salt) { + const userSalt = salt > 10 ? 10 : salt; + const saltResult = await bcrypt.genSalt(userSalt); + const result = await bcrypt.hash(password, saltResult); + + return result; +} + +async function isValidHash(currentHash, originalHash) { + const isValid = await bcrypt.compare(currentHash, originalHash); + return isValid; +} + +module.exports = { + encrypt, + isValidHash, +} \ No newline at end of file diff --git a/api/src/drivers/security/jwt.js b/api/src/drivers/security/jwt.js new file mode 100644 index 000000000..a199277b4 --- /dev/null +++ b/api/src/drivers/security/jwt.js @@ -0,0 +1,19 @@ +require('dotenv').config(); +const jwt = require("jsonwebtoken"); + +function createToken({ id }) { + const token = jwt.sign({ id }, process.env.SECRET, { + expiresIn: '5d' + }); + + return token; +} + +function isTokenValid({ token }, callback) { + jwt.verify(token, process.env.SECRET, callback); +} + +module.exports = { + createToken, + isTokenValid, +} \ No newline at end of file diff --git a/api/src/drivers/validate/joi/validateUserData.js b/api/src/drivers/validate/joi/validateUserData.js new file mode 100644 index 000000000..d74bee21a --- /dev/null +++ b/api/src/drivers/validate/joi/validateUserData.js @@ -0,0 +1,55 @@ +const Joi = require("joi"); + +function isValidEmail(email) { + const schema = Joi.object({ + email: Joi.string() + .required() + .max(255) + .pattern(new RegExp(/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) + }); + + const { error } = schema.validate({ email }); + + return error ? false : true; +} + +function isValidName(name) { + const schema = Joi.object({ + name: Joi.string() + .min(3) + .max(255) + .required() + }); + + const { error } = schema.validate({ name }); + + return error ? false : true; +} + +function isValidPassword(password) { + const schema = Joi.object({ + password: Joi.string() + .min(6) + .max(255) + .required() + }); + + const { error } = schema.validate({ password }); + + return error ? false : true; +} + +function isValidUserData(name, email, password) { + if (!isValidEmail(email)) return false; + if (!isValidName(name)) return false; + if (!isValidPassword(password)) return false; + + return true; +} + +module.exports = { + isValidEmail, + isValidName, + isValidPassword, + isValidUserData, +}; \ No newline at end of file diff --git a/api/src/infra/repositories/postgres/knex/pokemon/PokemonRepository.js b/api/src/infra/repositories/postgres/knex/pokemon/PokemonRepository.js new file mode 100644 index 000000000..173754900 --- /dev/null +++ b/api/src/infra/repositories/postgres/knex/pokemon/PokemonRepository.js @@ -0,0 +1,189 @@ +const { db } = require("../../../../../drivers/database/postgres/knex"); + +class PokemonRepository { + getStatTotal(atk, def, stat) { + const total = (Number(atk) + Number(def)) + Number(stat); + return total; + } + + async create({ + userId, name, typeOne, typeTwo, imageName, + weatherOne, weatherTwo, generation, + evolutionStage, familyId, atk, + def, stat, raidable, hatchable, + evolved, crossGender, lengendary, + acquirable, spawns, regional, shiny, + nest, newField, notGettable, futureEvolve + }) { + await db('pokemons') + .insert({ + name, generation, + atk, def, stat, raidable, hatchable, + evolved, lengendary, acquirable, + spawns, regional, shiny, nest, + evolution_stage: evolutionStage, + type_one: typeOne, + type_two: typeTwo, + image_name: imageName, + weather_one: weatherOne, + weather_two: weatherTwo, + family_id: familyId, + stat_total: this.getStatTotal(atk, def, stat), + cross_gender: crossGender, + new: newField, + not_gettable: notGettable, + future_evolve: futureEvolve, + user_id: userId + }); + + return; + } + + async updatePokemon({ + pokemonNumber, userId, + name, typeOne, typeTwo, + weatherOne, weatherTwo, generation, + evolutionStage, familyId, atk, + def, stat, raidable, hatchable, + evolved, crossGender, lengendary, + acquirable, spawns, regional, shiny, + nest, newField, notGettable, futureEvolve + }) { + await db('pokemons') + .where({ pokemon_number: pokemonNumber, user_id: userId }) + .update({ + name, generation, + atk, def, stat, raidable, hatchable, + evolved, lengendary, acquirable, + spawns, regional, shiny, nest, + evolution_stage: evolutionStage, + type_one: typeOne, + type_two: typeTwo, + weather_one: weatherOne, + weather_two: weatherTwo, + family_id: familyId, + stat_total: this.getStatTotal(atk, def, stat), + cross_gender: crossGender, + new: newField, + not_gettable: notGettable, + future_evolve: futureEvolve, + }); + + return; + } + + async dropPokemon({ pokemonNumber, userId }) { + const row = await db('pokemons') + .returning('image_name') + .where({ pokemon_number: pokemonNumber, user_id: userId }) + .del(); + + return row[0]; + } + + async dropAllPokemons({ userId }) { + await db('pokemons') + .where({ user_id: userId }) + .del(); + + return; + } + + async existsPokemon({ name, userId }) { + const exists = await db('pokemons') + .where({ name, user_id: userId }) + + return exists.length > 0 ? true : false; + } + + async existsPokemonById({ pokemonNumber, userId }) { + const exists = await db('pokemons') + .where({ pokemon_number: pokemonNumber, user_id: userId }); + + return exists.length > 0 ? true : false; + } + + async getAllPokemons({ userId, page, type, weather, minStatTotal, maxStatTotal, aboveStat }) { + const LIMIT_ITEMS = 24; + + let query = db('pokemons') + .limit(LIMIT_ITEMS) + .offset((page -1) * LIMIT_ITEMS) + .orderBy('created_at', 'desc') + .where({ user_id: userId }) + + if (type) { + query + .where({ type_one: type }) + .orWhere({ type_two: type }) + } + + if (weather) { + query + .where({ weather_one: weather }) + .orWhere({ weather_two: weather }) + } + + if (minStatTotal && maxStatTotal) { + query + .where('stat_total', '>=', minStatTotal) + .andWhere('stat_total', '<=', maxStatTotal) + } + + if (aboveStat) { + query + .where('stat_total', '>=', aboveStat) + } + + const pokemons = await query; + let count = 0; + + let queriesParams = [type, weather, maxStatTotal, minStatTotal, aboveStat]; + queriesParams = queriesParams.filter(item => item); + + let withQuery = queriesParams.length >= 1 ? true : false; + + if (!withQuery) { + const [{ count: result }] = await db('pokemons') + .count('user_id') + .where({ user_id: userId }); + + count = Number(result); + } else { + count = pokemons.length; + } + + const totalPages = count <= LIMIT_ITEMS ? 1 : Math.ceil(count / LIMIT_ITEMS); + + return { + count, + totalPages, + pokemons, + } + } + + async getPokemon({ pokemonNumber, userId }) { + const pokemon = await db('pokemons') + .where({ pokemon_number: pokemonNumber, user_id: userId }); + + return pokemon; + } + + async getOldPokemonImage({ pokemonNumber, userId }) { + const [{ image_name }] = await db('pokemons') + .select('image_name') + .where({ pokemon_number: pokemonNumber, user_id: userId }); + + return image_name; + } + + async updatePokemonImage({ pokemonNumber, userId, imageName }) { + await db('pokemons') + .where({ pokemon_number: pokemonNumber, user_id: userId }) + .update({ image_name: imageName }); + + return; + } +} + +module.exports = PokemonRepository; \ No newline at end of file diff --git a/api/src/infra/repositories/postgres/knex/user/UserRepository.js b/api/src/infra/repositories/postgres/knex/user/UserRepository.js new file mode 100644 index 000000000..a38e40f0c --- /dev/null +++ b/api/src/infra/repositories/postgres/knex/user/UserRepository.js @@ -0,0 +1,26 @@ +const { db } = require("../../../../../drivers/database/postgres/knex"); + +class UserRepository { + async create({ id, name, password, email }) { + await db('users') + .insert({ id, name, password, email }); + + return; + } + + async existsUserByEmail({ email }) { + const row = await db('users') + .where({ email }) + + return row.length > 0 ? true : false; + } + + async findUserByEmail({ email }) { + const row = await db('users') + .where({ email }); + + return row.length > 0 ? row[0] : null; + } +} + +module.exports = UserRepository; \ No newline at end of file diff --git a/api/src/interface/adapters/pokemon/PokemonAdapter.js b/api/src/interface/adapters/pokemon/PokemonAdapter.js new file mode 100644 index 000000000..1fd6ee02c --- /dev/null +++ b/api/src/interface/adapters/pokemon/PokemonAdapter.js @@ -0,0 +1,9 @@ +const PokemonUseCase = require("../../../application/use-cases/pokemon/PokemonUseCase"); +const PokemonRepository = require("../../../infra/repositories/postgres/knex/pokemon/PokemonRepository"); +const PokemonController = require("../../controllers/pokemon/PokemonController"); + +const pokemonRepository = new PokemonRepository(); +const pokemonUseCase = new PokemonUseCase({ pokemonRepository }); +const pokemonController = new PokemonController({ pokemonServices: pokemonUseCase }); + +module.exports = pokemonController; \ No newline at end of file diff --git a/api/src/interface/adapters/user/UserAdapter.js b/api/src/interface/adapters/user/UserAdapter.js new file mode 100644 index 000000000..c86c261e6 --- /dev/null +++ b/api/src/interface/adapters/user/UserAdapter.js @@ -0,0 +1,9 @@ +const UserUseCase = require("../../../application/use-cases/user/UserUseCase"); +const UserRepository = require("../../../infra/repositories/postgres/knex/user/UserRepository"); +const UserController = require("../../controllers/user/UserController"); + +const userRepository = new UserRepository(); +const userUseCase = new UserUseCase({ userRepository }); +const userController = new UserController({ userServices: userUseCase }); + +module.exports = userController; \ No newline at end of file diff --git a/api/src/interface/controllers/pokemon/PokemonController.js b/api/src/interface/controllers/pokemon/PokemonController.js new file mode 100644 index 000000000..a82b69177 --- /dev/null +++ b/api/src/interface/controllers/pokemon/PokemonController.js @@ -0,0 +1,153 @@ +const { compressImage } = require("../../../drivers/imageStoreSettings/sharp"); +const { deleteAllUploadImages, deleteUploadImage } = require('../../../utils/deleteUploadImage'); + +class PokemonController { + constructor({ pokemonServices }) { + this.pokemonServices = pokemonServices; + } + + async store(req, res) { + const data = req.body; + const userId = req.userId; + + const { filename } = req.file; + const [ originaFileName ] = filename.split('.'); + const imageName = `${originaFileName}.webp`; + + try { + if ((await this.pokemonServices.existsPokemon({ name: data.name, userId }))) { + return res.status(401).json({ error: 'Pokemon with name alredy exists'}); + } + + if (filename) { + await compressImage(req.file, 300); + } else { + return res.status(400).json({ error: 'A pokemon image is needed'}); + } + + await this.pokemonServices.create({ userId, imageName, ...data }); + + return res.status(201).json({ message: 'Pokemon created with success'}); + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async delete(req, res) { + const { id }= req.params; + const userId = req.userId; + + try { + if (!(await this.pokemonServices.existsPokemonById({ pokemonNumber: id, userId }))) { + return res.status(404).json({ error: 'Pokemon not found'}); + } + + const imageName = await this.pokemonServices.dropPokemon({ pokemonNumber: id, userId }); + deleteUploadImage(imageName); + + return res.status(200).json({ message: 'Pokemon was successfully deleted'}); + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async deleteAll(req, res) { + const userId = req.userId; + + try { + await this.pokemonServices.dropAllPokemons({ userId }); + deleteAllUploadImages(userId); + + return res.status(200).json({ message: 'Pokemons was successfully deleted'}); + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async update(req, res) { + const data = req.body; + const { id } = req.params; + const userId = req.userId; + + try { + if (!(await this.pokemonServices.existsPokemonById({ pokemonNumber: id, userId }))) { + return res.status(404).json({ error: 'Pokemon not found'}); + } + + await this.pokemonServices.updatePokemon({ + pokemonNumber: id, + userId, + newData: {...data}, + }); + + return res.status(200).json({ message: 'Pokemon was successfully updated'}); + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async getAll(req, res) { + const { page = 1, type, weather, min_stat, max_stat, above_stat } = req.query; + const userId = req.userId; + + try { + const pokemons = await this.pokemonServices.getAllPokemons({ + userId, + page, type, weather, + minStatTotal: min_stat, + maxStatTotal: max_stat, + aboveStat: above_stat + }); + + return res.status(200).json({ pokemons }); + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async getOne(req, res) { + const { id } = req.params; + const userId = req.userId; + + try { + const pokemon = await this.pokemonServices.getPokemon({ pokemonNumber: id, userId }); + + return res.status(200).json({ pokemon }); + } catch (err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } + + async updateImage(req, res) { + const { id } = req.params; + const { filename } = req.file; + const userId = req.userId; + + const [ originaFileName ] = filename.split('.'); + const imageName = `${originaFileName}.webp`; + + try { + if (!(await this.pokemonServices.existsPokemonById({ pokemonNumber: id, userId }))) { + deleteUploadImage(imageName); + return res.status(404).json({ error: 'Pokemon not found'}); + } + + if (filename) { + await compressImage(req.file, 300); + } else { + return res.status(400).json({ error: 'A pokemon image is needed' }); + } + + const oldImageName = await this.pokemonServices.getOldPokemonImage({ pokemonNumber: id, userId }); + await this.pokemonServices.updatePokemonImage({ pokemonNumber: id, userId, imageName }); + + deleteUploadImage(oldImageName) + + return res.status(200).json({ message: 'Pokemon image was successfully updated'}) + } catch(err) { + return res.status(500).json({ error: 'Internal server error'}); + } + } +} + +module.exports = PokemonController; \ No newline at end of file diff --git a/api/src/interface/controllers/user/UserController.js b/api/src/interface/controllers/user/UserController.js new file mode 100644 index 000000000..6803e423c --- /dev/null +++ b/api/src/interface/controllers/user/UserController.js @@ -0,0 +1,62 @@ +const { encrypt, isValidHash } = require("../../../drivers/security/bcrypt"); +const { createToken } = require("../../../drivers/security/jwt"); +const { isValidUserData } = require("../../../drivers/validate/joi/validateUserData"); + +class UserController { + constructor({ userServices }) { + this.userServices = userServices; + } + + async store(req, res) { + const { name, email, password } = req.body; + + try { + if (!isValidUserData(name, email, password)) { + return res.status(401).json({ error: 'Unspecified or incorrectly formatted data' }); + } + + const existsUser = await this.userServices.existsUserByEmail({ email }); + + if (existsUser) { + return res.status(401).json({ error: 'User with this email already exists' }); + } + + const passwordHash = await encrypt(password, 8); + const { id } = await this.userServices.create({ name, email, password: passwordHash }); + const token = createToken({ id }); + + return res.status(201).json({ name, email, token }); + } catch(err) { + return res.status(500).json({ error: 'Internal error '}); + } + } + + async login(req, res) { + const { email, password } = req.body; + + try { + const user = await this.userServices.findUserByEmail({ email }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + if(!(await isValidHash(password, user.password))) { + return res.status(401).json({ error: 'Password error' }); + } + + const token = createToken({ id: user.id }); + + return res.status(200).json({ + id: user.id, + name: user.name, + email, + token, + }); + } catch (err) { + return res.status(500).json({ error: 'Internal error' }); + } + } +} + +module.exports = UserController; diff --git a/api/src/interface/routes/middlewares/authMiddleware.js b/api/src/interface/routes/middlewares/authMiddleware.js new file mode 100644 index 000000000..a72180f54 --- /dev/null +++ b/api/src/interface/routes/middlewares/authMiddleware.js @@ -0,0 +1,32 @@ +const { isTokenValid } = require("../../../drivers/security/jwt"); + +function authMiddleware(req, res, next) { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ error: 'No token provieded' }); + } + + const parts = authHeader.split(' '); + + if (!parts.length === 2) { + return res.status(401).json({ error: 'Token properties error' }); + } + + const [ scheme, token ] = parts; + + if (!/^Bearer$/.test(scheme)) { + return res.status(401).json({ error: 'Token malformatted'}) + } + + isTokenValid({ token }, (err, decoded) => { + if (err) { + return res.status(401).json({ error: 'Token invalid' }); + } + + req.userId = decoded.id; + return next(); + }) +} + +module.exports = { authMiddleware }; \ No newline at end of file diff --git a/api/src/interface/routes/pokemon-routes.js b/api/src/interface/routes/pokemon-routes.js new file mode 100644 index 000000000..00fedce66 --- /dev/null +++ b/api/src/interface/routes/pokemon-routes.js @@ -0,0 +1,24 @@ +const { Router } = require("express"); +const multer = require("multer"); + +const { multerConfig } = require("../../drivers/imageStoreSettings/multer"); +const pokemonController = require("../adapters/pokemon/PokemonAdapter"); +const { authMiddleware } = require("./middlewares/authMiddleware"); + +const upload = multer(multerConfig); +const pokemonRoutes = Router(); + +pokemonRoutes.use('/session', authMiddleware); + +pokemonRoutes.post('/session/store', upload.single('image'), async (req, res) => await pokemonController.store(req, res)); + +pokemonRoutes.delete('/session/drop/:id', async (req, res) => await pokemonController.delete(req, res)); +pokemonRoutes.delete('/session/dropall', async (req, res) => await pokemonController.deleteAll(req, res)); + +pokemonRoutes.put('/session/update/:id', async (req, res) => await pokemonController.update(req, res)); +pokemonRoutes.put('/session/newimage/:id', upload.single('image'), async (req, res) => await pokemonController.updateImage(req, res)); + +pokemonRoutes.get('/session/pokemons', async (req, res) => await pokemonController.getAll(req, res)); +pokemonRoutes.get('/session/pokemon/:id', async (req, res) => await pokemonController.getOne(req, res)); + +module.exports = pokemonRoutes; \ No newline at end of file diff --git a/api/src/interface/routes/user-routes.js b/api/src/interface/routes/user-routes.js new file mode 100644 index 000000000..7c498f39b --- /dev/null +++ b/api/src/interface/routes/user-routes.js @@ -0,0 +1,10 @@ +const { Router } = require("express"); + +const userController = require("../adapters/user/UserAdapter"); + +const userRoutes = Router(); + +userRoutes.post('/register', async (req, res) => await userController.store(req, res)); +userRoutes.post('/login', async (req, res) => await userController.login(req, res)); + +module.exports = userRoutes; \ No newline at end of file diff --git a/api/src/main/app/index.js b/api/src/main/app/index.js new file mode 100644 index 000000000..72ef9c28b --- /dev/null +++ b/api/src/main/app/index.js @@ -0,0 +1,13 @@ +const express = require('express'); +const path = require('path'); + +const { setMiddlewares } = require('../config/middlewares'); +const { setRoutes } = require('../config/routes'); + +const app = new express(); + +app.use('/api/images', express.static(path.resolve(__dirname, '..', '..', '..', 'uploads'))); +setMiddlewares({ app }); +setRoutes({ app }); + +module.exports = app; \ No newline at end of file diff --git a/api/src/main/config/middlewares/index.js b/api/src/main/config/middlewares/index.js new file mode 100644 index 000000000..b7eea1b69 --- /dev/null +++ b/api/src/main/config/middlewares/index.js @@ -0,0 +1,14 @@ +const cors = require('cors'); +const helmet = require('helmet'); +const { json, urlencoded } = require('express'); + +function setMiddlewares({ app }) { + app.use(cors()); + app.use(helmet()); + app.use(json()); + app.use(urlencoded({ extended: true })); + + return; +} + +module.exports = { setMiddlewares }; \ No newline at end of file diff --git a/api/src/main/config/routes/index.js b/api/src/main/config/routes/index.js new file mode 100644 index 000000000..5f838fae7 --- /dev/null +++ b/api/src/main/config/routes/index.js @@ -0,0 +1,17 @@ +const { Router } = require('express'); + +const pokemonRoutes = require('../../../interface/routes/pokemon-routes'); +const userRoutes = require('../../../interface/routes/user-routes'); + +const router = Router(); + +function setRoutes({ app }) { + router.use(pokemonRoutes); + router.use(userRoutes); + + app.use('/api', router); + + return; +} + +module.exports = { setRoutes }; \ No newline at end of file diff --git a/api/src/main/server.js b/api/src/main/server.js new file mode 100644 index 000000000..ad1687adf --- /dev/null +++ b/api/src/main/server.js @@ -0,0 +1,6 @@ +require('dotenv').config(); + +const app = require('./app'); +const PORT = process.env.PORT || 3003; + +app.listen(PORT, () => console.log(`server is running at port: ${PORT}`)); \ No newline at end of file diff --git a/api/src/utils/deleteUploadImage.js b/api/src/utils/deleteUploadImage.js new file mode 100644 index 000000000..e576b74ac --- /dev/null +++ b/api/src/utils/deleteUploadImage.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const path = require('path'); + +function deleteUploadImage(imageName) { + const imagePath = `${path.resolve(__dirname, '..', '..', 'uploads')}/${imageName}`; + fs.unlink(imagePath, (err) => { if (err) console.log(err)}); +} + +function deleteAllUploadImages(userId) { + const imagesDirectory = path.resolve(__dirname, '..', '..', 'uploads'); + + fs.readdir(imagesDirectory, (err, files) => { + if (err) console.log(err); + + for (const file of files) { + const fileUserId = file.split(']-')[0].replace('[', ''); + + if (fileUserId === userId) { + fs.unlink(path.join(imagesDirectory, file), err => { + if (err) console.log(err); + }); + } + } + }); +} + +module.exports = { + deleteUploadImage, + deleteAllUploadImages +} \ No newline at end of file diff --git a/api/uploads/.uploads b/api/uploads/.uploads new file mode 100644 index 000000000..e69de29bb diff --git a/api/yarn.lock b/api/yarn.lock new file mode 100644 index 000000000..a6fe764c4 --- /dev/null +++ b/api/yarn.lock @@ -0,0 +1,2042 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@hapi/hoek@^9.0.0": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.1.tgz#9daf5745156fd84b8e9889a2dc721f0c58e894aa" + integrity sha512-CAEbWH7OIur6jEOzaai83jq3FmKmv4PmX1JYfs9IrYcGEVI/lyL1EXJGCj7eFVJ0bg5QR8LMxBlEtA+xKiLpFw== + +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/address@^4.1.0": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.1.tgz#9e321e74310963fdf8eebfbee09c7bd69972de4d" + integrity sha512-+I5aaQr3m0OAmMr7RQ3fR9zx55sejEYR2BFJaxL+zT3VM2611X0SHvPWIbAUBZVTn/YzYKbV8gJ2oT/QELknfQ== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== + dependencies: + string-width "^3.0.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541" + integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcryptjs@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +busboy@^0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" + integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= + dependencies: + dicer "0.2.5" + readable-stream "1.1.x" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.2.2: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cli-boxes@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" + integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.4" + +colorette@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + +commander@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +debug@2.6.9, debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +dicer@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" + integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= + dependencies: + readable-stream "1.1.x" + streamsearch "0.1.2" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +getopts@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" + integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +global-dirs@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== + dependencies: + ini "1.3.7" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +graceful-fs@^4.1.2: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +helmet@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7" + integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw== + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +joi@^17.4.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.0.tgz#b5c2277c8519e016316e49ababd41a1908d9ef20" + integrity sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.0" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +knex@^0.95.4: + version "0.95.4" + resolved "https://registry.yarnpkg.com/knex/-/knex-0.95.4.tgz#91578e425d054e76cf0aacbc1157fa8ee5b6da4c" + integrity sha512-IwUcHr6AkZPL707mJCOal1P4jlgxKMy17IMjJm5W23yrkM1jO2/APBM1eyw/MhQ61w8T7NpzGD+LEkr8M46mWw== + dependencies: + colorette "1.2.1" + commander "^7.1.0" + debug "4.3.1" + escalade "^3.1.1" + esm "^3.2.25" + getopts "2.2.5" + interpret "^2.2.0" + lodash "^4.17.21" + pg-connection-string "2.4.0" + rechoir "^0.7.0" + resolve-from "^5.0.0" + tarn "^3.0.1" + tildify "2.0.0" + +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-types@~2.1.24: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multer@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" + integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== + dependencies: + append-field "^1.0.0" + busboy "^0.2.11" + concat-stream "^1.5.2" + mkdirp "^0.5.1" + object-assign "^4.1.1" + on-finished "^2.3.0" + type-is "^1.6.4" + xtend "^4.0.0" + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +node-abi@^2.7.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" + integrity sha512-smhrivuPqEM3H5LmnY3KU6HfYv0u4QklgAxfFyRNujKUzbUcYZ+Jc2EhukB9SRcD2VpqhxM7n/MIcp1Ua1/JMg== + dependencies: + semver "^5.4.1" + +node-addon-api@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" + integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== + +nodemon@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" + integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.3" + update-notifier "^4.1.0" + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + +npmlog@^4.0.1, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +on-finished@^2.3.0, on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +pg-connection-string@2.4.0, pg-connection-string@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10" + integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.2.2.tgz#a560e433443ed4ad946b84d774b3f22452694dff" + integrity sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA== + +pg-protocol@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.4.0.tgz#43a71a92f6fe3ac559952555aa3335c8cb4908be" + integrity sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.5.1.tgz#34dcb15f6db4a29c702bf5031ef2e1e25a06a120" + integrity sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.4.0" + pg-pool "^3.2.2" + pg-protocol "^1.4.0" + pg-types "^2.1.0" + pgpass "1.x" + +pgpass@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.4.tgz#85eb93a83800b20f8057a2b029bf05abaf94ea9c" + integrity sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w== + dependencies: + split2 "^3.1.1" + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +prebuild-install@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.0.1.tgz#5902172f7a40eb67305b96c2a695db32636ee26d" + integrity sha512-7GOJrLuow8yeiyv75rmvZyeMGzl8mdEX5gY69d6a6bHWmiPevwqFw+tQavhK0EYMaSg3/KD24cWqeQv1EWsqDQ== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pupa@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@^1.2.7, rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.6, readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + 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" + +readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.9.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver@^5.4.1, semver@^5.6.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + 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" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sharp@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.27.2.tgz#a939775e630e88600c0b5e68f20593aea722252f" + integrity sha512-w3FVoONPG/x5MXCc3wsjOS+b9h3CI60qkus6EPQU4dkT0BDm0PyGhDCK6KhtfT3/vbeOMOXAKFNSw+I3QGWkMA== + dependencies: + array-flatten "^3.0.0" + color "^3.1.3" + detect-libc "^1.0.3" + node-addon-api "^3.1.0" + npmlog "^4.1.2" + prebuild-install "^6.0.1" + semver "^7.3.4" + simple-get "^4.0.0" + tar-fs "^2.1.1" + tunnel-agent "^0.6.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +split2@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.0.0, string-width@^4.1.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tarn@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.1.tgz#ebac2c6dbc6977d34d4526e0a7814200386a8aec" + integrity sha512-6usSlV9KyHsspvwu2duKH+FMUhqJnAh6J5J/4MITl8s94iSUQTLkJggdiewKv4RyARQccnigV48Z+khiuVZDJw== + +term-size@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== + +tildify@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +undefsafe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" + integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== + dependencies: + debug "^2.2.0" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +update-notifier@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/web/.env b/web/.env new file mode 100644 index 000000000..bf962b1f1 --- /dev/null +++ b/web/.env @@ -0,0 +1 @@ +REACT_APP_URL_API=http://localhost:3003 \ No newline at end of file diff --git a/web/.github/dashboard-create.png b/web/.github/dashboard-create.png new file mode 100644 index 000000000..3d03db8f4 Binary files /dev/null and b/web/.github/dashboard-create.png differ diff --git a/web/.github/dashboard.png b/web/.github/dashboard.png new file mode 100644 index 000000000..4456e97fa Binary files /dev/null and b/web/.github/dashboard.png differ diff --git a/web/.github/filtro.png b/web/.github/filtro.png new file mode 100644 index 000000000..717642a36 Binary files /dev/null and b/web/.github/filtro.png differ diff --git a/web/.github/home.png b/web/.github/home.png new file mode 100644 index 000000000..0647591ac Binary files /dev/null and b/web/.github/home.png differ diff --git a/web/.github/login.png b/web/.github/login.png new file mode 100644 index 000000000..fb65aec39 Binary files /dev/null and b/web/.github/login.png differ diff --git a/web/.github/register.png b/web/.github/register.png new file mode 100644 index 000000000..fe497c785 Binary files /dev/null and b/web/.github/register.png differ diff --git a/web/.github/view.png b/web/.github/view.png new file mode 100644 index 000000000..67478c98c Binary files /dev/null and b/web/.github/view.png differ diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 000000000..4d29575de --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/web/Procfile b/web/Procfile new file mode 100644 index 000000000..e8f79ea7b --- /dev/null +++ b/web/Procfile @@ -0,0 +1 @@ +web: npm start \ No newline at end of file diff --git a/web/README.md b/web/README.md new file mode 100644 index 000000000..8ec04f8c8 --- /dev/null +++ b/web/README.md @@ -0,0 +1,117 @@ +# RedFox - Teste para desenvolvedor web + +# Front-end + +Este é o front-end da aplicação, construida totalmente resonsiva, layout agradavél, com telas de login e registro, tela para listagem de pokemons, formulário para criar pokemons, filtro, etc. + +# Tópicos + +- [Tecologias](#techs) +- [Rotas da aplicação](#routes) +- [Rodando a aplicação](#execute) +- [Página online](#online) + + +## Tecnologias e bibliotecas utilizadas + +- [ReactJS](https://pt-br.reactjs.org/) +- [Material UI](https://material-ui.com/pt/) +- [Axios](https://www.npmjs.com/package/axios) +- [Toastify](https://www.npmjs.com/package/react-toastify) + + +## Rotas da aplicação + +### Públicas + +> As rotas públicas não necessitam da auteticação do usuário. + +- ``/`` + +> Rota da home da aplicação. + + +- ``/login`` + +> Rota para o usuário fazer login. + + +- ``/register`` + +> Rota para o usuário criar conta. + +### Privadas + +> AS rotas privadas necessitam que o usuário esteja autenticado. + + +- ``/dashboard`` + +> Rota da dashboard aplicação. + + +- ``/create`` + +> Rota para adicionar um novo pokemon. + +__Todas as rotas abaixo só podem ser acessadas via clique no botão de um card relacionado a um pokemon em espécifico, caso contrário você apenas retornará a página da dashboard__ + +- ``/updatepokemon`` + +> Rota para atualizar alguns dados do pokemon. + + +- ``/updateimage`` + +> Rota para atualizar a imagem de um pokemon. + + +- ``/viewpokemon`` + +> Rota para visualizar os dados de um pokemon + + +## Executando o front-end + +Para executar a frnt-end em sua máquina siga os passos abaixo. + +- 1 Clone meu repositório em sua máquina + +```sh +git clone git@github.com:edmilson-dk/teste-desenvolvimento-web.git + +# entre na pasta web + +cd teste-desenvolvimento-web/web +``` + +- 2 Após o passo acima, instale as dependências necessárias, para isso é preciso que você tenha o [NodeJS](https://nodejs.org/en/) instalado em sua máquina. + +```sh +npm install + +# ou com yarn + +yarn install +``` + +- 3 Agora crie um arquivo na raiz do projeto, chamado .env e dentro dele escreva o mesmo conteúdo que tem no arquivo "env.example" que deixei neste repositório, agora após o ``REACT_APP_URL_API=`` você deve colocar a url da api se você estiver executando a api em sua máquina local, igual eu ensinei na pasta da api, você deve deixar assim ``REACT_APP_URL_API=http://localhost:3003``, caso contrario use a api que esta online deve ficar assim ``https://redfox-api.herokuapp.com/``. + +- 4 Feito isso é hora de executar o projeto, para isso execute o comando abaixo. + +```sh +npm start + +# ou com yarn + +yarn start +``` + + +## Veja a aplicação funcionando + +Caso você não queira executar os passos de instalação manualmente, para sua sorte fiz o deploy da aplicação, e você pode testa ela no link abaixo. + +[Aplicação aqui](https://redfox-test-web.vercel.app/) + +Creator with 💙 by [Edmilson Jesus](https://www.linkedin.com/in/edmilson-jesus-4128711b5) diff --git a/web/env.example b/web/env.example new file mode 100644 index 000000000..1966cb31d --- /dev/null +++ b/web/env.example @@ -0,0 +1 @@ +REACT_APP_URL_API= \ No newline at end of file diff --git a/web/package.json b/web/package.json new file mode 100644 index 000000000..563b407e8 --- /dev/null +++ b/web/package.json @@ -0,0 +1,47 @@ +{ + "name": "web", + "version": "0.1.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", + "@material-ui/lab": "^4.0.0-alpha.57", + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "axios": "^0.21.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-draggable": "^4.4.3", + "react-icons": "^4.2.0", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.3", + "react-toastify": "^7.0.3", + "web-vitals": "^1.0.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": {} +} diff --git a/web/public/index.html b/web/public/index.html new file mode 100644 index 000000000..624761e50 --- /dev/null +++ b/web/public/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + PokeStore + + + +
+ + diff --git a/web/src/App.js b/web/src/App.js new file mode 100644 index 000000000..07c06f60f --- /dev/null +++ b/web/src/App.js @@ -0,0 +1,18 @@ +import { ToastContainer } from 'react-toastify'; + +import 'react-toastify/dist/ReactToastify.css'; +import './styles/index.css'; + +import Routes from "./routes"; +import CustomThemeProvider from './styles/theme'; + +function App() { + return ( + + + + + ); +} + +export default App; diff --git a/web/src/assets/images/bgPokemon.png b/web/src/assets/images/bgPokemon.png new file mode 100644 index 000000000..4dbaae8c3 Binary files /dev/null and b/web/src/assets/images/bgPokemon.png differ diff --git a/web/src/components/AuthForm/Button/index.js b/web/src/components/AuthForm/Button/index.js new file mode 100644 index 000000000..cda9a636e --- /dev/null +++ b/web/src/components/AuthForm/Button/index.js @@ -0,0 +1,16 @@ +import { Button, Typography } from "@material-ui/core"; +import useStyles from "./styles"; + +function AuthButton({ label, onClick }) { + const classes = useStyles(); + + return ( + + ) +} + +export default AuthButton; \ No newline at end of file diff --git a/web/src/components/AuthForm/Button/styles.js b/web/src/components/AuthForm/Button/styles.js new file mode 100644 index 000000000..3b64e17ed --- /dev/null +++ b/web/src/components/AuthForm/Button/styles.js @@ -0,0 +1,9 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + marginTop: '2rem' + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/AuthForm/Input/index.js b/web/src/components/AuthForm/Input/index.js new file mode 100644 index 000000000..da403efe0 --- /dev/null +++ b/web/src/components/AuthForm/Input/index.js @@ -0,0 +1,28 @@ +import { TextField } from "@material-ui/core"; + +import useStyles from "./styles"; + +function AuthInput({ id, name, label, type, value, onChange }) { + const classes = useStyles(); + + return ( + + ) +} +export default AuthInput; \ No newline at end of file diff --git a/web/src/components/AuthForm/Input/styles.js b/web/src/components/AuthForm/Input/styles.js new file mode 100644 index 000000000..b64f71aed --- /dev/null +++ b/web/src/components/AuthForm/Input/styles.js @@ -0,0 +1,11 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + fontSize: '2rem', + borderRadius: '5px', + backgroundColor: theme.palette.white.main, + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/AuthForm/index.js b/web/src/components/AuthForm/index.js new file mode 100644 index 000000000..1904d113c --- /dev/null +++ b/web/src/components/AuthForm/index.js @@ -0,0 +1,14 @@ +import { FormControl } from "@material-ui/core"; +import useStyles from "./styles"; + +function AuthForm({ children }) { + const classes = useStyles(); + + return ( + + { children } + + ) +} + +export default AuthForm; \ No newline at end of file diff --git a/web/src/components/AuthForm/styles.js b/web/src/components/AuthForm/styles.js new file mode 100644 index 000000000..b60782e6e --- /dev/null +++ b/web/src/components/AuthForm/styles.js @@ -0,0 +1,11 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: '#f1f1f1', + padding: '1rem', + borderRadius: '5px', + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/CardPokemon/index.js b/web/src/components/CardPokemon/index.js new file mode 100644 index 000000000..7ab2925ec --- /dev/null +++ b/web/src/components/CardPokemon/index.js @@ -0,0 +1,41 @@ +import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Typography } from "@material-ui/core"; +import { Create, DeleteOutline, Visibility, Wallpaper } from "@material-ui/icons"; + +import useStyles from "./styles"; + +function CardPokemon({ image, name, handleViewPokemon, handleDelete, handleUpdateImage, handleUpdatePokemon }) { + const classes = useStyles(); + + return ( + + + + + + { name } + + + + + + + + + + + ) +} + +export default CardPokemon; \ No newline at end of file diff --git a/web/src/components/CardPokemon/styles.js b/web/src/components/CardPokemon/styles.js new file mode 100644 index 000000000..39667d0cc --- /dev/null +++ b/web/src/components/CardPokemon/styles.js @@ -0,0 +1,60 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + maxWidth: '100%', + backgroundColor: theme.palette.dark.main, + boxShadow: '0 0 3px 2px rgba(244, 244, 244, 0.080)' + }, + media: { + height: 140, + }, + title: { + fontSize: '1.5rem', + fontWeight: 500, + color: theme.palette.primary.main, + }, + cardActions: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + }, + buttonView: { + backgroundColor: theme.palette.success.main, + color: theme.palette.white.main, + fontSize: '1rem', + '&:hover': { + backgroundColor: theme.palette.success.main, + } + }, + buttonDelete: { + backgroundColor: theme.palette.error.main, + color: theme.palette.white.main, + fontSize: '1rem', + '&:hover': { + backgroundColor: theme.palette.error.main, + } + }, + buttonUpdate: { + backgroundColor: theme.palette.blue.main, + color: theme.palette.white.main, + fontSize: '1rem', + '&:hover': { + backgroundColor: theme.palette.blue.main, + } + }, + buttonImage: { + backgroundColor: theme.palette.secondary.main, + color: theme.palette.white.main, + fontSize: '1rem', + '&:hover': { + backgroundColor: theme.palette.secondary.main, + } + }, + iconStyle: { + fontSize: '30px', + } +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/CardViewPokemon/index.js b/web/src/components/CardViewPokemon/index.js new file mode 100644 index 000000000..39e08c013 --- /dev/null +++ b/web/src/components/CardViewPokemon/index.js @@ -0,0 +1,46 @@ +import { Card, CardActionArea, CardActions, CardContent, CardMedia, Typography } from "@material-ui/core"; + +import useStyles from "./styles"; + +function CardViewPokemon({ data, name }) { + const classes = useStyles(); + + return ( + + + + + + { name } + + + + + { + data.map(item => ( + <> +

Type one: {item.type_one}

+

Type Two: {item.type_two}

+

Weather One: {item.weather_one}

+

Weather Two: {item.weather_two}

+

Generation: {item.generation}

+

Atk: {item.atk}

+

Def: {item.def}

+

Stat: {item.stat}

+

Stat total: {item.stat_total}

+

Raidable: {item.raidable}

+

Hatchable: {item.hatchable}

+

Evolution Stage: {item.evolution_stage}

+ + )) + } +
+
+ ) +} + +export default CardViewPokemon; \ No newline at end of file diff --git a/web/src/components/CardViewPokemon/styles.js b/web/src/components/CardViewPokemon/styles.js new file mode 100644 index 000000000..84549e98e --- /dev/null +++ b/web/src/components/CardViewPokemon/styles.js @@ -0,0 +1,25 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + + backgroundColor: theme.palette.dark.main, + boxShadow: '0 0 3px 3px rgba(244, 244, 244, 0.080)' + }, + media: { + height: 240, + }, + title: { + fontSize: '1.8rem', + fontWeight: 700, + color: theme.palette.secondary.main, + }, + cardActions: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/Container/index.js b/web/src/components/Container/index.js new file mode 100644 index 000000000..76bbb8ba5 --- /dev/null +++ b/web/src/components/Container/index.js @@ -0,0 +1,11 @@ +import { Container } from '@material-ui/core'; + +function ContainerComponent({ children }) { + return ( + + { children } + + ) +} + +export default ContainerComponent; \ No newline at end of file diff --git a/web/src/components/DashBoardWrapper/index.js b/web/src/components/DashBoardWrapper/index.js new file mode 100644 index 000000000..2b4b06240 --- /dev/null +++ b/web/src/components/DashBoardWrapper/index.js @@ -0,0 +1,80 @@ +import { AppBar, Box, Button } from "@material-ui/core"; +import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import TuneIcon from '@material-ui/icons/Tune'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import { Link, useHistory } from "react-router-dom"; +import { useContext } from "react"; + +import useTokenStore from "../../hooks/useTokenStore"; + +import ContainerComponent from "../Container"; +import Logo from "../Logo"; +import useStyles from "./styles"; + +import { FilterPokemonsContext } from "../../contexts/FilterPokemonsContext"; + +function DashboardWrapper({ children}) { + const { handleOpenDialogFilter } = useContext(FilterPokemonsContext); + + const classes = useStyles(); + const { userLogout } = useTokenStore(); + let history = useHistory(); + + function handleLogout() { + userLogout(); + history.push('/'); + } + + return ( + <> + + + +
+ +
+ +
+
+
+ +
+ + +
+
+ + + { children } + + + + ) +} + +export default DashboardWrapper; \ No newline at end of file diff --git a/web/src/components/DashBoardWrapper/styles.js b/web/src/components/DashBoardWrapper/styles.js new file mode 100644 index 000000000..07de1d638 --- /dev/null +++ b/web/src/components/DashBoardWrapper/styles.js @@ -0,0 +1,39 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + navBarApp: { + backgroundColor: theme.palette.white.main, + boxShadow: "0 0 0 0 transparent" + }, + wrapperButtons: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + '& > *': { + margin: theme.spacing(1), + }, + }, + buttonLogout: { + fontSize: '1.2rem', + }, + appBar: { + backgroundColor: theme.palette.dark.main, + height: '5rem', + boxShadow: "0 0 0 0 transparent" + }, + buttonStyle: { + width: '180px', + maxWidth: '200px', + fontSize: '1rem', + color: theme.palette.secondary.main, + border: '2px solid', + borderColor: theme.palette.secondary.main, + }, + iconStyle: { + color: theme.palette.secondary.main, + fontSize: 30, + } +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/FilterPokemons/InputFilter/index.js b/web/src/components/FilterPokemons/InputFilter/index.js new file mode 100644 index 000000000..0ad4f9123 --- /dev/null +++ b/web/src/components/FilterPokemons/InputFilter/index.js @@ -0,0 +1,28 @@ +import { TextField } from "@material-ui/core"; + +import useStyles from "./styles"; + +function InputFilter({ id, name, label, type, value, onChange }) { + const classes = useStyles(); + + return ( + + ) +} +export default InputFilter; \ No newline at end of file diff --git a/web/src/components/FilterPokemons/InputFilter/styles.js b/web/src/components/FilterPokemons/InputFilter/styles.js new file mode 100644 index 000000000..b64f71aed --- /dev/null +++ b/web/src/components/FilterPokemons/InputFilter/styles.js @@ -0,0 +1,11 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + fontSize: '2rem', + borderRadius: '5px', + backgroundColor: theme.palette.white.main, + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/FilterPokemons/index.js b/web/src/components/FilterPokemons/index.js new file mode 100644 index 000000000..a476abc93 --- /dev/null +++ b/web/src/components/FilterPokemons/index.js @@ -0,0 +1,67 @@ +import { + Button, Dialog, DialogActions, DialogContent, + DialogTitle, FormControlLabel, Grid, Paper, Radio, RadioGroup, Typography + } from "@material-ui/core"; +import { useContext, useState } from "react"; +import Draggable from 'react-draggable'; +import { FilterPokemonsContext } from "../../contexts/FilterPokemonsContext"; +import InputFilter from "./InputFilter"; + +function PaperComponent(props) { + return ( + + + + ); +} + +function FilterPokemons() { + const { openDialogFilter, handleCloseDialogFilter, setQueries } = useContext(FilterPokemonsContext); + const [ stat, setStat ] = useState(''); + const [ type, setType ] = useState(''); + const [ weather, setWeather ] = useState(''); + + function handlerSave() { + setQueries(`${stat}type=${type}&weather=${weather}`); + handleCloseDialogFilter(); + } + + return ( +
+ + + Filtros + + + + + Stat entre: + setStat(e.target.value)}> + } label='0 - 300' /> + } label='300 - 600' /> + } label='600 - 1000' /> + } label='Acima de 1000' /> + + + + setType(e.target.value)}/> + setWeather(e.target.value)}/> + + + + + + + +
+ ); +} + +export default FilterPokemons; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/Button/index.js b/web/src/components/FormCreatePokemon/Button/index.js new file mode 100644 index 000000000..7fbb165df --- /dev/null +++ b/web/src/components/FormCreatePokemon/Button/index.js @@ -0,0 +1,16 @@ +import { Button, Typography } from "@material-ui/core"; +import useStyles from "./styles"; + +function ButtonCreatePokemon({ label, onClick }) { + const classes = useStyles(); + + return ( + + ) +} + +export default ButtonCreatePokemon; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/Button/styles.js b/web/src/components/FormCreatePokemon/Button/styles.js new file mode 100644 index 000000000..3b64e17ed --- /dev/null +++ b/web/src/components/FormCreatePokemon/Button/styles.js @@ -0,0 +1,9 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + marginTop: '2rem' + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/Input/index.js b/web/src/components/FormCreatePokemon/Input/index.js new file mode 100644 index 000000000..a216b3631 --- /dev/null +++ b/web/src/components/FormCreatePokemon/Input/index.js @@ -0,0 +1,44 @@ +import { TextField } from "@material-ui/core"; + +import useStyles from "./styles"; + +function InputCreatePokemon({ isFile, id, name, label, type, value, onChange }) { + const classes = useStyles(); + + return ( + <> + { !isFile ? ( + ) : ( + <> + + + + )} + + ) +} +export default InputCreatePokemon; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/Input/styles.js b/web/src/components/FormCreatePokemon/Input/styles.js new file mode 100644 index 000000000..f8a07fac5 --- /dev/null +++ b/web/src/components/FormCreatePokemon/Input/styles.js @@ -0,0 +1,29 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + fontSize: '2rem', + borderRadius: '5px', + backgroundColor: theme.palette.white.main, + }, + inputFile: { + display: 'none', + }, + labelFile: { + width: '100%', + height: '3.5rem', + padding: '10px', + textAlign: 'left', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + backgroundColor: theme.palette.white.main, + border: '1px solid #ccc', + fontSize: '1.6rem', + color: 'gray', + borderRadius: '5px', + marginTop: '1rem', + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/Radio/index.js b/web/src/components/FormCreatePokemon/Radio/index.js new file mode 100644 index 000000000..c525eeeff --- /dev/null +++ b/web/src/components/FormCreatePokemon/Radio/index.js @@ -0,0 +1,15 @@ +import { FormControlLabel, Radio, RadioGroup, Typography } from "@material-ui/core"; + +function RadioCreatePokemon({ value, onChange, ariaLabel, name, label }) { + return ( + <> + {label} + + } label='True' /> + } label='False' /> + + + ) +} + +export default RadioCreatePokemon; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/index.js b/web/src/components/FormCreatePokemon/index.js new file mode 100644 index 000000000..f14c715ce --- /dev/null +++ b/web/src/components/FormCreatePokemon/index.js @@ -0,0 +1,14 @@ +import { FormControl } from "@material-ui/core"; +import useStyles from "./styles"; + +function FormCreatePokemon({ children }) { + const classes = useStyles(); + + return ( + + { children } + + ) +} + +export default FormCreatePokemon; \ No newline at end of file diff --git a/web/src/components/FormCreatePokemon/styles.js b/web/src/components/FormCreatePokemon/styles.js new file mode 100644 index 000000000..b60782e6e --- /dev/null +++ b/web/src/components/FormCreatePokemon/styles.js @@ -0,0 +1,11 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: '#f1f1f1', + padding: '1rem', + borderRadius: '5px', + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/GoBackDashBoardHeader/index.js b/web/src/components/GoBackDashBoardHeader/index.js new file mode 100644 index 000000000..b5d3ca5ea --- /dev/null +++ b/web/src/components/GoBackDashBoardHeader/index.js @@ -0,0 +1,29 @@ +import { AppBar, Box, Button } from "@material-ui/core"; +import { Link } from "react-router-dom"; + +import ContainerComponent from "../Container"; +import Logo from "../Logo"; +import useStyles from "./styles" + +function GoBackDashBoardHeader() { + const classes = useStyles(); + + return ( + + + +
+ +
+ +
+
+
+ ) +} + +export default GoBackDashBoardHeader; \ No newline at end of file diff --git a/web/src/components/GoBackDashBoardHeader/styles.js b/web/src/components/GoBackDashBoardHeader/styles.js new file mode 100644 index 000000000..dab9762eb --- /dev/null +++ b/web/src/components/GoBackDashBoardHeader/styles.js @@ -0,0 +1,15 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + appBar: { + backgroundColor: theme.palette.dark.main, + height: '5rem', + boxShadow: "0 4px 10px 0 transparent" + }, + btnStyle: { + fontSize: "1rem", + fontWeight: 600, + } +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/HomeHeader/index.js b/web/src/components/HomeHeader/index.js new file mode 100644 index 000000000..46784279b --- /dev/null +++ b/web/src/components/HomeHeader/index.js @@ -0,0 +1,42 @@ +import { AppBar, Box, Button } from '@material-ui/core'; +import { Link } from 'react-router-dom'; + +import ContainerComponent from '../Container'; +import Logo from '../Logo'; + +import useStyles from './styles'; + +function HomeHeader({ registerRouter, loginRouter, isRouterHome }) { + const classes = useStyles(); + + return ( + + + +
+ +
+ +
+
+
+ ) +} + +export default HomeHeader; \ No newline at end of file diff --git a/web/src/components/HomeHeader/styles.js b/web/src/components/HomeHeader/styles.js new file mode 100644 index 000000000..0b64499c0 --- /dev/null +++ b/web/src/components/HomeHeader/styles.js @@ -0,0 +1,21 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + mr: { + marginRight: '10px' + }, + appBar: { + backgroundColor: theme.palette.dark.main, + height: '5rem', + boxShadow: "0 4px 10px 0 transparent" + }, + border2x: { + border: '2px solid' + }, + btnStyle: { + fontSize: "1rem", + fontWeight: 600, + } +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/Logo/index.js b/web/src/components/Logo/index.js new file mode 100644 index 000000000..d8dd68aef --- /dev/null +++ b/web/src/components/Logo/index.js @@ -0,0 +1,16 @@ +import { Box, Typography } from "@material-ui/core"; + +import useStyles from "./styles"; + +function Logo() { + const classes = useStyles(); + + return ( + + Poke + Store + + ) +} + +export default Logo; \ No newline at end of file diff --git a/web/src/components/Logo/styles.js b/web/src/components/Logo/styles.js new file mode 100644 index 000000000..f04d2ff1f --- /dev/null +++ b/web/src/components/Logo/styles.js @@ -0,0 +1,10 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + font: { + fontSize: "2rem", + fontWeight: 500, + }, +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/components/TitleSecondary/index.js b/web/src/components/TitleSecondary/index.js new file mode 100644 index 000000000..8d4d958e6 --- /dev/null +++ b/web/src/components/TitleSecondary/index.js @@ -0,0 +1,20 @@ +import { Typography } from "@material-ui/core"; + +import useStyles from "./styles"; + +function TitleSecondary({ label, color = '#e68b04'}) { + const classes = useStyles(); + + return ( + + { label } + + ) +} + +export default TitleSecondary; \ No newline at end of file diff --git a/web/src/components/TitleSecondary/styles.js b/web/src/components/TitleSecondary/styles.js new file mode 100644 index 000000000..49d8bc123 --- /dev/null +++ b/web/src/components/TitleSecondary/styles.js @@ -0,0 +1,12 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + title: { + fontSize: '3rem', + fontWeight: 600, + marginBottom: '3rem', + textAlign: 'center' + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/contexts/FilterPokemonsContext/index.js b/web/src/contexts/FilterPokemonsContext/index.js new file mode 100644 index 000000000..0a174b9f9 --- /dev/null +++ b/web/src/contexts/FilterPokemonsContext/index.js @@ -0,0 +1,28 @@ +import { createContext, useState } from "react"; + +export const FilterPokemonsContext = createContext(null); + +export function FilterPokemonsContextProvieder({ children }) { + const [ openDialogFilter, setOpenDialogFilter ] = useState(false); + const [ queries, setQueries ] = useState(''); + + const handleOpenDialogFilter = () => { + setOpenDialogFilter(true); + }; + + const handleCloseDialogFilter = () => { + setOpenDialogFilter(false); + }; + + return ( + + { children } + + ) +} \ No newline at end of file diff --git a/web/src/hooks/useTokenStore.js b/web/src/hooks/useTokenStore.js new file mode 100644 index 000000000..d9799eabe --- /dev/null +++ b/web/src/hooks/useTokenStore.js @@ -0,0 +1,21 @@ +function useTokenStore() { + const TOKEN_KEY = "@pokeStoreToken"; + + const isAuthenticated = () => localStorage.getItem(TOKEN_KEY) !== null; + + const getUserToken = () => localStorage.getItem(TOKEN_KEY); + + const userLogin = token => localStorage.setItem(TOKEN_KEY, token); + + const userLogout = () => localStorage.removeItem(TOKEN_KEY); + + return { + TOKEN_KEY, + isAuthenticated, + getUserToken, + userLogin, + userLogout, + } +} + +export default useTokenStore; \ No newline at end of file diff --git a/web/src/index.js b/web/src/index.js new file mode 100644 index 000000000..dd1c38f90 --- /dev/null +++ b/web/src/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/web/src/pages/DashBoard/index.js b/web/src/pages/DashBoard/index.js new file mode 100644 index 000000000..2f5bd4527 --- /dev/null +++ b/web/src/pages/DashBoard/index.js @@ -0,0 +1,136 @@ +import { Box, Grid, Typography } from "@material-ui/core"; +import Pagination from '@material-ui/lab/Pagination' +import { useContext, useEffect, useState } from "react"; +import { useHistory } from "react-router-dom"; +import { toast } from "react-toastify"; +import CardPokemon from "../../components/CardPokemon"; + +import DashboardWrapper from "../../components/DashBoardWrapper"; +import FilterPokemons from "../../components/FilterPokemons"; +import { FilterPokemonsContext } from "../../contexts/FilterPokemonsContext"; + +import useTokenStore from "../../hooks/useTokenStore"; +import API from "../../services/api"; +import getPageValue from "../../utils/getMaterialUiPageValue"; +import useStyles from "./styles"; + +function DashBoard() { + const classes = useStyles(); + const { queries } = useContext(FilterPokemonsContext); + + const history = useHistory(); + const [ pokemons, setPokemons ] = useState([]); + const [ pages, setPages ] = useState(0); + + const { getUserToken } = useTokenStore(); + const token = getUserToken(); + + async function getPokemonsData(page, withPage = false) { + const { data } = !withPage + ? await API.get(`/session/pokemons?${queries}`, { headers: { Authorization: `Bearer ${token}`}}) + : await API.get(`/session/pokemons?page=${page}&${queries}`, { headers: { Authorization: `Bearer ${token}`}}); + + const { totalPages, pokemons } = data.pokemons; + + setPokemons(pokemons); + setPages(totalPages); + } + + async function handlePagination(event) { + const page = getPageValue(event.target.getAttribute("aria-label")); + await getPokemonsData(page, true); + } + + async function handleDeletePokemon(id) { + try { + const response = await API.delete(`/session/drop/${id}`, { headers: { Authorization: `Bearer ${token}`}}); + await getPokemonsData(); + + if (response.status === 200) { + toast.success('Pokemon removido com sucesso!'); + } + } catch (err) { + console.log(err); + toast.error('Houve um erro inesperado.'); + } + } + + function handleUpdatePokemonImage(id, name) { + history.push({ + pathname: '/updateimage', + state: { id, name } + }); + } + + function handleUpdatePokemon(id, name) { + history.push({ + pathname: '/updatepokemon', + state: { id, name } + }); + } + + function handleViewPokemon(id, name) { + history.push({ + pathname: '/viewpokemon', + state: { id, name } + }); + } + + useEffect(() => { + (async () => { + await getPokemonsData(); + })() + }, []) + + useEffect(() => { + (async () => { + await getPokemonsData(); + })() + }, [queries]) + + return ( + + +
+ + { pokemons.length > 0 ? ( + pokemons.map((pokemon, index) => ( + + + await handleDeletePokemon(pokemon.pokemon_number)} + handleUpdateImage={() => handleUpdatePokemonImage(pokemon.pokemon_number, pokemon.name)} + handleUpdatePokemon={() => handleUpdatePokemon(pokemon.pokemon_number, pokemon.name)} + handleViewPokemon={() => handleViewPokemon(pokemon.pokemon_number, pokemon.name)} + /> + + + ))) : ( + + + Ainda não há pokemons cadastrados. + + + )} + + + { pokemons.length > 0 && ( + + )} +
+
+ ) +} + +export default DashBoard; \ No newline at end of file diff --git a/web/src/pages/DashBoard/styles.js b/web/src/pages/DashBoard/styles.js new file mode 100644 index 000000000..6a8e6b349 --- /dev/null +++ b/web/src/pages/DashBoard/styles.js @@ -0,0 +1,19 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + pagination: { + maxWidth: '380px', + margin: '3rem auto', + padding: '10px 0', + borderRadius: '5px', + backgroundColor: theme.palette.white.main, + color: theme.palette.white.main, + }, + titleNotPoke: { + fontWeight: 600, + textAlign: 'center', + fontSize: '3rem' + }, +})); + +export default useStyles; \ No newline at end of file diff --git a/web/src/pages/DashBoardCreatePokemon/index.js b/web/src/pages/DashBoardCreatePokemon/index.js new file mode 100644 index 000000000..be6883f17 --- /dev/null +++ b/web/src/pages/DashBoardCreatePokemon/index.js @@ -0,0 +1,179 @@ +import { Box, Grid } from "@material-ui/core"; +import { useState } from "react"; +import { useHistory } from "react-router-dom"; +import { toast } from "react-toastify"; + +import ContainerComponent from "../../components/Container"; +import FormCreatePokemon from "../../components/FormCreatePokemon"; +import ButtonCreatePokemon from "../../components/FormCreatePokemon/Button"; +import InputCreatePokemon from "../../components/FormCreatePokemon/Input"; +import RadioCreatePokemon from "../../components/FormCreatePokemon/Radio"; +import GoBackDashBoardHeader from "../../components/GoBackDashBoardHeader"; +import TitleSecondary from "../../components/TitleSecondary"; +import useTokenStore from "../../hooks/useTokenStore"; +import API from "../../services/api"; +import handlerFile from "../../utils/handlerFile"; +import { validatePokemonData } from "../../utils/isValidData"; +import useStyles from "./styles"; + +function DashBoardCreatePokemon() { + const history = useHistory(); + + // string + const [ image, setImage ] = useState(null); + const [ name, setName ] = useState(''); + const [ typeOne, setTypeOne ] = useState(''); + const [ typeTwo, setTypeTwo ] = useState(''); + const [ weatherOne, setWeatherOne ] = useState(''); + const [ weatherTwo, setWeatherTwo ] = useState(''); + // number + const [ generation, setGeneration ] = useState(null); + const [ evolutionStage, setEvolutionStage ] = useState(null); + const [ familyId, setFamilyId ] = useState(null); + const [ atk, setAtk ] = useState(null); + const [ def, setDef ] = useState(null); + const [ stat, setStat ] = useState(null); + const [ raidable, setRaidable ] = useState(null); + const [ hatchable, setHatchable ] = useState(null); + // booleans + const [ evolved, setEvolved ] = useState(true); + const [ crossGender, setCrossGender ] = useState(true); + const [ lengendary, setLengendary ] = useState(true); + const [ acquirable, setAcquirable ] = useState(true); + const [ spawns, setSpawns ] = useState(true); + const [ regional, setRegional ] = useState(true); + const [ shiny, setShiny ] = useState(true); + const [ nest, setNest ] = useState(true); + const [ newField, setNewField ] = useState(true); + const [ notGettable, setNotGettable ] = useState(true); + const [ futureEvolve, setFutureEvolve ] = useState(true); + + const classes = useStyles(); + + const { getUserToken } = useTokenStore(); + const token = getUserToken(); + + async function handlerCreatePokemon() { + const data = new FormData(); + + data.append('image', image); + data.append('name', name); + data.append('typeOne', typeOne); + data.append('typeTwo', typeTwo); + data.append('weatherOne', weatherOne); + data.append('weatherTwo', weatherTwo); + data.append('generation', generation); + data.append('evolutionStage', evolutionStage); + data.append('familyId', familyId); + data.append('atk', atk); + data.append('def', def); + data.append('stat', stat); + data.append('raidable', raidable); + data.append('hatchable', hatchable); + + data.append('evolved', evolved); + data.append('crossGender', crossGender); + data.append('lengendary', lengendary); + data.append('acquirable', acquirable); + data.append('spawns', spawns); + data.append('nest', nest); + data.append('shiny', shiny); + data.append('regional', regional); + data.append('newField', newField); + data.append('notGettable', notGettable); + data.append('futureEvolve', futureEvolve); + + const priorityValues = [name, atk, evolutionStage, familyId, def, stat, raidable, hatchable, generation]; + + if (validatePokemonData(priorityValues)) { + try { + const response = await API.post('/session/store', data, + { headers: { Authorization: `Bearer ${token}`, + 'content-type': 'multipart/form-data' }}); + + if (response.status === 201) { + toast.success('Pokemon criado com sucess'); + history.push('/dashboard'); + } + } catch(err) { + toast.info('Pokemon com este nome já existe!'); + } + } + } + + function handlerImage(e) { + setImage(e.target.files[0]); + handlerFile(e, '#imageFile'); + } + + return ( + <> + + + + + + +
+ + + + setName(e.target.value)}/> + setTypeOne(e.target.value)}/> + setTypeTwo(e.target.value)}/> + setWeatherOne(e.target.value)}/> + setWeatherTwo(e.target.value)}/> + + + + + + setGeneration(e.target.value)}/> + setEvolutionStage(e.target.value)}/> + setFamilyId(e.target.value)}/> + setAtk(e.target.value)}/> + setDef(e.target.value)}/> + + + + + setStat(e.target.value)}/> + setRaidable(e.target.value)}/> + setHatchable(e.target.value)}/> + + setEvolved(!evolved)} ariaLabel="evolved" name="evolved"/> + + + + + setCrossGender(!crossGender)} ariaLabel="crossGender" name="crossGender"/> + setLengendary(!lengendary)} ariaLabel="lengendary" name="lengendary"/> + setAcquirable(!acquirable)} ariaLabel="acquirable" name="acquirable"/> + + + + + setShiny(!shiny)} ariaLabel="shiny" name="shiny"/> + setNest(!nest)} ariaLabel="nest" name="nest"/> + setNewField(!newField)} ariaLabel="newField" name="newField"/> + setNotGettable(!notGettable)} ariaLabel="notGettable" name="notGettable"/> + + + + + setSpawns(!spawns)} ariaLabel="spawns" name="spawns"/> + setRegional(!regional)} ariaLabel="regional" name="regional"/> + setFutureEvolve(!futureEvolve)} ariaLabel="futureEvolve" name="futureEvolve"/> + + + + +
+
+
+
+ + ) +} + +export default DashBoardCreatePokemon; \ No newline at end of file diff --git a/web/src/pages/DashBoardCreatePokemon/styles.js b/web/src/pages/DashBoardCreatePokemon/styles.js new file mode 100644 index 000000000..846be9223 --- /dev/null +++ b/web/src/pages/DashBoardCreatePokemon/styles.js @@ -0,0 +1,12 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + root: { + '@media screen and (max-width: 779px)': { + width: '380px', + margin: '0 auto', + } + }, +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/pages/DashBoardUpdateImage/index.js b/web/src/pages/DashBoardUpdateImage/index.js new file mode 100644 index 000000000..62ee43bdc --- /dev/null +++ b/web/src/pages/DashBoardUpdateImage/index.js @@ -0,0 +1,85 @@ +import { Box } from "@material-ui/core"; +import { useEffect, useState } from "react"; +import { toast } from 'react-toastify'; + +import { useHistory, useLocation } from "react-router-dom"; + +import ContainerComponent from "../../components/Container"; +import TitleSecondary from "../../components/TitleSecondary"; + +import API from "../../services/api"; +import useTokenStore from "../../hooks/useTokenStore"; +import InputCreatePokemon from "../../components/FormCreatePokemon/Input"; +import FormCreatePokemon from "../../components/FormCreatePokemon"; +import ButtonCreatePokemon from "../../components/FormCreatePokemon/Button"; +import GoBackDashBoardHeader from "../../components/GoBackDashBoardHeader"; +import handlerFile from "../../utils/handlerFile"; + +function DashBoardUpdateImage() { + const location = useLocation(); + const history = useHistory(); + const { getUserToken } = useTokenStore(); + + const [ image, setImage ] = useState(''); + const [ pokemonId, setPokemonId ] = useState(null); + const [ pokemonName, setPokemonName ] = useState(''); + + useEffect(() => { + (async () => { + if (!location.state) { + history.push('/dashboard'); + return; + } else { + setPokemonId(location.state.id); + setPokemonName(location.state.name); + } + })() + }, []); + + const token = getUserToken(); + + async function handlerUpdateImage() { + if (image) { + const data = new FormData(); + data.append('image', image); + + try { + const response = await API.put(`/session/newimage/${pokemonId}`, data, + { headers: { Authorization: `Bearer ${token}`, + 'content-type': 'multipart/form-data' }}); + + if (response.status === 200) { + toast.success('Imagem atualizada com sucesso!'); + } + } catch (err) { + toast.info('Houve um problema inesperado, tente novamente.'); + } + } + } + + function handlerImage(e) { + setImage(e.target.files[0]); + handlerFile(e, '#imageFile'); + } + + return ( + <> + + + + + + + + + + + + + + + + ) +} + +export default DashBoardUpdateImage; \ No newline at end of file diff --git a/web/src/pages/DashBoardUpdatePokemon/index.js b/web/src/pages/DashBoardUpdatePokemon/index.js new file mode 100644 index 000000000..c97d2fa28 --- /dev/null +++ b/web/src/pages/DashBoardUpdatePokemon/index.js @@ -0,0 +1,124 @@ +import { Box } from "@material-ui/core"; +import { useEffect, useState } from "react"; +import { toast } from 'react-toastify'; +import { useHistory, useLocation } from "react-router-dom"; + +import ContainerComponent from "../../components/Container"; +import TitleSecondary from "../../components/TitleSecondary"; + +import API from "../../services/api"; +import useTokenStore from "../../hooks/useTokenStore"; +import InputCreatePokemon from "../../components/FormCreatePokemon/Input"; +import FormCreatePokemon from "../../components/FormCreatePokemon"; +import ButtonCreatePokemon from "../../components/FormCreatePokemon/Button"; +import GoBackDashBoardHeader from "../../components/GoBackDashBoardHeader"; +import { validatePokemonData } from "../../utils/isValidData"; + +function DashBoardUpdatePokemon() { + const location = useLocation(); + const history = useHistory(); + + const { getUserToken } = useTokenStore(); + + const [ pokemonId, setPokemonId ] = useState(null); + const [ pokemonName, setPokemonName ] = useState(''); + const [ typeOne, setTypeOne ] = useState(''); + const [ typeTwo, setTypeTwo ] = useState(''); + const [ weatherOne, setWeatherOne ] = useState(''); + const [ weatherTwo, setWeatherTwo ] = useState(''); + const [ generation, setGeneration ] = useState(null); + const [ evolutionStage, setEvolutionStage ] = useState(null); + const [ atk, setAtk ] = useState(null); + const [ def, setDef ] = useState(null); + const [ stat, setStat ] = useState(null); + + const [ defaultData, setDefaultData ] = useState({}); + + useEffect(() => { + (async () => { + if (!location.state) { + history.push('/dashboard'); + return; + } else { + setPokemonId(location.state.id); + setPokemonName(location.state.name); + await getPokemonData(); + } + })() + }, []); + + const token = getUserToken(); + + function deleteNotUsedData(data, keys) { + keys.forEach(key => delete data[key]); + return data; + } + + async function getPokemonData() { + const { data } = await API.get(`/session/pokemon/${pokemonId}`, + { headers: { Authorization: `Bearer ${token}`}}); + + const result = data.pokemon[0]; + + const notUsedData = [ + 'type_one', 'type_two', 'weather_one', + 'weather_two', 'generation', 'evolution_stage', + 'atk', 'def', 'stat', 'image_name' + ]; + + const newData = deleteNotUsedData(result, notUsedData); + setDefaultData(newData); + } + + async function handleUpdateImage() { + const data = { + typeOne, typeTwo, atk, evolutionStage, def, + stat, weatherOne, generation, weatherTwo, + ...defaultData, + }; + + const priorityValues = [ typeOne, typeTwo, atk, evolutionStage, def, stat, weatherOne, generation, weatherTwo]; + + if (validatePokemonData(priorityValues)) { + try { + const response = await API.put(`session/update/${pokemonId}`, data, + { headers: { Authorization: `Bearer ${token}`}}); + + if (response.status === 200) { + toast.success('Pokemon atualizado com sucess'); + } + } catch(err) { + toast.info('Houve um erro inesperado na atualização, tente novamente.'); + } + } + } + + return ( + <> + + + + + + + + setGeneration(e.target.value)}/> + setEvolutionStage(e.target.value)}/> + setAtk(e.target.value)}/> + setDef(e.target.value)}/> + setStat(e.target.value)}/> + setTypeOne(e.target.value)}/> + setTypeTwo(e.target.value)}/> + setWeatherOne(e.target.value)}/> + setWeatherTwo(e.target.value)}/> + + + + + + + + ) +} + +export default DashBoardUpdatePokemon; \ No newline at end of file diff --git a/web/src/pages/DashBoardViewPokemon/index.js b/web/src/pages/DashBoardViewPokemon/index.js new file mode 100644 index 000000000..b263e658f --- /dev/null +++ b/web/src/pages/DashBoardViewPokemon/index.js @@ -0,0 +1,56 @@ +import { Box, Grid } from "@material-ui/core"; +import { useEffect, useState } from "react"; + +import ContainerComponent from "../../components/Container"; + +import API from "../../services/api"; +import useTokenStore from "../../hooks/useTokenStore"; +import GoBackDashBoardHeader from "../../components/GoBackDashBoardHeader"; +import { useLocation } from "react-router-dom"; +import CardViewPokemon from "../../components/CardViewPokemon"; + +function DashBoardViewPokemon() { + const location = useLocation(); + const { getUserToken } = useTokenStore(); + const [ pokemon, setPokemon ] = useState([]); + + const pokemonId = location.state.id; + + const pokemonName = location.state.name; + const token = getUserToken(); + + useEffect(() => { + (async () => { + const { data } = await API.get(`/session/pokemon/${pokemonId}`, + { headers: { Authorization: `Bearer ${token}`}}); + const { pokemon } = data; + setPokemon(pokemon); + })() + }, []) + + return ( + <> + + + +
+ + {pokemon.length > 0 && ( + + + + + + )} + +
+
+
+ + ) +} + +export default DashBoardViewPokemon; \ No newline at end of file diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js new file mode 100644 index 000000000..c996cec62 --- /dev/null +++ b/web/src/pages/Home/index.js @@ -0,0 +1,49 @@ +import { Box, Button, Grid, Typography } from '@material-ui/core'; +import { Link } from 'react-router-dom'; + +import bgImg from "../../assets/images/bgPokemon.png"; + +import ContainerComponent from '../../components/Container'; +import HomeHeader from '../../components/HomeHeader'; +import useStyles from './styles'; + +function Home() { + const classes = useStyles(); + + return ( + <> + + + + + + + + PokeStore registre seus pokemons favoritos! + + + + Com o PokeStore você poderá salvar os dados de todos os pokemons que você conhece! além de poder gerenciar de um jeito simples e intuitivo. + + + + + + + Pokemon + + + + + + + + ) +} + +export default Home; \ No newline at end of file diff --git a/web/src/pages/Home/styles.js b/web/src/pages/Home/styles.js new file mode 100644 index 000000000..c9c3caa19 --- /dev/null +++ b/web/src/pages/Home/styles.js @@ -0,0 +1,30 @@ +import { makeStyles } from "@material-ui/core"; + +const useStyles = makeStyles(theme => ({ + fontTitleMain: { + fontSize: "2.5rem", + fontWeight: 600, + }, + text: { + fontSize: "1.25rem", + color: "#A1A1AA", + margin: "40px 0" + }, + bgImgStyles: { + maxWidth: "100%", + maxHeight: "100%", + height: "100%", + objectFit: "cover", + }, + btn: { + padding: '12px', + width: '180px', + marginBottom: "40px", + }, + btnStyles: { + fontSize: "1.125rem", + fontWeight: 600, + } +})) + +export default useStyles; \ No newline at end of file diff --git a/web/src/pages/LoginPage/index.js b/web/src/pages/LoginPage/index.js new file mode 100644 index 000000000..8be25294d --- /dev/null +++ b/web/src/pages/LoginPage/index.js @@ -0,0 +1,61 @@ +import { Box } from "@material-ui/core"; +import { useState } from "react"; +import { toast } from 'react-toastify'; + +import AuthForm from "../../components/AuthForm"; +import Input from "../../components/AuthForm/Input"; +import Button from "../../components/AuthForm/Button"; +import ContainerComponent from "../../components/Container"; +import TitleSecondary from "../../components/TitleSecondary"; +import HomeHeader from "../../components/HomeHeader"; + +import API from "../../services/api"; +import useTokenStore from "../../hooks/useTokenStore"; +import { useHistory } from "react-router-dom"; +import { isValidDataLogin } from "../../utils/isValidData"; + +function LoginPage() { + const { userLogin } = useTokenStore(); + + let history = useHistory(); + const [ email, setEmail ] = useState(''); + const [ password, setPassword ] = useState(''); + + async function handlerLoginClick() { + if (isValidDataLogin(email, password)) { + try { + const response = await API.post('/login', { email, password }); + + if (response.status === 200) { + toast.success('Bem vindo de volta!'); + userLogin(response.data.token); + history.push('/dashboard'); + } + } catch (err) { + toast.info('Dados incorretos, tente novamente!'); + } + } + } + + return ( + <> + + + + + + + + setEmail(e.target.value)}/> + setPassword(e.target.value)}/> +