diff --git a/.gitignore b/.gitignore index 03a24d8d..c81546b0 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,8 @@ dist # package-lock (main is yarn.lock) package-lock.json + +# Arquivo de configuração de backup remoto +rclone.conf + +miaajuda.pem \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f4d9d3b..ae034ee4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,7 @@ build: IMAGE_NAME: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" before_script: - cat $FIREBASE_CONFIG > ./src/config/firebaseAuthConfig.js + - cat $RCLONE_CONFIG > ./config/rclone.conf script: - echo "Building image" - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY @@ -40,6 +41,7 @@ build stable: IMAGE_NAME: "$CI_REGISTRY_IMAGE:stable" before_script: - cat $FIREBASE_CONFIG > ./src/config/firebaseAuthConfig.js + - cat $RCLONE_CONFIG > ./config/rclone.conf script: - echo "Building image" - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e939734f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,101 @@ +# Guia de Contribuição :smile: + +Bem vindo ao Mia Ajuda! + +Adoramos quando novas pessoas contribuem com o projeto. Queremos que a sua contribuição para o Mia Ajuda se torne a mais simples possível. Todas as ajudas ao projeto são bem vindas, seja: + +* Reportando _bugs_ encontrados; +* Enviando correção de _bugs_; +* Propondo novas soluções para o projeto, seja: Visual, Arquitetural ou de Negócio; +* Propondo novas funcionalidades; +* Implementado novas funcionalidades previstas em _issues_ nos nossos repositórios. + +Caso queira conhecer melhor nosso projeto, acesse o nosso [site](https://miaajuda.netlify.app/), nosso [Instagram](https://www.instagram.com/miaajuda/) ou a nossa [Organização no Github](https://github.com/mia-ajuda). + +Para entrar em contato conosco, além de abrir uma _issue_ aqui no Github, você pode nos enviar um email, para: miaajudadev@gmail.com + +## Como Iniciar a sua Contribuição ao Mia Ajuda + +Muito Obrigado pelo interesse em contribuir para o Projeto. + +Para iniciar a sua jornada, você pode estar contribuindo para o projeto abrindo _issues_ em nosso repositório de documentação [repositório](https://github.com/mia-ajuda/Documentation/issues), seguindo o nosso [template](https://github.com/mia-ajuda/Documentation/tree/master/.github/ISSUE_TEMPLATE). Essas _issues_ podem ser abertas reportando possíveis _bugs_ ou sugerindo novas funcionalidades para o projeto. + +Caso você queira contribuir para o código do Mia Ajuda, basta seguir os próximos passos: + +* Busque a _issue_ na qual você se identifica, se marque e comente nessa _issue_. Atenção: Certifique-se antes, de que a _issue_ não está sendo resolvida por alguém, antes; +* Faça um _fork_ dos nossos repositórios, se você for um contribuidor externo; +* Crie uma _branch_ a partir da develop, seguindo nossas políticas de _branch_ abaixo; +* Crie um _Pull Request_ com o status _WIP_, no repositório para nos certificarmos que você está trabalhando na sua _issue_; +* Ao gerar _commits_, siga a nossa política de _commits_; +* Ao concluir o desenvolvimento da _issue_, troque o status do seu _Pull Request_ de _WIP_ para _Solve_, seguindo o nosso [template de Pull Request](https://github.com/mia-ajuda/Backend/blob/develop/.github/pull_request_template.md); +* Após um revisor aprovar o seu _Pull Request_, mescle-o com a a _branch_ base, seguindo a política do [_Squash Rebase_](https://docs.github.com/pt/github/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits); + +## _Workflow_ de Trabalho + +Todo o nosso _workflow_ de trabalho é inteiramente baseado no [_GitFlow_](https://www.atlassian.com/br/git/tutorials/comparing-workflows/gitflow-workflow). + +## Politicas de _Branches_ + +As _branches_ são dividas em camadas de desenvolvimento, baseado do modelo do [_GitFlow_](https://www.atlassian.com/br/git/tutorials/comparing-workflows/gitflow-workflow), sendo a `main` a camada que contém a aplicação em sua versão estável, a `develop` a versão de estado em desenvolvimento. Para a criação de `feature` _branches_ utilize a `develop` como base. + +O formato para os nomes das _feature_ _branches_ será composto por: + +US + NUMERO_DA_US + FUNCIONALIDADE. + +Exemplo: +``` +US13-Creation_of_a_new_screen +``` + +Para _hotfix branches_, o formato do nome da _branch_ se dará pela seguinte forma: + +HOTFIX + NOME_DA_FIX + +Exemplo: +``` +hotfix_login_bug +``` + +### Mantendo as _branches_ atualizadas + +Mantenha as suas _branches_ atualizadas com a _branch_ base. Utilize o comando _rebase_ para isso. + +Exemplo: + +``` +> git pull --rebase origin develop +``` + +## Política de _Commits_ + +Os nossos _commits_ possuem um [_lint_](https://github.com/legend80s/commit-msg-linter#readme), sendo obrigatório seguir esse padrão: + +``` +tipo do commit: descrição concisa e em inglês do commit +``` + +Exemplo: + +``` +git commit -m "feat: create login button" +``` + +As nossas regras são: + +* _Commits_ devem ser redigidos em idioma inglês; +* Devem seguir as regras do [_lint_](https://github.com/legend80s/commit-msg-linter#readme); +* Devem ser simples e concisos, possuindo títulos curtos; +* Devem iniciar com verbo no infinitivo informando o objetivo. + +### _Commits_ em equipes + +Caso mais de uma pessoa tenha trabalhado com você no _commit_, utilize do _Co-authored-by_, na descrição do _commit_. + +Exemplo: + +``` +fix: fix contacts modal + + +Co-authored-by: Link +``` diff --git a/README.md b/README.md index 5311b2c2..a4eb1ffa 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,15 @@

- - - - + + + +

## Rode o Backend com Docker + ### Dependências Inicialmente, instale localmente as seguintes dependências: @@ -52,9 +53,13 @@ LONGITUDE_ENV= SENTRY_DSN= NODE_ENV=development DATABASE_URL=mongodb://mongo/miaAjudaDB +MONGODB_USERNAME= +MONGODB_PASSWORD= ``` -* O preenchimento do serviço de monitoramento de erros ([Sentry](https://sentry.io/)) é opcional. A latitude e a longitude serão utilizadas para popular exemplos de pedido de ajuda próximos a essa coordenada. +* O preenchimento do serviço de monitoramento de erros ([Sentry](https://sentry.io/)) é opcional. +* A latitude e a longitude serão utilizadas para popular exemplos de pedido de ajuda próximos a essa coordenada. +* As variáveis de ambiente `MONGODB_USERNAME` e `MONGODB_PASSWORD` destinam-se ao acesso da base de dados com autenticação. Ver detalhes de configuração de backup abaixo. ### Inicialização do Projeto @@ -72,3 +77,25 @@ sudo docker-compose -f docker-compose.yml up --build 2. Na raiz do projeto, verifique a corretude do código com `eslint . --ext .js`; ou 3. Configure uma extensão no seu editor de texto preferido (exemplo: [VSCode - ESLINT](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)); 4. Abra o seu editor de texto na raiz do projeto `/Backend` e comece a desenvolver. + +### Backup da Base de Dados + +A aplicação realiza backups da base "miaAjudaDB" regularmente às 04h da manhã. O serviço guarda um total de 14 backups locais e sempre salva o backup no Google Drive. Para isso ser possível, proceda com as instruções: + +#### Configuração do Backup + +1. Preencha adequadamente as variáveis de ambiente do tópico "Arquivos de Configuração"; +2. Salve na pasta `/Backend/config/` o arquivo `rclone.conf`; +3. Em produção, inicie os serviços de backend, banco e backup com: + +```sh +sudo docker-compose -f docker-compose.yml -f docker-compose.prod.yml up +``` + +#### Restauração do Backup + +1. Para realizar a restauração de um backup, proceda com o comando: + +```sh +mongorestore --gzip --archive=backup-scheduler-1594607580.gz --drop +``` diff --git a/config/backup-scheduler.yaml b/config/backup-scheduler.yaml new file mode 100644 index 00000000..80f06998 --- /dev/null +++ b/config/backup-scheduler.yaml @@ -0,0 +1,24 @@ +scheduler: + # run every day at 4:00 UTC-3 + cron: "0 7 */1 * *" + # number of backups to keep locally + retention: 14 + # backup operation timeout in minutes + timeout: 60 +target: + # mongod IP or host name + host: "mongo" + # mongodb port + port: 27017 + # mongodb database name, leave blank to backup all databases + database: "miaAjudaDB" + # leave blank if auth is not enabled + username: ${MONGODB_USERNAME} + password: ${MONGODB_PASSWORD} + # add custom params to mongodump (eg. Auth or SSL support), leave blank if not needed + params: "--authenticationDatabase admin" # "--ssl" +rclone: + bucket: "MiaAjuda-Rclone" + # See https://rclone.org/docs/ for details on how to configure rclone + configFilePath: /config/rclone.conf + configSection: "MiaAjuda-Rclone" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..da9e7594 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,16 @@ +version: "3" +services: + mgob: + container_name: mgob + environment: + - MONGODB_USERNAME=${MONGODB_USERNAME} + - MONGODB_PASSWORD=${MONGODB_PASSWORD} + image: stefanprodan/mgob:edge + command: ./mgob -LogLevel=info + volumes: + - ./config:/config + - /mgob/storage:/storage + - /mgob/tmp:/tmp + - /mgob/data:/data + depends_on: + - mongo diff --git a/package.json b/package.json index a3fc6866..d32d3dbb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@sentry/node": "5.18.0", - "bcrypt": "^4.0.1", + "bcrypt": "^5.0.0", "body-parser": "^1.19.0", "cors": "^2.8.5", "cpf-cnpj-validator": "^1.0.1", @@ -20,11 +20,11 @@ "express": "^4.17.1", "faker": "^4.1.0", "firebase-admin": "^8.10.0", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "mongodb": "^3.5.9", "mongoose": "^5.9.6", "node-schedule": "^1.3.2", - "socket.io": "^2.3.0", + "socket.io": "^2.4.0", "swagger-ui-express": "^4.1.4", "yamljs": "^0.3.0" }, @@ -32,6 +32,7 @@ "eslint": "^6.8.0", "eslint-config-airbnb-base": "^14.1.0", "eslint-plugin-import": "^2.20.2", + "git-commit-msg-linter": "^3.2.6", "nodemon": "^2.0.2" } } diff --git a/src/config/database.js b/src/config/database.js index 9afd0b35..04f9fc63 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -9,12 +9,9 @@ const envType = process.env.NODE_ENV || 'development'; const databaseConnect = async () => { try { - await mongoose.connect(databaseURL, { useNewUrlParser: true, useUnifiedTopology: true }) - .then(() => console.log('Banco de dados conectado!')) - .catch((err) => { - console.log('Não foi possível se conectar ao banco de dados!'); - console.log(err); - }); + await mongoose.connect(databaseURL, { useNewUrlParser: true, useUnifiedTopology: true }); + console.log('Banco de dados conectado!'); + mongoose.set('useFindAndModify', false); await CategorySeed(); // só popula usuários e ajudas falsos em desenvolvimento diff --git a/src/controllers/CampaignController.js b/src/controllers/CampaignController.js new file mode 100644 index 00000000..8bba2160 --- /dev/null +++ b/src/controllers/CampaignController.js @@ -0,0 +1,141 @@ +const CampaignService = require('../services/CampaignService'); +const saveError = require('../utils/ErrorHistory'); + +class CampaignController { + constructor() { + this.CampaignService = new CampaignService(); + } + + async createCampaign(req, res) { + try { + const newCampaign = await this.CampaignService.createNewCampaign( + req.body, + ); + return res.json(newCampaign); + } catch (error) { + return res.status(400).json({ error: error.message }); + } + } + + async listCampaign(req, res) { + try { + const campaign = await this.CampaignService.listCampaign(); + return res.json(campaign); + } catch (error) { + return res.status(400).json(error); + } + } + + async getCampaignListByStatus(req, res, next) { + const { userId } = req.params; + + const statusList = req.query.statusList.split(','); + + try { + const result = await this.CampaignService.getCampaignListByStatus({ + userId, + statusList, + }); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async listCampaignByOwnerId(req, res) { + const { ownerId } = req.params; + try { + const campaign = await this.CampaignService.listCampaignByOwnerId( + ownerId, + ); + return res.json(campaign); + } catch (error) { + return res.status(400).json(error); + } + } + + async deleteCampaignLogic(req, res, next) { + const { id } = req.params; + try { + const result = await this.CampaignService.deleteCampaign(id); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async listCampaignNear(req, res, next) { + const except = !!req.query['id.except']; + const helper = !!req.query['id.helper']; + let temp = null; + if (except) { + temp = 'except'; + } else if (helper) { + temp = 'helper'; + } + + const id = temp ? req.query[`id.${temp}`] : req.query.id; + const categoryArray = req.query.categoryId + ? req.query.categoryId.split(',') + : null; + + const near = !!req.query.near; + const coords = near + ? req.query.coords.split(',').map((coord) => Number(coord)) + : null; + + try { + let result; + if (near) { + result = await this.CampaignService.getNearCampaignList( + coords, + except, + id, + categoryArray, + ); + } + res.status(200); + res.json(result); + next(); + } catch (err) { + console.log(err); + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async finishCampaign(req, res, next) { + const { id } = req.params; + try { + const result = await this.CampaignService.finishCampaign(id); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async getCampaignById(req, res, next) { + const { id } = req.params; + try { + const result = await this.CampaignService.getCampaignById(id); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } +} + +module.exports = CampaignController; diff --git a/src/controllers/EntityController.js b/src/controllers/EntityController.js new file mode 100644 index 00000000..19bab5cc --- /dev/null +++ b/src/controllers/EntityController.js @@ -0,0 +1,143 @@ +const EntityService = require('../services/EntityService'); +const saveError = require('../utils/ErrorHistory'); + +class EntityController { + constructor() { + this.entityService = new EntityService(); + } + + async createEntity(req, res, next) { + const { latitude, longitude } = req.body; + + const location = { + type: 'Point', + coordinates: [longitude, latitude], + }; + + const data = { + location, + ...req.body, + hasEntity: req.query.hasEntity === 'true', + }; + + try { + const result = await this.entityService.createEntity(data); + res.status(201).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async editEntityById(req, res, next) { + const data = { + email: req.decodedToken.email, + photo: req.body.photo, + name: req.body.name, + phone: req.body.phone, + notificationToken: req.body.notificationToken, + deviceId: req.body.deviceId, + }; + try { + const result = await this.entityService.editEntityById(data); + res.status(200).json(result); + return next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + return next(); + } + } + + async editEntityAddressById(req, res, next) { + const data = { + email: req.decodedToken.email, + cep: req.body.cep, + number: req.body.number, + city: req.body.city, + state: req.body.state, + complement: req.body.complement, + }; + + try { + const result = await this.entityService.editEntityAddressById(data); + res.status(200).json(result); + return next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + return next(); + } + } + + async deleteEntityLogic(req, res, next) { + const { email } = req.decodedToken; + + try { + const result = await this.entityService.deleteEntityLogically(email); + res.status(200).json(result); + return next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + return next(); + } + } + + async getEntityById(req, res, next) { + const data = { + id: req.params.id, + email: req.decodedToken.email, + }; + + try { + const result = await this.entityService.getEntity(data); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(404).json({ error: err.message }); + next(); + } + } + + async updateEntityLocationById(req, res, next) { + const data = { + email: req.decodedToken.email, + latitude: req.body.latitude, + longitude: req.body.longitude, + }; + + try { + const result = await this.entityService.updateEntityLocationById(data); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + + async checkEntityExistence(req, res, next) { + let { entityIdentifier } = req.params; + + if (!entityIdentifier) { + entityIdentifier = req.decodedToken.email; + } + + try { + const result = await this.entityService.checkEntityExistence(entityIdentifier); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(404).json({ error: err.message }); + next(); + } + } +} + +module.exports = EntityController; diff --git a/src/controllers/HelpController.js b/src/controllers/HelpController.js index c33ae77a..310b5737 100644 --- a/src/controllers/HelpController.js +++ b/src/controllers/HelpController.js @@ -14,21 +14,22 @@ class HelpController { }; try { - const result = await this.HelpService.createHelp(data); - res.status(201).json(result); + await this.HelpService.createHelp(data); + res.status(201).send(); next(); } catch (err) { + console.log(err); saveError(err); res.status(400).send({ error: err.message }); next(); } } - async getHelpById(req, res, next) { + async getHelpWithAggregationById(req, res, next) { const { id } = req.params; try { - const result = await this.HelpService.getHelpByid(id); + const result = await this.HelpService.getHelpWithAggregationById(id); res.status(200).json(result); next(); } catch (err) { @@ -39,36 +40,19 @@ class HelpController { } async getHelpList(req, res, next) { - const except = !!req.query['id.except']; - const helper = !!req.query['id.helper']; - const temp = except ? 'except' : helper ? 'helper' : null; - const id = temp ? req.query[`id.${temp}`] : req.query.id; - const status = req.query.status || null; + const { id } = req.query; + const isUserEntity = global.isUserEntity; + const coords = req.query.coords.split(',').map((coord) => Number(coord)); const categoryArray = req.query.categoryId ? req.query.categoryId.split(',') : null; /* A requisição do Query é feita com o formato "34312ID12312,12312ID13213", sendo que não é aceito o formato "34312ID12312, 12312ID13213" com espaço */ - - const near = !!req.query.near; - const coords = near ? req.query.coords.split(',').map((coord) => Number(coord)) : null; - try { - let result; - if (near) { - result = await this.HelpService.getNearHelpList( - coords, - except, - id, - categoryArray, - ); - } else { - result = await this.HelpService.getHelpList( - id, - status, - except, - helper, - categoryArray, - ); - } + const result = await this.HelpService.getHelpList( + coords, + id, + isUserEntity, + categoryArray, + ); res.status(200); res.json(result); next(); @@ -106,8 +90,8 @@ class HelpController { const { id } = req.params; try { - const result = await this.HelpService.deleteHelpLogically(id); - res.status(200).json(result); + await this.HelpService.deleteHelpLogically(id); + res.status(204).send(); next(); } catch (err) { saveError(err); @@ -120,8 +104,8 @@ class HelpController { const data = { ...req.params }; try { - const result = await this.HelpService.helperConfirmation(data); - res.status(200).json(result); + await this.HelpService.helperConfirmation(data); + res.status(204).send(); next(); } catch (err) { saveError(err); @@ -134,8 +118,8 @@ class HelpController { const data = { ...req.params }; try { - const result = await this.HelpService.ownerConfirmation(data); - res.status(200).json(result); + await this.HelpService.ownerConfirmation(data); + res.status(204).send(); next(); } catch (err) { saveError(err); @@ -163,7 +147,7 @@ class HelpController { try { await this.HelpService.addPossibleHelpers(id, idHelper); - res.status(204).json(); + res.status(204).send(); next(); } catch (err) { saveError(err); @@ -184,6 +168,20 @@ class HelpController { next(); } } + + async getHelpInfoById(req, res, next) { + try { + const { helpId } = req.params; + const result = await this.HelpService.getHelpInfoById(helpId); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } + } module.exports = HelpController; diff --git a/src/controllers/HelpOfferController.js b/src/controllers/HelpOfferController.js new file mode 100644 index 00000000..b87df59d --- /dev/null +++ b/src/controllers/HelpOfferController.js @@ -0,0 +1,92 @@ +const HelpOfferService = require("../services/HelpOfferService"); +const saveError = require("../utils/ErrorHistory"); + +class OfferedHelpController { + constructor() { + this.HelpOfferService = new HelpOfferService(); + } + + async createHelpOffer(req, res) { + try { + const newHelpOffer = await this.HelpOfferService.createNewHelpOffer( + req.body + ); + return res.json(newHelpOffer); + } catch (error) { + return res.status(400).json({ error: error.message }); + } + } + + async getHelpWithAggregationById(req, res, next) { + const { id } = req.params; + + try { + const result = await this.HelpOfferService.getHelpOfferWithAggregationById(id); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(400).send({ error: err.message }); + next(); + } + } + + async listHelpsOffers(req, res) { + const userId = req.query.userId; + const getOtherUsers = req.query.getOtherUsers == 'true' ? true : false; + const isUserEntity = global.isUserEntity; + try { + const helpOffers = await this.HelpOfferService.listHelpsOffers(userId, isUserEntity, null, getOtherUsers); + return res.json(helpOffers); + } catch (error) { + return res.status(400).json({ error: error.message }); + } + } + + async listHelpOffersByHelpedUserId(req, res) { + const { helpedUserId } = req.params; + try { + const helpOffers = await this.HelpOfferService.listHelpOffersByHelpedUserId( + helpedUserId + ); + return res.json(helpOffers); + } catch (error) { + return res.status(400).json(error); + } + } + + async addPossibleHelpedUsers(req, res) { + const { helpedId, helpOfferId } = req.params; + try { + await this.HelpOfferService.addPossibleHelpedUsers(helpedId, helpOfferId); + return res.status(204).json(); + } catch (error) { + return res.status(400).json({ error: error.message }); + } + } + + async chooseHelpedUsers(req, res) { + const { helpedId, helpOfferId } = req.params; + try { + await this.HelpOfferService.addHelpedUsers(helpedId, helpOfferId); + return res.status(204).json(); + } catch (error) { + return res.status(400).json({ error: error.message }); + } + } + + async finishHelpOfferByOwner(req, res) { + const { helpOfferId } = req.params; + const { email } = req.decodedToken; + + try { + await this.HelpOfferService.finishHelpOfferByOwner(helpOfferId, email); + return res.status(204).json(); + } catch (error) { + saveError(error); + return res.status(400).send({ error: error.message }); + } + } +} + +module.exports = OfferedHelpController; diff --git a/src/controllers/NotificationController.js b/src/controllers/NotificationController.js index 3ab67641..cd7bb479 100644 --- a/src/controllers/NotificationController.js +++ b/src/controllers/NotificationController.js @@ -19,6 +19,18 @@ class NotificationController { next(); } } + + async sendNotifications(req, res, next) { + const { title, body } = req.body; + try { + const result = await this.notificationService.createAndSendNotifications(title, body); + res.status(200).json(result); + } catch (err) { + saveError(err); + res.status(400).json({ error: err.message }); + next(); + } + } } module.exports = NotificationController; diff --git a/src/controllers/UserController.js b/src/controllers/UserController.js index 19c95816..eeb40ad8 100644 --- a/src/controllers/UserController.js +++ b/src/controllers/UserController.js @@ -1,6 +1,6 @@ -const UserService = require('../services/UserService'); -const { riskGroups } = require('../models/RiskGroup'); -const saveError = require('../utils/ErrorHistory'); +const UserService = require("../services/UserService"); +const { riskGroups } = require("../models/RiskGroup"); +const saveError = require("../utils/ErrorHistory"); class UserController { constructor() { @@ -11,14 +11,14 @@ class UserController { const { latitude, longitude } = req.body; const location = { - type: 'Point', + type: "Point", coordinates: [longitude, latitude], }; const data = { location, ...req.body, - hasUser: req.query.hasUser === 'true', + hasUser: req.query.hasUser === "true", }; try { @@ -92,7 +92,6 @@ class UserController { id: req.params.id, email: req.decodedToken.email, }; - try { const result = await this.userService.getUser(data); res.status(200).json(result); @@ -104,6 +103,21 @@ class UserController { } } + async getAnyUserById(req, res, next) { + const data = { + id: req.params.id, + }; + try { + const result = await this.userService.getAnyUser(data); + res.status(200).json(result); + next(); + } catch (err) { + saveError(err); + res.status(404).json({ error: err.message }); + next(); + } + } + async updateUserLocationById(req, res, next) { const data = { email: req.decodedToken.email, @@ -123,10 +137,14 @@ class UserController { } async checkUserExistence(req, res, next) { - const { value } = req.params; + let { userIdentifier } = req.params; + + if (!userIdentifier) { + userIdentifier = req.decodedToken.email; + } try { - const result = await this.userService.checkUserExistence(value); + const result = await this.userService.checkUserExistence(userIdentifier); res.status(200).json(result); next(); } catch (err) { diff --git a/src/models/Campaign.js b/src/models/Campaign.js new file mode 100644 index 00000000..5081fb6c --- /dev/null +++ b/src/models/Campaign.js @@ -0,0 +1,91 @@ +const mongoose = require('mongoose'); +const helpStatusEnum = require('../utils/enums/helpStatusEnum'); +const { + getDistance, + calculateDistance, +} = require('../utils/geolocation/calculateDistance'); + +const campaignSchema = new mongoose.Schema({ + title: { + type: String, + required: true, + }, + description: { + type: String, + maxlength: 500, + required: true, + }, + status: { + type: String, + enum: Object.values(helpStatusEnum), + default: helpStatusEnum.WAITING, + }, + categoryId: { + type: [mongoose.Schema.Types.ObjectId], + ref: 'Category', + }, + ownerId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + helperId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: false, + }, + creationDate: { + type: Date, + default: Date.now, + }, + finishedDate: { + type: Date, + required: false, + }, + active: { + default: true, + type: Boolean, + }, +}, + { + collection: 'campaign', + toObject: { + virtuals: true, + }, + toJSON: { + virtuals: true, + }, + }); + +campaignSchema.virtual('categories', { + ref: 'Category', + localField: 'categoryId', + foreignField: '_id', +}); +campaignSchema.virtual('entity', { + ref: 'Entity', + localField: 'ownerId', + foreignField: '_id', + justOne: true, +}); + +campaignSchema.virtual('distances') + .set(({ campaignCoords, coords }) => { + campaignCoords = { + longitude: campaignCoords[0], + latitude: campaignCoords[1], + }; + const coordinates = { + longitude: coords[0], + latitude: coords[1], + }; + this.distanceValue = calculateDistance(coordinates, campaignCoords); + this.distance = getDistance(coordinates, campaignCoords); + }) + +campaignSchema.virtual('distanceValue') + .get(() => this.distanceValue); +campaignSchema.virtual('distance') + .get(() => this.distance); + +module.exports = mongoose.model('Campaign', campaignSchema); diff --git a/src/models/Entity.js b/src/models/Entity.js new file mode 100644 index 00000000..bc802ff2 --- /dev/null +++ b/src/models/Entity.js @@ -0,0 +1,74 @@ +const mongoose = require('mongoose'); +const { cnpj } = require('cpf-cnpj-validator'); +const Point = require('./Point'); + +const entitySchema = new mongoose.Schema({ + name: { + type: String, + required: true, + }, + deviceId: { + type: String, + required: false, + }, + email: { + type: String, + required: true, + unique: true, + index: true, + }, + cnpj: { + type: String, + required: true, + unique: true, + index: true, + validate: { + validator: (v) => cnpj.isValid(v), + message: (props) => `${props.value} não é um cnpj válido`, + }, + }, + photo: { + type: String, + required: true, + }, + notificationToken: { + type: String, + }, + address: { + cep: { + type: String, + required: true, + }, + number: { + type: Number, + required: true, + }, + city: { + type: String, + required: true, + }, + state: { + type: String, + required: true, + }, + complement: String, + }, + location: { + type: Point, + index: '2dsphere', + }, + phone: { + type: String, + required: true, + }, + registerDate: { + type: Date, + default: Date.now, + }, + active: { + default: true, + type: Boolean, + }, +}, { collection: 'entity' }); + +module.exports = mongoose.model('Entity', entitySchema); diff --git a/src/models/Help.js b/src/models/Help.js index 6dd49872..17b5a9ac 100644 --- a/src/models/Help.js +++ b/src/models/Help.js @@ -1,64 +1,75 @@ const mongoose = require('mongoose'); const helpStatusEnum = require('../utils/enums/helpStatusEnum'); +const { + getDistance, + calculateDistance, +} = require('../utils/geolocation/calculateDistance'); -const helpSchema = new mongoose.Schema({ - title: { - type: String, - required: true, +const helpSchema = new mongoose.Schema( + { + title: { + type: String, + required: true, + }, + description: { + type: String, + maxlength: 300, + required: true, + }, + status: { + type: String, + enum: Object.values(helpStatusEnum), + default: helpStatusEnum.WAITING, + }, + possibleHelpers: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: false, + }], + possibleEntities: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'Entity', + required: false, + }], + categoryId: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'Category', + }], + ownerId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + helperId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: false, + }, + creationDate: { + type: Date, + default: Date.now, + }, + finishedDate: { + type: Date, + required: false, + }, + active: { + default: true, + type: Boolean, + }, }, - description: { - type: String, - maxlength: 300, - required: true, + { + collection: 'userHelp', + toObject: { + virtuals: true, + }, + toJSON: { + virtuals: true, + }, }, - status: { - type: String, - enum: Object.values(helpStatusEnum), - default: helpStatusEnum.WAITING, - }, - possibleHelpers: { - type: [mongoose.Schema.Types.ObjectId], - ref: 'User', - required: false, - }, - categoryId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Category', - }, - ownerId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - required: true, - }, - helperId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - required: false, - }, - creationDate: { - type: Date, - default: Date.now, - }, - finishedDate: { - type: Date, - required: false, - }, - active: { - default: true, - type: Boolean, - }, -}, -{ - collection: 'userHelp', - toObject: { - virtuals: true, - }, - toJSON: { - virtuals: true, - }, -}); +); -helpSchema.virtual('category', { +helpSchema.virtual('categories', { ref: 'Category', localField: 'categoryId', foreignField: '_id', @@ -67,6 +78,26 @@ helpSchema.virtual('user', { ref: 'User', localField: 'ownerId', foreignField: '_id', + justOne: true }); +helpSchema.virtual('distances') + .set(({ userCoords, coords }) => { + userCoords = { + longitude: userCoords[0], + latitude: userCoords[1], + }; + const coordinates = { + longitude: coords[0], + latitude: coords[1], + }; + this.distanceValue = calculateDistance(coordinates, userCoords); + this.distance = getDistance(coordinates, userCoords); + }); + +helpSchema.virtual('distanceValue') + .get(() => this.distanceValue); +helpSchema.virtual('distance') + .get(() => this.distance); + module.exports = mongoose.model('Help', helpSchema); diff --git a/src/models/HelpOffer.js b/src/models/HelpOffer.js new file mode 100644 index 00000000..f25ea38b --- /dev/null +++ b/src/models/HelpOffer.js @@ -0,0 +1,87 @@ +const { Schema, model } = require('mongoose'); +const helpStatusEnum = require('../utils/enums/helpStatusEnum'); + +const offeredHelpSchema = new Schema( + { + title: { + type: String, + required: true, + }, + description: { + type: String, + maxlength: 300, + required: true, + }, + status: { + type: String, + enum: Object.values(helpStatusEnum), + default: helpStatusEnum.WAITING, + }, + possibleHelpedUsers: [{ + type: Schema.Types.ObjectId, + ref: 'User', + required: false, + }], + possibleEntities: [{ + type: Schema.Types.ObjectId, + ref: 'Entity', + required: false, + }], + categoryId: [{ + type: Schema.Types.ObjectId, + ref: 'Category', + }], + ownerId: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + helpedUserId: [{ + type: Schema.Types.ObjectId, + ref: ['User' , 'Entity'], + required: false, + }], + creationDate: { + type: Date, + default: Date.now, + }, + finishedDate: { + type: Date, + required: false, + }, + active: { + default: true, + type: Boolean, + }, + }, + { + collection: 'helpOffer', + toObject: { + virtuals: true, + }, + toJSON: { + virtuals: true, + }, + } +); + +offeredHelpSchema.virtual('user', { + ref: 'User', + localField: 'ownerId', + foreignField: '_id', + justOne: true, +}); + +offeredHelpSchema.virtual('categories', { + ref: 'Category', + localField: 'categoryId', + foreignField: '_id', +}); + +offeredHelpSchema.virtual('helpedUsers', { + ref: ['User', 'Entity'], + localField: 'helpedUserId', + foreignField: '_id', +}); + +module.exports = model('OfferedHelp', offeredHelpSchema); \ No newline at end of file diff --git a/src/models/Notification.js b/src/models/Notification.js index 0394f010..dc6b6376 100644 --- a/src/models/Notification.js +++ b/src/models/Notification.js @@ -5,6 +5,8 @@ const notificationTypes = { ajudaAceita: 'Sua oferta de ajuda foi aceita!', ajudaFinalizada: 'Seu pedido de ajuda foi finalizado!', ajudaExpirada: 'Seu pedido de ajuda expirou!', + ofertaRequerida: 'Sua oferta de ajuda possui um usuário interessado!', + ofertaAceita: 'Seu pedido de ajuda foi aceito!', outros: 'Demais tipos de notificação!', }; @@ -13,6 +15,9 @@ const notificationTypesEnum = { ajudaAceita: 'ajudaAceita', ajudaFinalizada: 'ajudaFinalizada', ajudaExpirada: 'ajudaExpirada', + notificacaoManual: 'notificacaoManual', + ofertaRequerida: 'ofertaRequerida', + ofertaAceita: 'ofertaAceita', outros: 'outros', }; @@ -22,6 +27,11 @@ const NotificationSchema = new mongoose.Schema({ ref: 'User', required: false, }, + isOffer: { + type: Boolean, + required: false, + default: false, + }, helpId: { type: mongoose.Schema.Types.ObjectId, ref: 'Help', diff --git a/src/repository/BaseRepository.js b/src/repository/BaseRepository.js index 23f15071..81dd81cf 100644 --- a/src/repository/BaseRepository.js +++ b/src/repository/BaseRepository.js @@ -15,6 +15,11 @@ class BaseRepository { return savedModel; } + async $populateExistingDoc(doc, populate) { + const populatedDoc = doc.populate(populate).execPopulate(); + return populatedDoc; + } + async $saveMany(itemsModel, mongoSession = {}) { itemsModel.forEach((item) => { item.lastUpdateDate = Date.now(); @@ -65,9 +70,10 @@ class BaseRepository { return recordModel; } - async $list(query, populate = null) { - const recordModel = await this.modelClass.find(query).populate(populate); - return recordModel; + async $list(query, selectedField, populate = null, sort = null) { + return this.modelClass.find(query, selectedField) + .populate(populate) + .sort(sort); } async $countDocuments(query) { @@ -75,24 +81,19 @@ class BaseRepository { return numberDocuments; } - async findOne(query, mongoSession = {}) { - let result; - - if (mongoSession !== undefined || mongoSession.session !== undefined) { - result = await this.modelClass - .findOne(query) - .session(mongoSession.session); - return result; - } - result = await this.modelClass.findOne(query); - - return result; + async $findOne(query, projection, populate = null) { + return this.modelClass.findOne(query, projection) + .populate(populate); } async $destroy(query) { const result = await this.modelClass.deleteOne(query); return result; } + + async $findOneAndUpdate(filter, update) { + await this.modelClass.findOneAndUpdate(filter, update); + } } module.exports = BaseRepository; diff --git a/src/repository/CampaignRepository.js b/src/repository/CampaignRepository.js new file mode 100644 index 00000000..c03666ed --- /dev/null +++ b/src/repository/CampaignRepository.js @@ -0,0 +1,82 @@ +const { ObjectID } = require('mongodb'); +const BaseRepository = require('./BaseRepository'); +const Campaign = require('../models/Campaign'); +const EntitySchema = require('../models/Entity'); + +class CampaignRepository extends BaseRepository { + constructor() { + super(Campaign); + } + + async create(campaign) { + const newCampaign = await super.$save(campaign); + return newCampaign; + } + + async list() { + const query = null; + const populate = 'campaign'; + const campaigns = await super.$list(query, populate); + return campaigns; + } + + async listByOwnerId(ownerId) { + const query = { ownerId }; + const campaigns = await super.$list(query); + return campaigns; + } + + async getById(id) { + const campaign = await super.$getById(id); + return campaign; + } + + async update(campaign) { + await super.$update(campaign); + } + + async listNear(coords, except, id, categoryArray) { + const userQuery = { + _id: except ? { $ne: id } : null, + }; + const users = await EntitySchema.find(userQuery); + const arrayUsersId = users.map((user) => user._id); + const matchQuery = { + active: true, + ownerId: { $in: arrayUsersId }, + status: 'waiting' + }; + const populate = ['entity', 'categories']; + + if (categoryArray) { + matchQuery.categoryId = { + $in: categoryArray.map((categoryString) => ObjectID(categoryString)), + }; + } + + const campaigns = await super.$list(matchQuery, {}, populate); + const campaignsWithDistances = campaigns.map(campaign => { + campaign.distances = { campaignCoords: campaign.entity.location.coordinates, coords } + return campaign.toObject(); + }) + + campaignsWithDistances.sort((a, b) => a.distanceValue - b.distanceValue); + + return campaignsWithDistances; + } + + async getCampaignListByStatus(userId, statusList) { + const matchQuery = { + ownerId: ObjectID(userId), + status: { + $in: [...statusList], + }, + active: true, + }; + const entity = 'entity'; + const categories = 'categories'; + return super.$list(matchQuery, {}, [entity, categories]); + } +} + +module.exports = CampaignRepository; diff --git a/src/repository/EntityRepository.js b/src/repository/EntityRepository.js new file mode 100644 index 00000000..2da68fab --- /dev/null +++ b/src/repository/EntityRepository.js @@ -0,0 +1,66 @@ +const BaseRepository = require('./BaseRepository'); +const EntitySchema = require('../models/Entity'); + +class EntityRepository extends BaseRepository { + constructor() { + super(EntitySchema); + } + + async create(entity) { + const result = await super.$save(entity); + return result; + } + + async getById(id) { + const result = await super.$getById(id); + return result; + } + + async getEntityByEmail(email) { + const result = await super.$list({ email }); + return result[0]; + } + + async update(entity) { + const result = await super.$update(entity); + return result; + } + + async checkEntityExistence(id) { + const entities = await super.$listAggregate([ + { + $match: { + $or: [ + { cnpj: id }, + { email: id }, + ], + }, + }, { + $count: 'id', + }, + ]); + + let result = 0; + + if (entities[0] && entities[0].id > 0) { + result = entities[0].id; + } + + return result; + } + + async removeEntity({ id, email }) { + const query = {}; + query._id = id; + query.email = email; + + await super.$destroy(query); + } + + async findOneEntityWithProjection(query,projection){ + const entity = await super.$findOne(query,projection); + return entity; + } +} + +module.exports = EntityRepository; diff --git a/src/repository/HelpOfferRepository.js b/src/repository/HelpOfferRepository.js new file mode 100644 index 00000000..1642e213 --- /dev/null +++ b/src/repository/HelpOfferRepository.js @@ -0,0 +1,151 @@ +const { ObjectID } = require('mongodb'); +const BaseRepository = require('./BaseRepository'); +const OfferedHelp = require('../models/HelpOffer'); + +class OfferdHelpRepository extends BaseRepository { + constructor() { + super(OfferedHelp); + } + + async create(offeredHelp) { + const newOfferdHelp = await super.$save(offeredHelp); + return newOfferdHelp; + } + + async update(helpOffer) { + await super.$update(helpOffer); + } + + async getByIdWithAggregation(id) { + const query = { _id: ObjectID(id) }; + const helpOfferFields = [ + '_id', + 'description', + 'title', + 'status', + 'ownerId', + 'categoryId', + 'possibleHelpedUsers', + 'possibleEntities', + 'helpedUserId' + ]; + const user = { + path: 'user', + select: ['photo', 'phone', 'name', 'birthday', 'address.city'] + }; + const categories = { + path: 'categories', + select: ['_id', 'name'] + }; + const possibleHelpedUsers = { + path: 'possibleHelpedUsers', + select: ['_id', 'name', 'photo', 'birthday', 'phone', 'address.city'] + }; + const possibleEntities = { + path: 'possibleEntities', + select: ['_id', 'name', 'photo', 'birthday', 'address.city'] + }; + const helpedUsers = { + path: 'helpedUsers', + select: ['_id', 'name', 'photo', 'birthday', 'phone', 'address.city'] + }; + + const populate = [user, categories, possibleHelpedUsers, possibleEntities, helpedUsers]; + return super.$findOne(query, helpOfferFields, populate); + } + + async list(userId, isUserEntity, categoryArray, getOtherUsers) { + const matchQuery = this.getHelpOfferListQuery( + userId, + isUserEntity, + true, + getOtherUsers, + categoryArray + ); + const helpOfferFields = ['_id', 'title', 'categoryId', 'ownerId', 'helpedUserId']; + const sort = { creationDate: -1 } + const user = { + path: 'user', + select: ['name', 'address', 'birthday', 'location.coordinates'] + } + + const categories = 'categories'; + + const possibleHelpedUsers = { + path: 'possibleHelpedUsers', + select: ['_id', 'name'] + }; + + const possibleEntities = { + path: 'possibleEntities', + select: ['_id', 'name'] + }; + + const populate = [user, categories, possibleHelpedUsers, possibleEntities]; + + return super.$list(matchQuery, helpOfferFields, populate, sort); + } + getHelpOfferListQuery(userId, isUserEntity, active, getOtherUsers, categoryArray) { + var matchQuery = { active }; + if (!getOtherUsers) { + matchQuery.ownerId = { $ne: ObjectID(userId) }; + + if(isUserEntity){ + matchQuery.possibleEntities = { $nin: [ObjectID(userId)] }; + }else{ + matchQuery.possibleHelpedUsers = { $nin: [ObjectID(userId)] }; + } + } else { + matchQuery.ownerId = { $eq: ObjectID(userId) }; + } + + if (categoryArray) { + matchQuery.categoryId = { + $in: categoryArray.map((category) => ObjectID(category)), + }; + } + return matchQuery; + } + + async listByOwnerId(ownerId) { + const query = { ownerId }; + const helpOffers = await super.$list(query); + return helpOffers; + } + + async listByHelpedUserId(helpedUserId) { + const query = { helpedUserId }; + const helpOffers = await super.$list(query); + return helpOffers; + } + + async getById(id) { + const helpOffer = await super.$getById(id); + return helpOffer; + } + + async findOne(query, projection, populate = null) { + return super.$findOne(query, projection, populate); + } + + async finishHelpOfferByOwner(helpOffer) { + helpOffer.active = false; + return super.$update(helpOffer); + } + + async getEmailByHelpOfferId(helpOfferId) { + const matchQuery = { _id: ObjectID(helpOfferId) }; + const helpProjection = { + _id: 0, + ownerId: 1, + } + const user = { + path: 'user', + select: 'email -_id' + } + const helpOffer = await super.$findOne(matchQuery, helpProjection, user); + return helpOffer.user.email; + } +} + +module.exports = OfferdHelpRepository; \ No newline at end of file diff --git a/src/repository/HelpRepository.js b/src/repository/HelpRepository.js index a11ad7bf..795aa499 100644 --- a/src/repository/HelpRepository.js +++ b/src/repository/HelpRepository.js @@ -1,8 +1,8 @@ +// eslint-disable-next-line import/no-unresolved const { ObjectID } = require('mongodb'); const BaseRepository = require('./BaseRepository'); const HelpSchema = require('../models/Help'); -const UserSchema = require('../models/User'); -const { getDistance, calculateDistance } = require('../utils/geolocation/calculateDistance'); +const sharedAgreggationInfo = require('../utils/sharedAggregationInfo'); class HelpRepository extends BaseRepository { constructor() { @@ -10,38 +10,26 @@ class HelpRepository extends BaseRepository { } async create(help) { - const result = await super.$save(help); - - const aggregation = [ - { - $match: { _id: result._id }, - }, - { - $lookup: { - from: 'user', - localField: 'ownerId', - foreignField: '_id', - as: 'user', - }, - }, + const doc = await super.$save(help); + const populate = [ { - $unwind: { - path: '$user', - preserveNullAndEmptyArrays: false, - }, + path: 'user', + select: ['name', 'riskGroup', 'location.coordinates'] }, { - $lookup: { - from: 'category', - localField: 'categoryId', - foreignField: '_id', - as: 'category', - }, - }, - ]; - - const helps = await super.$listAggregate(aggregation); - return helps[0]; + path: 'categories', + select: ['name'] + } + ] + let result = await super.$populateExistingDoc(doc, populate); + return { + _id: result._id, + ownerId: result.ownerId, + title: result.title, + categoryId: result.categoryId, + categories: result.categories, + user: result.user + } } async getById(id) { @@ -49,194 +37,75 @@ class HelpRepository extends BaseRepository { return help; } - async update(help) { - const helpUpdated = await super.$update(help); - return helpUpdated; + async getByIdWithAggregation(id) { + const matchQuery = { _id: ObjectID(id) }; + const helpFields = [ + '_id', 'ownerId', 'categoryId', + 'possibleHelpers', 'possibleEntities', + 'description', 'helperId', 'status', 'title' + ]; + const user = { + path: 'user', + select: ['photo', 'name', 'phone', 'birthday', 'address.city', 'location.coordinates'] + } + const categories = { + path: 'categories', + select: ['_id', 'name'] + } + const possibleHelpers = { + path: 'possibleHelpers', + select: ['_id', 'name', 'phone', 'photo', 'birthday', 'address.city'] + } + const possibleEntities = { + path: 'possibleEntities', + select: ['_id', 'name', 'photo', 'address.city'] + } + return super.$findOne( + matchQuery, + helpFields, + [user, categories, possibleHelpers, possibleEntities] + ); } - async list(id, status, except, helper, categoryArray) { - const ownerId = except - ? { $ne: ObjectID(id) } - : helper - ? null - : ObjectID(id); - const helperId = helper ? ObjectID(id) : null; - const query = {}; - if (status) query.status = status; - if (categoryArray) query.categoryId = { $in: categoryArray }; - if (helper) query.helperId = helperId; - else query.ownerId = ownerId; - - const result = await super.$listAggregate([ - { - $match: query, - }, - { - $lookup: { - from: 'user', - localField: 'ownerId', - foreignField: '_id', - as: 'user', - }, - }, - { - $unwind: { - path: '$user', - preserveNullAndEmptyArrays: false, - }, - }, - { - $addFields: { - ageRisk: { - $cond: [ - { - $gt: [ - { - $subtract: [ - { - $year: '$$NOW', - }, - { - $year: '$user.birthday', - }, - ], - }, - 60, - ], - }, - 1, - 0, - ], - }, - cardio: { - $cond: [ - { - $in: ['$user.riskGroup', [['doenCardio']]], - }, - 1, - 0, - ], - }, - risco: { - $size: '$user.riskGroup', - }, - }, - }, - { - $sort: { - ageRisk: -1, - cardio: -1, - risco: -1, - }, - }, - { - $project: { - ageRisk: 0, - cardio: 0, - risco: 0, - }, - }, - { - $lookup: { - from: 'category', - localField: 'categoryId', - foreignField: '_id', - as: 'category', - }, - }, - { - $lookup: { - from: 'user', - localField: 'possibleHelpers', - foreignField: '_id', - as: 'possibleHelpers', - }, - }, - ]); - return result; + async update(help) { + await super.$update(help); } - async listNear(coords, except, id, categoryArray) { - const query = {}; - const ownerId = except ? { $ne: id } : null; - - query._id = ownerId; - - const users = await UserSchema.find(query); - const arrayUsersId = users.map((user) => user._id); - - const matchQuery = {}; - - matchQuery.active = true; - matchQuery.possibleHelpers = { $not: { $in: [ObjectID(id)] } }; - matchQuery.ownerId = { - $in: arrayUsersId, + async shortList(coords, id, isUserEntity, categoryArray) { + const matchQuery = { + active: true, + ownerId: { $ne: ObjectID(id) }, + status: 'waiting' }; - matchQuery.status = 'waiting'; + + if(isUserEntity){ + matchQuery.possibleEntities = { $nin: [ObjectID(id)] }; + }else{ + matchQuery.possibleHelpers = { $nin: [ObjectID(id)] }; + } if (categoryArray) { matchQuery.categoryId = { $in: categoryArray.map((categoryString) => ObjectID(categoryString)), }; } - const aggregation = [ - { - $match: matchQuery, - }, - { - $lookup: { - from: 'user', - localField: 'ownerId', - foreignField: '_id', - as: 'user', - }, - }, - { - $unwind: { - path: '$user', - preserveNullAndEmptyArrays: false, - }, - }, - { - $lookup: { - from: 'category', - localField: 'categoryId', - foreignField: '_id', - as: 'category', - }, - }, - { - $lookup: { - from: 'user', - localField: 'possibleHelpers', - foreignField: '_id', - as: 'possibleHelpers', - }, - }, - ]; - - const helps = await super.$listAggregate(aggregation); - const helpsWithDistance = helps.map((help) => { - const coordinates = { - latitude: coords[1], - longitude: coords[0], - }; - const helpCoords = { - latitude: help.user.location.coordinates[1], - longitude: help.user.location.coordinates[0], - }; - help.distance = getDistance(coordinates, helpCoords); - help.distanceValue = calculateDistance(coordinates, helpCoords); - return help; - }); - helpsWithDistance.sort((a, b) => { - if (a.distanceValue < b.distanceValue) { - return -1; - } if (a.distanceValue > b.distanceValue) { - return 1; - } - return 0; + const helpFields = ['_id', 'title', 'description', 'categoryId', 'ownerId']; + const user = { + path: 'user', + select: ['name', 'riskGroup', 'location.coordinates'] + }; + const categories = { + path: 'categories', + select: ['_id', 'name'] + } + const helps = await super.$list(matchQuery, helpFields, [user, categories]) + const helpsWithDistance = helps.map(help => { + help.distances = { userCoords: help.user.location.coordinates, coords } + return help.toObject(); }); + + helpsWithDistance.sort((a, b) => a.distanceValue - b.distanceValue); + return helpsWithDistance; } @@ -254,7 +123,7 @@ class HelpRepository extends BaseRepository { const date = new Date(); date.setDate(date.getDate() - 14); - return await super.$list({ + return super.$list({ creationDate: { $lt: new Date(date) }, active: true, }); @@ -268,7 +137,29 @@ class HelpRepository extends BaseRepository { active: true, }; + const fields = [ + '_id', + 'description', + 'title', + 'status', + 'ownerId', + 'categoryId' + ]; + + const user = { + path: 'user', + select: ['photo', 'phone', 'name', 'birthday', 'address.city'], + }; + + const categories = { + path: 'categories', + select: ['_id', 'name'], + }; + + const populate = [user, categories]; + if (helper) { + user.select.push('location.coordinates'); matchQuery.$or = [ { possibleHelpers: { $in: [ObjectID(userId)] }, @@ -277,46 +168,48 @@ class HelpRepository extends BaseRepository { helperId: ObjectID(userId), }, ]; - } else { - matchQuery.ownerId = ObjectID(userId); - } - const helpList = await super.$listAggregate([ - { - $match: matchQuery, - }, - { - $lookup: { - from: 'user', - localField: 'possibleHelpers', - foreignField: '_id', - as: 'possibleHelpers', - }, - }, - { - $lookup: { - from: 'user', - localField: 'ownerId', - foreignField: '_id', - as: 'user', - }, - }, - { - $lookup: { - from: 'category', - localField: 'categoryId', - foreignField: '_id', - as: 'category', - }, - }, - { - $unwind: { - path: '$user', - preserveNullAndEmptyArrays: false, - }, - }, - ]); + } else { + const possibleHelpers = { + path: 'possibleHelpers', + select: ['_id', 'photo', 'name', 'birthday', 'address.city'], + }; + + const possibleEntities = { + path: 'possibleEntities', + select: ['_id', 'photo', 'name', 'birthday', 'address.city'], + }; + + fields.push('helperId'); + + populate.push(possibleHelpers); + populate.push(possibleEntities); + + matchQuery.ownerId = ObjectID(userId); + } + + const helpList = await super.$list(matchQuery, fields, populate); return helpList; } + + async getHelpInfoById(helpId) { + const matchQuery = { _id: ObjectID(helpId) }; + + const populate = { + path: 'user', + select: ['photo', 'birthday', 'address.city'] + } + + const projection = { + description: 1, + _id: 0, + }; + + return super.$findOne( + matchQuery, + projection, + populate + ); + } } module.exports = HelpRepository; diff --git a/src/repository/UserRepository.js b/src/repository/UserRepository.js index ae0a5052..ebf85cf0 100644 --- a/src/repository/UserRepository.js +++ b/src/repository/UserRepository.js @@ -56,6 +56,17 @@ class UserRepository extends BaseRepository { await super.$destroy(query); } + + async getUsersWithDevice() { + const users = await super.$list({ deviceId: { $ne: null } }); + + return users; + } + + async findOneUserWithProjection(query,projection){ + const user = await super.$findOne(query,projection); + return user; + } } module.exports = UserRepository; diff --git a/src/routes/BaseRoutes.js b/src/routes/BaseRoutes.js index 0050379f..7c9b5f78 100644 --- a/src/routes/BaseRoutes.js +++ b/src/routes/BaseRoutes.js @@ -1,13 +1,16 @@ const YAML = require('yamljs'); const swaggerUi = require('swagger-ui-express'); const userRoutes = require('./UserRoutes'); +const entityRoutes = require('./EntityRoutes'); const helpRoutes = require('./HelpRoutes'); const categoryRoutes = require('./CategoryRoutes'); const notificationRoutes = require('./NotificationRoutes'); +const helpOfferRoutes = require('./HelpOfferRoutes'); +const campaignRoutes = require('./CampaignRoutes'); const swaggerDocument = YAML.load('docs/swagger.yaml'); module.exports = (app) => { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); - app.use('/api', [userRoutes, helpRoutes, categoryRoutes, notificationRoutes]); + app.use('/api', [userRoutes, helpRoutes, categoryRoutes, notificationRoutes, entityRoutes, helpOfferRoutes, campaignRoutes]); }; diff --git a/src/routes/CampaignRoutes.js b/src/routes/CampaignRoutes.js new file mode 100644 index 00000000..9467249e --- /dev/null +++ b/src/routes/CampaignRoutes.js @@ -0,0 +1,35 @@ +const express = require('express'); +const CampaignController = require('../controllers/CampaignController'); +const isAuthenticated = require('../validation/middlewares/authFirebase'); + +const campaignController = new CampaignController(); +const routes = express.Router(); + +routes.post('/campaign', isAuthenticated, (req, res, next) => { + campaignController.createCampaign(req, res, next); +}); + +routes.get('/campaign', isAuthenticated, (req, res, next) => { + campaignController.listCampaignNear(req, res, next); +}); + +routes.get( + '/campaign/listbyStatus/:userId', + isAuthenticated, + (req, res, next) => { + campaignController.getCampaignListByStatus(req, res, next); + }, +); + +routes.put('/campaign/:id', isAuthenticated, (req, res, next) => { + campaignController.finishCampaign(req, res, next); +}); + +routes.delete('/campaign/:id', isAuthenticated, async (req, res, next) => { + campaignController.finishCampaign(req, res, next); +}); +routes.get('/campaign/:id', isAuthenticated, (req, res, next) => { + campaignController.getCampaignById(req, res, next); +}); + +module.exports = routes; diff --git a/src/routes/EntityRoutes.js b/src/routes/EntityRoutes.js new file mode 100644 index 00000000..cdb96cdb --- /dev/null +++ b/src/routes/EntityRoutes.js @@ -0,0 +1,37 @@ +const express = require('express'); +const EntityController = require('../controllers/EntityController'); +const isAuthenticated = require('../validation/middlewares/authFirebase'); + +const routes = express.Router(); +const entityController = new EntityController(); + +routes.post('/entity', async (req, res, next) => { + entityController.createEntity(req, res, next); +}); + +routes.get('/entity/getEntity/:id*?/', isAuthenticated, async (req, res, next) => { + entityController.getEntityById(req, res, next); +}); + +routes.put('/entity', isAuthenticated, async (req, res, next) => { + entityController.editEntityById(req, res, next); +}); + +routes.put('/entity/address', isAuthenticated, async (req, res, next) => { + entityController.editEntityAddressById(req, res, next); +}); + +routes.put('/entity/location', isAuthenticated, async (req, res, next) => { + entityController.updateEntityLocationById(req, res, next); +}); + +routes.delete('/entity', isAuthenticated, async (req, res, next) => { + entityController.deleteEntityLogic(req, res, next); +}); + +// Verifica a existência de uma entidade/ONG baseado no email ou CNPJ +routes.get('/checkEntityExistence/:entityIdentifier', async (req, res, next) => { + entityController.checkEntityExistence(req, res, next); +}); + +module.exports = routes; diff --git a/src/routes/HelpOfferRoutes.js b/src/routes/HelpOfferRoutes.js new file mode 100644 index 00000000..a5c068d1 --- /dev/null +++ b/src/routes/HelpOfferRoutes.js @@ -0,0 +1,48 @@ +const express = require('express'); +const HelpOfferController = require('../controllers/HelpOfferController'); +const isAuthenticated = require('../validation/middlewares/authFirebase'); + +const helpOfferController = new HelpOfferController(); +const routes = express.Router(); + +routes.post('/helpOffer', isAuthenticated, (req, res, next) => { + helpOfferController.createHelpOffer(req, res, next); +}); + +routes.get('/helpOffer/list', isAuthenticated, (req, res, next) => { + helpOfferController.listHelpsOffers(req, res, next); +}); + +routes.get('/helpOffer/aggregation/:id', isAuthenticated, async (req, res, next) => { + helpOfferController.getHelpWithAggregationById(req, res, next); +}); + +routes.get( + '/helpOffer/list/:helpedUserId', + isAuthenticated, + (req, res, next) => { + helpOfferController.listHelpOffersByHelpedUserId(req, res, next); + }, +); + +routes.put( + '/helpOffer/possibleHelpedUsers/:helpedId/:helpOfferId', + isAuthenticated, + (req, res, next) => { + helpOfferController.addPossibleHelpedUsers(req, res, next); + }, +); + +routes.put( + '/helpOffer/chooseHelpedUsers/:helpedId/:helpOfferId', + isAuthenticated, + (req, res, next) => { + helpOfferController.chooseHelpedUsers(req, res, next); + } +); + +routes.delete('/helpOffer/:helpOfferId', isAuthenticated, async (req, res, next) => { + helpOfferController.finishHelpOfferByOwner(req, res, next); +}); + +module.exports = routes; diff --git a/src/routes/HelpRoutes.js b/src/routes/HelpRoutes.js index 02a2e39f..c9235e85 100644 --- a/src/routes/HelpRoutes.js +++ b/src/routes/HelpRoutes.js @@ -9,15 +9,19 @@ routes.post('/help', isAuthenticated, async (req, res, next) => { helpController.createHelp(req, res, next); }); -routes.get('/help/:id', isAuthenticated, async (req, res, next) => { - helpController.getHelpById(req, res, next); +routes.get('/help/aggregation/:id', isAuthenticated, async (req, res, next) => { + helpController.getHelpWithAggregationById(req, res, next); +}); + +routes.get('/help/helpInfo/:helpId', isAuthenticated, async (req, res, next) => { + helpController.getHelpInfoById(req, res, next); }); routes.get('/help', isAuthenticated, async (req, res, next) => { helpController.getHelpList(req, res, next); }); -routes.get('/help/listbyStatus/:userId', async (req, res, next) => { +routes.get('/help/listbyStatus/:userId', isAuthenticated, async (req, res, next) => { helpController.getHelpListByStatus(req, res, next); }); diff --git a/src/routes/NotificationRoutes.js b/src/routes/NotificationRoutes.js index b4dec354..e2d2fa2e 100644 --- a/src/routes/NotificationRoutes.js +++ b/src/routes/NotificationRoutes.js @@ -9,4 +9,8 @@ routes.get('/notification/user/:id', isAuthenticated, async (req, res, next) => notificationController.getUserNotificationsById(req, res, next); }); +routes.post('/notifications/send', isAuthenticated, async (req, res, next) => { + notificationController.sendNotifications(req, res, next); +}); + module.exports = routes; diff --git a/src/routes/UserRoutes.js b/src/routes/UserRoutes.js index 3e526acf..7b7ee246 100644 --- a/src/routes/UserRoutes.js +++ b/src/routes/UserRoutes.js @@ -1,38 +1,47 @@ -const express = require('express'); -const UserController = require('../controllers/UserController'); -const isAuthenticated = require('../validation/middlewares/authFirebase'); +const express = require("express"); +const UserController = require("../controllers/UserController"); +const isAuthenticated = require("../validation/middlewares/authFirebase"); const routes = express.Router(); const userController = new UserController(); -routes.post('/user', async (req, res, next) => { +routes.post("/user", async (req, res, next) => { userController.createUser(req, res, next); }); -routes.get('/user/getUser/:id*?/', isAuthenticated, async (req, res, next) => { +routes.get("/user/getUser/:id*?/", isAuthenticated, async (req, res, next) => { userController.getUserById(req, res, next); }); -routes.put('/user', isAuthenticated, async (req, res, next) => { +routes.get( + "/user/getAnyUser/:id*?/", + isAuthenticated, + async (req, res, next) => { + userController.getAnyUserById(req, res, next); + } +); + +routes.put("/user", isAuthenticated, async (req, res, next) => { userController.editUserById(req, res, next); }); -routes.put('/user/address', isAuthenticated, async (req, res, next) => { +routes.put("/user/address", isAuthenticated, async (req, res, next) => { userController.editUserAddressById(req, res, next); }); -routes.put('/user/location', isAuthenticated, async (req, res, next) => { +routes.put("/user/location", isAuthenticated, async (req, res, next) => { userController.updateUserLocationById(req, res, next); }); -routes.delete('/user', isAuthenticated, async (req, res, next) => { +routes.delete("/user", isAuthenticated, async (req, res, next) => { userController.deleteUserLogic(req, res, next); }); -routes.get('/groupRisk', async (req, res, next) => { +routes.get("/groupRisk", async (req, res, next) => { userController.getUserGroupRiskList(req, res, next); }); -routes.get('/checkUserExistence/:value', async (req, res, next) => { +// Verifica a existência de um usuário baseado no email ou CPF +routes.get("/checkUserExistence/:userIdentifier", async (req, res, next) => { userController.checkUserExistence(req, res, next); }); diff --git a/src/services/CampaignService.js b/src/services/CampaignService.js new file mode 100644 index 00000000..bd8e7f1a --- /dev/null +++ b/src/services/CampaignService.js @@ -0,0 +1,92 @@ +const CampaignRepository = require('../repository/CampaignRepository'); +const CategoryService = require('./CategoryService'); +const helpStatusEnum = require('../utils/enums/helpStatusEnum'); + +class CampaignService { + constructor() { + this.CampaignRepository = new CampaignRepository(); + this.CategoryService = new CategoryService(); + } + + async createNewCampaign(campaignInfo) { + await this.CategoryService.getCategoryByid(campaignInfo.categoryId); + const newCampaign = await this.CampaignRepository.create(campaignInfo); + return newCampaign; + } + + async listCampaign() { + const campaign = await this.CampaignRepository.list(); + return campaign; + } + + async getCampaignListByStatus({ userId, statusList }) { + const checkHelpStatusExistence = statusList.filter( + (item) => !Object.values(helpStatusEnum).includes(item), + ); + + if (checkHelpStatusExistence.length > 0) { + throw new Error('Um dos status informados é ínvalido'); + } + + const helpList = await this.CampaignRepository.getCampaignListByStatus( + userId, + statusList, + ); + + return helpList; + } + + async getNearCampaignList(coords, except, id, categoryArray) { + const CampaignList = await this.CampaignRepository.listNear( + coords, + except, + id, + categoryArray, + ); + if (!CampaignList) { + throw new Error( + 'Nenhuma campanha foi encontrada no seu raio de distância', + ); + } + + return CampaignList; + } + + async getCampaignById(id) { + const Campaign = await this.CampaignRepository.getById(id); + + if (!Campaign) { + throw new Error('Campanha não encontrada'); + } + + return Campaign; + } + + async deleteCampaign(id) { + let campaign = await this.getCampaignById(id); + console.log(campaign); + campaign.active = false; + + await this.CampaignRepository.update(campaign); + + + campaign = JSON.parse(JSON.stringify(campaign)); + return { message: `Campaign ${id} deleted!` }; + } + + async listCampaignByOwnerId(ownerId) { + const campaign = await this.CampaignRepository.listByOwnerId(ownerId); + return campaign; + } + + async finishCampaign(id) { + const campaign = await this.getCampaignById(id); + + campaign.status = 'finished'; + + const result = await this.CampaignRepository.update(campaign); + return result; + } +} + +module.exports = CampaignService; diff --git a/src/services/EntityService.js b/src/services/EntityService.js new file mode 100644 index 00000000..0e4161f3 --- /dev/null +++ b/src/services/EntityService.js @@ -0,0 +1,161 @@ +const EntityRepository = require("../repository/EntityRepository"); +const UserRepository = require("../repository/UserRepository"); +const firebase = require("../config/authFirebase"); +const { ObjectID } = require("mongodb"); + +class EntityService { + constructor() { + this.entityRepository = new EntityRepository(); + this.userRepository = new UserRepository(); + } + + async createEntity(data) { + const isUserRegistered = await this.userRepository.getUserByEmail( + data.email + ); + + if (isUserRegistered) { + throw new Error("Email já sendo utilizado"); + } + + if (data.password.length < 8) { + throw new Error("Senha inválida"); + } + + if (data.cnpj.length >= 14) { + data.cnpj = data.cnpj.replace(/([^0-9])+/g, ""); + } + + data.email = data.email.toLowerCase(); + try { + const createdEntity = await this.entityRepository.create(data); + + if (!data.hasUser) { + console.log("Usuario Criado"); + // Cria o usuário no firebase + await firebase + .auth() + .createUser({ + email: data.email, + password: data.password, + displayName: `${data.name} | PJ`, + emailVerified: false, + }) + .catch(async (err) => { + await this.removeEntity(data.email); + throw err; + }); + } + + return createdEntity; + } catch (err) { + throw err; + } + } + + async getEntity({ id = undefined, email = undefined }) { + if (!id && !email) { + throw new Error("Nenhum identificador encontrado"); + } + let entity; + + if (id) { + entity = await this.entityRepository.getById(id); + } else { + entity = await this.entityRepository.getEntityByEmail(email); + } + if (!entity) { + throw new Error("Usuário não encontrado"); + } + return entity; + } + + async editEntityById({ + email, + photo, + name, + phone, + notificationToken, + deviceId, + }) { + const entity = await this.getEntity({ email }); + + entity.photo = photo || entity.photo; + entity.name = name || entity.name; + entity.phone = phone || entity.phone; + entity.notificationToken = notificationToken || entity.notificationToken; + entity.deviceId = deviceId || entity.deviceId; + + const result = await this.entityRepository.update(entity); + + return result; + } + + async editEntityAddressById({ email, cep, number, city, state, complement }) { + const entity = await this.getEntity({ email }); + + const address = { + cep: cep || entity.address.cep, + number: number || entity.address.number, + city: city || entity.address.city, + state: state || entity.address.state, + complement: complement || entity.address.complement, + }; + + entity.address = address; + + const result = await this.entityRepository.update(entity); + + return result; + } + + async updateEntityLocationById({ email, longitude, latitude }) { + const entity = await this.getEntity({ email }); + + if (longitude || latitude) { + entity.location.coordinates[0] = + longitude || entity.location.coordinates[0]; + entity.location.coordinates[1] = + latitude || entity.location.coordinates[1]; + } + + const result = await this.entityRepository.update(entity); + + return result; + } + + async deleteEntityLogically(email) { + const entity = await this.getEntity({ email }); + + entity.active = false; + + await this.entityRepository.update(entity); + + return { message: `Entity ${entity._id} deleted!` }; + } + + async removeEntity(email) { + const entity = await this.getEntity({ email }); + await this.entityRepository.removeEntity({ id: entity._id, email }); + } + + async checkEntityExistence(entityIdentifier) { + const result = await this.entityRepository.checkEntityExistence( + entityIdentifier + ); + + if (result) { + return true; + } + + return false; + } + async findOneEntityWithProjection(entityId,projection){ + const query = { _id: ObjectID(entityId) }; + const entity = await this.entityRepository.findOneEntityWithProjection(query,projection); + + return entity; + } +} + +module.exports = EntityService; diff --git a/src/services/HelpOfferService.js b/src/services/HelpOfferService.js new file mode 100644 index 00000000..c025d76b --- /dev/null +++ b/src/services/HelpOfferService.js @@ -0,0 +1,202 @@ +const OfferedHelpRepository = require("../repository/HelpOfferRepository"); +const UserService = require("./UserService"); +const EntityService = require("./EntityService"); +const NotificationService = require("./NotificationService"); +const NotificationMixin = require("../utils/NotificationMixin"); +const { notificationTypesEnum } = require("../models/Notification"); +const saveError = require('../utils/ErrorHistory'); +const { findConnections, sendMessage } = require("../../websocket"); +const { ObjectID } = require('mongodb'); + +class OfferedHelpService { + constructor() { + this.OfferedHelpRepository = new OfferedHelpRepository(); + this.UserService = new UserService(); + this.NotificationService = new NotificationService(); + this.NotificationMixin = new NotificationMixin(); + this.EntityService = new EntityService(); + } + + async createNewHelpOffer(offeredHelpInfo) { + const newOfferdHelp = await this.OfferedHelpRepository.create( + offeredHelpInfo + ); + return newOfferdHelp; + } + + async getHelpOfferWithAggregationById(id) { + const help = await this.OfferedHelpRepository.getByIdWithAggregation(id); + + if (!help) { + throw new Error('Oferta não encontrada'); + } + + return help; + } + + async listHelpsOffers(userId, isUserEntity, categoryArray,getOtherUsers) { + const helpOffers = await this.OfferedHelpRepository.list(userId, isUserEntity, categoryArray,getOtherUsers); + return helpOffers; + } + + async listHelpsOffersByOwnerId(ownerId) { + const helpOffers = await this.OfferedHelpRepository.listByOwnerId(ownerId); + return helpOffers; + } + + async listHelpOffersByHelpedUserId(helpedUserId) { + const helpOffers = await this.OfferedHelpRepository.listByHelpedUserId( + helpedUserId + ); + return helpOffers; + } + + validateOwnerAndHelpedUser(helpedId, helpOffer) { + if (helpOffer.ownerId == helpedId) + throw new Error("Dono não pode ser ajudado da própria oferta"); + else if (helpOffer.helpedUserId != null && helpOffer.helpedUserId.includes(helpedId)) + throw new Error("Usuário já está sendo ajudado"); + } + + validateOwner(helpOffer, email){ + if (helpOffer.user.email !== email) { + throw new Error('Usuário não autorizado'); + } + } + + isUserInPossibleHelpedUsers(helpedUser, helpOffer, helpedId) { + if (!helpedUser.isEntity) + return helpOffer.possibleHelpedUsers.includes(helpedId) + else + return helpOffer.possibleEntities.includes(helpedId) + } + + possibleInterestedArray(helpOffer, helpedUser){ + if(helpedUser.isEntity) + return helpOffer.possibleEntities + else + return helpOffer.possibleHelpedUsers + } + + async addPossibleHelpedUsers(helpedId, helpOfferId) { + const helpOffer = await this.getHelpOfferById(helpOfferId); + const helpedUser = await this.verifyUserEntity(helpedId); + const possibleHelpedUser = this.possibleInterestedArray(helpOffer, helpedUser) + + // Validacao + this.validateOwnerAndHelpedUser(helpedId, helpOffer) + if (this.isUserInPossibleHelpedUsers(helpedUser, helpOffer, helpedId)) + throw new Error("Usuário já é um possível ajudado"); + + // Alteracao do array + await this.useService(possibleHelpedUser, "push", [helpedId]); + await this.OfferedHelpRepository.update(helpOffer); + + + //Notificação + const ownerProjection = { deviceId: 1, _id: 0 }; + const { deviceId: ownerDeviceId } = await this.useService(this.UserService, "findOneUserWithProjection", [helpOffer.ownerId, ownerProjection]); + + const title = `${helpedUser.name} quer sua ajuda!`; + const body = `Sua oferta ${helpOffer.title} recebeu um interessado`; + + await this.sendHelpOfferNotification(ownerDeviceId, title, body, helpOffer.ownerId, helpOfferId, notificationTypesEnum.ofertaRequerida); + + } + + async addHelpedUsers(helpedId, helpOfferId) { + const helpOffer = await this.getHelpOfferById(helpOfferId); + const helpedUser = await this.verifyUserEntity(helpedId); + const interestedArray = this.possibleInterestedArray(helpOffer, helpedUser) + + // Validacao + this.validateOwnerAndHelpedUser(helpedId, helpOffer) + if (!this.isUserInPossibleHelpedUsers(helpedUser, helpOffer, helpedId)) + throw new Error("Usuário não é um interessado na ajuda"); + + // Alteracao do array + await this.useService(helpOffer.helpedUserId, "push", [helpedId]); + await this.useService(interestedArray, "pull", [helpedId]); + await this.OfferedHelpRepository.update(helpOffer); + + //Notificacao + const ownerProjection = { name: 1, _id: 0 }; + const { name: ownerName } = await this.useService(this.UserService, "findOneUserWithProjection", [helpOffer.ownerId, ownerProjection]); + + const title = `${ownerName} escolheu ajudar você!`; + const body = `Você foi escolhido para ser ajudado na oferta ${helpOffer.title}`; + + await this.sendHelpOfferNotification(helpedUser.deviceId, title, body, helpedId, helpOfferId, notificationTypesEnum.ofertaAceita); + } + + async verifyUserEntity(helpedId) { + let helpedUserProjection = { name: 1, deviceId: 1, cpf: 1, _id: 0 }; + let helpedUserName; + + helpedUserName = await this.useService(this.UserService, "findOneUserWithProjection", [helpedId, helpedUserProjection]); + if (helpedUserName == null) { + helpedUserProjection = { name: 1, deviceId: 1, cnpj: 1, _id: 0 }; + helpedUserName = await this.useService(this.EntityService, "findOneEntityWithProjection", [helpedId, helpedUserProjection]); + } + + const isUserEntity = (helpedUserName.cnpj ? true : false); + return { name: helpedUserName.name, deviceId: helpedUserName.deviceId, isEntity: isUserEntity }; + } + + async sendHelpOfferNotification(deviceId, title, body, userId, helpOfferId, notificationType) { + const notificationHistory = { + userId: userId, + helpId: helpOfferId, + isOffer: true, + title, + body, + notificationType: notificationType, + }; + + try { + await this.NotificationMixin.sendNotification( + deviceId, + title, + body + ); + await this.NotificationService.createNotification(notificationHistory); + } catch (err) { + console.log("Não foi possível enviar a notificação!"); + saveError(err); + } + } + + async getHelpOfferById(helpOfferId) { + const helpOffer = await this.OfferedHelpRepository.getById(helpOfferId); + return helpOffer; + } + + async finishHelpOfferByOwner(helpOfferId, email) { + const query = {_id: ObjectID(helpOfferId)}; + const helpOfferProjection = ['ownerId', 'categoryId', 'active']; + const owner = { + path: 'user', + select: 'email' + } + let helpOffer = await this.OfferedHelpRepository.findOne(query, helpOfferProjection, owner); + + this.validateOwner(helpOffer, email); + + helpOffer = await this.OfferedHelpRepository.finishHelpOfferByOwner(helpOffer); + + const sendSocketMessageTo = findConnections(helpOffer.categoryId, helpOffer.ownerId.toString()); + sendMessage(sendSocketMessageTo, 'delete-help-offer', helpOfferId); + } + + async getEmailByHelpOfferId(helpOfferId) { + const ownerEmail = await this.OfferedHelpRepository.getEmailByHelpOfferId(helpOfferId); + return ownerEmail; + } + + async useService(service, functionName, params = []) { + let functionReturn = await service[functionName](...params); + return functionReturn; + } +} + +module.exports = OfferedHelpService; diff --git a/src/services/HelpService.js b/src/services/HelpService.js index c70c3f1c..0cf56c43 100644 --- a/src/services/HelpService.js +++ b/src/services/HelpService.js @@ -1,17 +1,19 @@ -const HelpRepository = require('../repository/HelpRepository'); -const NotificationService = require('./NotificationService'); -const { notificationTypesEnum } = require('../models/Notification'); -const UserService = require('./UserService'); -const CategoryService = require('./CategoryService'); -const { findConnections, sendMessage } = require('../../websocket'); -const NotificationMixin = require('../utils/NotificationMixin'); -const helpStatusEnum = require('../utils/enums/helpStatusEnum'); -const saveError = require('../utils/ErrorHistory'); +const HelpRepository = require("../repository/HelpRepository"); +const NotificationService = require("./NotificationService"); +const { notificationTypesEnum } = require("../models/Notification"); +const UserService = require("./UserService"); +const EntityService = require("./EntityService"); +const CategoryService = require("./CategoryService"); +const { findConnections, sendMessage } = require("../../websocket"); +const NotificationMixin = require("../utils/NotificationMixin"); +const helpStatusEnum = require("../utils/enums/helpStatusEnum"); +const saveError = require("../utils/ErrorHistory"); class HelpService { constructor() { this.HelpRepository = new HelpRepository(); this.UserService = new UserService(); + this.EntityService = new EntityService(); this.CategoryService = new CategoryService(); this.NotificationService = new NotificationService(); this.NotificationMixin = new NotificationMixin(); @@ -20,7 +22,7 @@ class HelpService { async createHelp(data) { const countHelp = await this.HelpRepository.countDocuments(data.ownerId); if (countHelp >= 5) { - throw new Error('Limite máximo de pedidos atingido'); + throw new Error("Limite máximo de pedidos atingido"); } await this.CategoryService.getCategoryByid(data.categoryId); @@ -29,47 +31,43 @@ class HelpService { const sendSocketMessageTo = findConnections( createdHelp.categoryId, - JSON.parse(JSON.stringify(createdHelp.ownerId)), + JSON.parse(JSON.stringify(createdHelp.ownerId)) ); sendMessage(sendSocketMessageTo, 'new-help', createdHelp); - - return createdHelp; } async getHelpByid(id) { const Help = await this.HelpRepository.getById(id); if (!Help) { - throw new Error('Ajuda não encontrada'); + throw new Error("Ajuda não encontrada"); } return Help; } - async getHelpList(id, status, category, except, helper) { - const Helplist = await this.HelpRepository.list( - id, - status, - category, - except, - helper, - ); - if (!Helplist) { - throw new Error('Nenhuma Ajuda com esse status foi encontrada'); + + async getHelpWithAggregationById(id) { + const Help = await this.HelpRepository.getByIdWithAggregation(id); + + if (!Help) { + throw new Error('Ajuda não encontrada'); } - return Helplist; + return Help; } - async getNearHelpList(coords, except, id, categoryArray) { - const Helplist = await this.HelpRepository.listNear( + async getHelpList(coords, id, isUserEntity, categoryArray) { + const Helplist = await this.HelpRepository.shortList( coords, - except, id, - categoryArray, + isUserEntity, + categoryArray ); if (!Helplist) { - throw new Error('Pedidos de ajuda não encontrados no seu raio de distância'); + throw new Error( + "Pedidos de ajuda não encontrados no seu raio de distância" + ); } return Helplist; @@ -85,18 +83,22 @@ class HelpService { help = JSON.parse(JSON.stringify(help)); const sendSocketMessageTo = findConnections(help.categoryId, JSON.parse(JSON.stringify(help.ownerId))); sendMessage(sendSocketMessageTo, 'delete-help', id); - - return { message: `Help ${id} deleted!` }; } async getHelpListByStatus({ userId, statusList, helper = false }) { - const checkHelpStatusExistence = statusList.filter((item) => !Object.values(helpStatusEnum).includes(item)); + const checkHelpStatusExistence = statusList.filter( + (item) => !Object.values(helpStatusEnum).includes(item) + ); if (checkHelpStatusExistence.length > 0) { - throw new Error('Um dos status informados é ínvalido'); + throw new Error("Um dos status informados é ínvalido"); } - const helpList = await this.HelpRepository.getHelpListByStatus(userId, statusList, helper); + const helpList = await this.HelpRepository.getHelpListByStatus( + userId, + statusList, + helper + ); return helpList; } @@ -105,28 +107,38 @@ class HelpService { const { idHelper } = data; const help = await this.getHelpByid(data.idHelp); const { ownerId } = help; - const helper = await this.UserService.getUser({ id: idHelper }); + let helper; + try { + helper = await this.UserService.getUser({ id: idHelper }); + } catch { + helper = await this.EntityService.getEntity({ id: idHelper }); + } + const owner = await this.UserService.getUser({ id: ownerId }); if (help.helperId) { - throw new Error('Ajuda já possui ajudante'); + throw new Error("Ajuda já possui ajudante"); } const sendSocketMessageTo = findConnections( help.categoryId, - JSON.parse(JSON.stringify(ownerId)), + JSON.parse(JSON.stringify(ownerId)) ); - sendMessage(sendSocketMessageTo, 'delete-help', help._id); + sendMessage(sendSocketMessageTo, "delete-help", help._id); const title = `${owner.name} aceitou sua oferta de ajuda!`; const body = `Sua oferta para ${help.title} foi aceita`; const userPosition = help.possibleHelpers.indexOf(data.idHelper); - if (userPosition >= 0) { + const entityPosition = help.possibleEntities.indexOf(data.idHelper); + if (userPosition < 0 && entityPosition < 0) { + throw new Error('Ajudante não encontrado'); + } else { help.helperId = data.idHelper; - help.status = 'on_going'; + help.status = "on_going"; help.possibleHelpers = []; - const result = await this.HelpRepository.update(help); + help.possibleEntities = []; + await this.HelpRepository.update(help); const notificationHistory = { userId: helper._id, @@ -137,27 +149,32 @@ class HelpService { }; try { - this.NotificationService.createNotification(notificationHistory); - this.NotificationMixin.sendNotification(helper.deviceId, title, body); + await this.NotificationService.createNotification(notificationHistory); + await this.NotificationMixin.sendNotification( + helper.deviceId, + title, + body + ); } catch (err) { - console.log('Não foi possível enviar a notificação!'); + console.log("Não foi possível enviar a notificação!"); saveError(err); } - - return result; } - throw new Error('Ajudante não encontrado'); } async helperConfirmation(data) { const help = await this.getHelpByid(data.helpId); const owner = await this.UserService.getUser({ id: help.ownerId }); - const helper = await this.UserService.getUser({ id: help.helperId }); - + let helper; + try { + helper = await this.UserService.getUser({ id: help.helperId }); + } catch { + helper = await this.EntityService.getEntity({ id: help.helperId }); + } if (help.helperId != data.helperId) { - throw new Error('Usuário não é o ajudante dessa ajuda'); - } else if (help.status === 'owner_finished') { - const ownerTitle = 'Pedido de ajuda finalizado!'; + throw new Error("Usuário não é o ajudante dessa ajuda"); + } else if (help.status === "owner_finished") { + const ownerTitle = "Pedido de ajuda finalizado!"; const ownerBody = `Seu pedido ${help.title} foi finalizado`; const ownerNotificationHistory = { @@ -168,7 +185,7 @@ class HelpService { notificationType: notificationTypesEnum.ajudaFinalizada, }; - const helperTitle = 'Oferta de ajuda finalizada!'; + const helperTitle = "Oferta de ajuda finalizada!"; const helperBody = `Sua oferta da ajuda ${help.title} foi finalizada`; const helperNotificationHistory = { userId: help.helperId, @@ -179,38 +196,53 @@ class HelpService { }; try { - this.NotificationMixin.sendNotification(owner.deviceId, ownerTitle, ownerBody); - this.NotificationService.createNotification(ownerNotificationHistory); - this.NotificationMixin.sendNotification(helper.deviceId, helperTitle, helperBody); - this.NotificationService.createNotification(helperNotificationHistory); + await this.NotificationMixin.sendNotification( + owner.deviceId, + ownerTitle, + ownerBody + ); + await this.NotificationService.createNotification( + ownerNotificationHistory + ); + await this.NotificationMixin.sendNotification( + helper.deviceId, + helperTitle, + helperBody + ); + await this.NotificationService.createNotification( + helperNotificationHistory + ); } catch (err) { - console.log('Não foi possível enviar a notificação!'); + console.log("Não foi possível enviar a notificação!"); saveError(err); } - help.status = 'finished'; - } else if (help.status === 'helper_finished') { - throw new Error('Usuário já confirmou a finalização da ajuda'); - } else if (help.status === 'finished') { - throw new Error('Ajuda já foi finalizada'); + help.status = "finished"; + } else if (help.status === "helper_finished") { + throw new Error("Usuário já confirmou a finalização da ajuda"); + } else if (help.status === "finished") { + throw new Error("Ajuda já foi finalizada"); } else { - help.status = 'helper_finished'; + help.status = "helper_finished"; } - const result = await this.HelpRepository.update(help); - - return result; + await this.HelpRepository.update(help); } async ownerConfirmation(data) { const help = await this.getHelpByid(data.helpId); const owner = await this.UserService.getUser({ id: help.ownerId }); - const helper = await this.UserService.getUser({ id: help.helperId }); + let helper; + try { + helper = await this.UserService.getUser({ id: help.helperId }); + } catch { + helper = await this.EntityService.getEntity({ id: help.helperId }); + } if (help.ownerId != data.ownerId) { - throw new Error('Usuário não é o dono da ajuda'); - } else if (help.status === 'helper_finished') { - const ownerTitle = 'Pedido de ajuda finalizado!'; + throw new Error("Usuário não é o dono da ajuda"); + } else if (help.status === "helper_finished") { + const ownerTitle = "Pedido de ajuda finalizado!"; const ownerBody = `Seu pedido ${help.title} foi finalizado`; const ownerNotificationHistory = { @@ -221,7 +253,7 @@ class HelpService { notificationType: notificationTypesEnum.ajudaFinalizada, }; - const helperTitle = 'Oferta de ajuda finalizada!'; + const helperTitle = "Oferta de ajuda finalizada!"; const helperBody = `Sua oferta da ajuda ${help.title} foi finalizada`; const helperNotificationHistory = { userId: help.helperId, @@ -232,49 +264,75 @@ class HelpService { }; try { - this.NotificationMixin.sendNotification(owner.deviceId, ownerTitle, ownerBody); - this.NotificationService.createNotification(ownerNotificationHistory); - this.NotificationMixin.sendNotification(helper.deviceId, helperTitle, helperBody); - this.NotificationService.createNotification(helperNotificationHistory); + await this.NotificationMixin.sendNotification( + owner.deviceId, + ownerTitle, + ownerBody + ); + await this.NotificationService.createNotification( + ownerNotificationHistory + ); + await this.NotificationMixin.sendNotification( + helper.deviceId, + helperTitle, + helperBody + ); + await this.NotificationService.createNotification( + helperNotificationHistory + ); } catch (err) { - console.log('Não foi possível enviar a notificação!'); + console.log("Não foi possível enviar a notificação!"); saveError(err); } - help.status = 'finished'; - } else if (help.status === 'owner_finished') { - throw new Error('Usuário já confirmou a finalização da ajuda'); - } else if (help.status === 'finished') { - throw new Error('Essa ajuda já foi finalizada'); + help.status = "finished"; + } else if (help.status === "owner_finished") { + throw new Error("Usuário já confirmou a finalização da ajuda"); + } else if (help.status === "finished") { + throw new Error("Essa ajuda já foi finalizada"); } else { - help.status = 'owner_finished'; + help.status = "owner_finished"; } - const result = await this.HelpRepository.update(help); - return result; + await this.HelpRepository.update(help); } async addPossibleHelpers(id, idHelper) { const help = await this.getHelpByid(id); const owner = await this.UserService.getUser({ id: help.ownerId }); - if (idHelper == help.ownerId) { - throw new Error('Você não pode ser ajudante de sua própria ajuda'); + throw new Error("Você não pode ser ajudante de sua própria ajuda"); } if (help.helperId) { - throw new Error('Ajuda já possui ajudante'); + throw new Error("Ajuda já possui ajudante"); + } + let helper; + let isUser = false; + try { + helper = await this.UserService.getUser({ id: idHelper }); + isUser = true; + } catch { + helper = await this.EntityService.getEntity({ id: idHelper }); } + if (isUser) { + const userPosition = help.possibleHelpers.indexOf(idHelper); - const helper = await this.UserService.getUser({ id: idHelper }); - const userPosition = help.possibleHelpers.indexOf(idHelper); + if (userPosition > -1) { + throw new Error("Usuário já é um possível ajudante"); + } - if (userPosition > -1) { - throw new Error('Usuário já é um possível ajudante'); - } + help.possibleHelpers.push(idHelper); + } else { + const userPosition = help.possibleEntities.indexOf(idHelper); - help.possibleHelpers.push(idHelper); + if (userPosition > -1) { + throw new Error("Usuário já é um possível ajudante"); + } - const result = await this.HelpRepository.update(help); + help.possibleEntities.push(idHelper); + } + + await this.HelpRepository.update(help); const title = `${helper.name} quer te ajudar!`; const body = `Seu pedido ${help.title} recebeu uma oferta de ajuda`; @@ -288,24 +346,34 @@ class HelpService { }; try { - this.NotificationMixin.sendNotification(owner.deviceId, title, body); - this.NotificationService.createNotification(notificationHistory); + await this.NotificationMixin.sendNotification( + owner.deviceId, + title, + body + ); + await this.NotificationService.createNotification(notificationHistory); } catch (err) { - console.log('Não foi possível enviar a notificação!'); + console.log("Não foi possível enviar a notificação!"); saveError(err); } - - return result; } async getListToDelete() { const Helplist = await this.HelpRepository.listToExpire(); if (!Helplist) { - throw new Error('Pedidos de ajuda não encontrados'); + throw new Error("Pedidos de ajuda não encontrados"); } return Helplist; } + + async getHelpInfoById(helpId) { + const helpInfo = await this.HelpRepository.getHelpInfoById(helpId); + if (!helpInfo) { + throw new Error('Pedido de ajuda não encontrado'); + } + return helpInfo; + } } module.exports = HelpService; diff --git a/src/services/NotificationService.js b/src/services/NotificationService.js index f8bccb56..30f5e144 100644 --- a/src/services/NotificationService.js +++ b/src/services/NotificationService.js @@ -1,4 +1,7 @@ +const { notificationTypesEnum } = require('../models/Notification'); const NotificationRepository = require('../repository/NotificationRepository'); +const notify = require('../utils/Notification'); +const UserService = require('./UserService'); class NotificationService { constructor() { @@ -16,6 +19,38 @@ class NotificationService { return notificationCreated; } + + async createAndSendNotifications(title, body) { + const userService = new UserService(); + const users = await userService.getUsersWithDevice(); + + const messages = []; + const notifications = []; + users.forEach((user) => { + messages.push({ + to: user.deviceId, + sound: 'default', + title, + body, + }); + notifications.push({ + userId: user._id, + title, + body, + notificationType: notificationTypesEnum.notificacaoManual, + }); + }); + + try { + notify(messages); + + notifications.forEach(async (notification) => { + await this.createNotification(notification); + }); + } catch (err) { + throw new Error(err); + } + } } module.exports = NotificationService; diff --git a/src/services/UserService.js b/src/services/UserService.js index f6d37b52..caff3905 100644 --- a/src/services/UserService.js +++ b/src/services/UserService.js @@ -1,33 +1,44 @@ -const UserRepository = require('../repository/UserRepository'); -const firebase = require('../config/authFirebase'); +const UserRepository = require("../repository/UserRepository"); +const EntityRepository = require("../repository/EntityRepository"); +const firebase = require("../config/authFirebase"); +const { ObjectID } = require("mongodb"); class UserService { constructor() { this.userRepository = new UserRepository(); + this.entityRepository = new EntityRepository(); } async createUser(data) { + const isEntityRegistered = await this.entityRepository.checkEntityExistence( + data.email + ); + + if (isEntityRegistered) { + throw new Error("Email já sendo utilizado"); + } + if (data.password.length < 8) { - throw new Error('Senha inválida'); + throw new Error("Senha inválida"); } if (data.cpf.length >= 11) { - data.cpf = data.cpf.replace(/[-.]/g, ''); + data.cpf = data.cpf.replace(/[-.]/g, ""); } + data.email = data.email.toLowerCase(); try { const createdUser = await this.userRepository.create(data); if (!data.hasUser) { - console.log('Usuario Criado'); // Cria o usuário no firebase await firebase .auth() .createUser({ email: data.email, password: data.password, - displayName: data.name, - emailVerified: false + displayName: `${data.name} | PF`, + emailVerified: false, }) .catch(async (err) => { await this.removeUser(data.email); @@ -39,13 +50,20 @@ class UserService { } catch (err) { throw err; } + } - return createdUser; + async getUsersWithDevice() { + try { + const users = await this.userRepository.getUsersWithDevice(); + return users; + } catch (err) { + throw err; + } } async getUser({ id = undefined, email = undefined }) { if (!id && !email) { - throw new Error('Nenhum identificador encontrado'); + throw new Error("Nenhum identificador encontrado"); } let user; @@ -55,7 +73,23 @@ class UserService { user = await this.userRepository.getUserByEmail(email); } if (!user) { - throw new Error('Usuário não encontrado'); + throw new Error("Usuário não encontrado"); + } + return user; + } + + async getAnyUser({ id = undefined, email = undefined }) { + if (!id && !email) { + throw new Error("Nenhum identificador encontrado"); + } + let user; + + if (id) { + user = await this.userRepository.getById(id); + if (!user) user = await this.entityRepository.getById(id); + } + if (!user) { + throw new Error("Usuário não encontrado"); } return user; } @@ -81,9 +115,7 @@ class UserService { return result; } - async editUserAddressById({ - email, cep, number, city, state, complement, - }) { + async editUserAddressById({ email, cep, number, city, state, complement }) { const user = await this.getUser({ email }); const address = { @@ -129,8 +161,8 @@ class UserService { await this.userRepository.removeUser({ id: user._id, email }); } - async checkUserExistence(identificator) { - const result = await this.userRepository.checkUserExistence(identificator); + async checkUserExistence(userIdentifier) { + const result = await this.userRepository.checkUserExistence(userIdentifier); if (result) { return true; @@ -138,6 +170,14 @@ class UserService { return false; } + + async findOneUserWithProjection(userId,projection){ + const query = { _id: ObjectID(userId) }; + + const user = await this.userRepository.findOneUserWithProjection(query,projection); + + return user; + } } module.exports = UserService; diff --git a/src/utils/IsEntity.js b/src/utils/IsEntity.js new file mode 100644 index 00000000..cd4a1ea9 --- /dev/null +++ b/src/utils/IsEntity.js @@ -0,0 +1,13 @@ +const isEntity = (userName) => +{ + let userType = userName.trim().split(' '); + userType = userType.pop(); + + if (userType == 'PJ'){ + return true; + } else { + return false; + } +} + +module.exports = isEntity; \ No newline at end of file diff --git a/src/utils/seed/HelpSeed.js b/src/utils/seed/HelpSeed.js index fc201642..ca00d91f 100644 --- a/src/utils/seed/HelpSeed.js +++ b/src/utils/seed/HelpSeed.js @@ -44,7 +44,7 @@ const seedHelp = async () => { description: faker.lorem.lines(1), status: sampleStatus, possibleHelpers: samplePossibleHelpsID, - categoryId: sampleCategory._id, + categoryId: [sampleCategory._id], ownerId: sampleUsers[0]._id, finishedDate: faker.date.future(), }), diff --git a/src/utils/sharedAggregationInfo.js b/src/utils/sharedAggregationInfo.js new file mode 100644 index 00000000..1c0b5322 --- /dev/null +++ b/src/utils/sharedAggregationInfo.js @@ -0,0 +1,49 @@ +const sharedAgreggationInfo = [ + { + $lookup: { + from: 'user', + localField: 'ownerId', + foreignField: '_id', + as: 'user', + }, + }, + { + $lookup: { + from: 'categories', + localField: 'categoryId', + foreignField: '_id', + as: 'categories', + }, + }, + { + $unwind: { + path: '$user', + preserveNullAndEmptyArrays: false, + }, + }, + { + $project: { + _id: 1, + description: 1, + title: 1, + status: 1, + ownerId: 1, + user: { + photo: 1, + phone: 1, + name: 1, + birthday: 1, + address: { + city: 1, + }, + }, + categories: { + _id: 1, + name: 1, + }, + }, + }, + +] + +module.exports = sharedAgreggationInfo; \ No newline at end of file diff --git a/src/validation/middlewares/authFirebase.js b/src/validation/middlewares/authFirebase.js index 85fd9988..922379ca 100644 --- a/src/validation/middlewares/authFirebase.js +++ b/src/validation/middlewares/authFirebase.js @@ -1,5 +1,6 @@ const admin = require('../../config/authFirebase'); const saveError = require('../../utils/ErrorHistory'); +const isEntity = require('../../utils/IsEntity'); const isAuthenticated = async (req, res, next) => { if (req.headers.authorization @@ -9,7 +10,11 @@ const isAuthenticated = async (req, res, next) => { try { const idToken = await admin.auth().verifyIdToken(token); + req.decodedToken = idToken; + + global.isUserEntity = isEntity(req.decodedToken.name); + return next(); } catch (err) { saveError(err); @@ -21,4 +26,5 @@ const isAuthenticated = async (req, res, next) => { return res.status(403).json({ error: err.message }); }; + module.exports = isAuthenticated; diff --git a/websocket.js b/websocket.js index a7e14129..0cbe5a35 100644 --- a/websocket.js +++ b/websocket.js @@ -8,12 +8,11 @@ exports.setupWebsocket = (server) => { io = socketio(server); io.on('connection', (socket) => { - const { currentRegion, userId } = socket.handshake.query; - + const { userPosition, userId } = socket.handshake.query; connections.push({ id: socket.id, userId, - currentRegion, + userPosition, categories: [], }); @@ -59,7 +58,7 @@ exports.findConnections = (category, userId) => { exports.sendMessage = (to, message, data) => { to.forEach((connection) => { if (typeof (data) === 'object' && message == 'new-help') { - const userLocation = JSON.parse(connection.currentRegion); + const userLocation = JSON.parse(connection.userPosition); const helpLocation = { latitude: data.user.location.coordinates[1], longitude: data.user.location.coordinates[0], diff --git a/yarn.lock b/yarn.lock index 75ab4c1d..f502b2db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -203,7 +203,7 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" @@ -218,12 +218,12 @@ "@protobufjs/eventemitter@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" @@ -231,27 +231,27 @@ "@protobufjs/float@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@sentry/apm@5.18.0": version "5.18.0" @@ -367,19 +367,14 @@ integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= "@types/long@^4.0.0", "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/node@*": - version "14.0.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" - integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== -"@types/node@^13.7.0": - version "13.13.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.12.tgz#9c72e865380a7dc99999ea0ef20fc9635b503d20" - integrity sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw== +"@types/node@*", "@types/node@>=13.7.0": + version "17.0.38" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" + integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g== "@types/node@^8.10.59": version "8.10.61" @@ -434,9 +429,9 @@ agent-base@6: debug "4" ajv@^6.10.0, ajv@^6.10.2: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -562,11 +557,6 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" @@ -599,20 +589,13 @@ base64id@2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== -bcrypt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-4.0.1.tgz#06e21e749a061020e4ff1283c1faa93187ac57fe" - integrity sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ== - dependencies: - node-addon-api "^2.0.0" - node-pre-gyp "0.14.0" - -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= +bcrypt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2" + integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg== dependencies: - callsite "1.0.0" + node-addon-api "^3.0.0" + node-pre-gyp "0.15.0" bignumber.js@^9.0.0: version "9.0.0" @@ -625,9 +608,9 @@ binary-extensions@^2.0.0: integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== bl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" - integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" + integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== dependencies: readable-stream "^2.3.5" safe-buffer "^5.1.1" @@ -720,11 +703,6 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -735,7 +713,7 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -772,7 +750,7 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1: +chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -921,16 +899,21 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1, cookie@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - 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== +cookie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + 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" @@ -1089,6 +1072,14 @@ dicer@^0.3.0: dependencies: streamsearch "0.1.2" +did-you-mean@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/did-you-mean/-/did-you-mean-0.0.1.tgz#8851ce82407903cb62c12cb6ad4f676921ccdec3" + integrity sha1-iFHOgkB5A8tiwSy2rU9naSHM3sM= + dependencies: + levenshtein "*" + underscore "*" + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -1175,20 +1166,20 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -engine.io-client@~3.4.0: - version "3.4.3" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c" - integrity sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw== +engine.io-client@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.0.tgz#fc1b4d9616288ce4f2daf06dcf612413dec941c7" + integrity sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA== dependencies: component-emitter "~1.3.0" component-inherit "0.0.3" - debug "~4.1.0" + debug "~3.1.0" engine.io-parser "~2.2.0" has-cors "1.1.0" indexof "0.0.1" - parseqs "0.0.5" - parseuri "0.0.5" - ws "~6.1.0" + parseqs "0.0.6" + parseuri "0.0.6" + ws "~7.4.2" xmlhttprequest-ssl "~1.5.4" yeast "0.1.2" @@ -1203,17 +1194,17 @@ engine.io-parser@~2.2.0: blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.4.0: - version "3.4.2" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.2.tgz#8fc84ee00388e3e228645e0a7d3dfaeed5bd122c" - integrity sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg== +engine.io@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b" + integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA== dependencies: accepts "~1.3.4" base64id "2.0.0" - cookie "0.3.1" + cookie "~0.4.1" debug "~4.1.0" engine.io-parser "~2.2.0" - ws "^7.1.2" + ws "~7.4.2" ent@^2.2.0: version "2.2.0" @@ -1615,7 +1606,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-minipass@^1.2.5: +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== @@ -1712,10 +1703,18 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +git-commit-msg-linter@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/git-commit-msg-linter/-/git-commit-msg-linter-3.2.6.tgz#e2894fc21d3b69635433e1c5b161e25e6b6068d1" + integrity sha512-u9LolsJjHFLm12FWCnZhrhMJmHzDBP4Uz31F+OLpTSGvZW9ms6/+0hscJA9nisFU0HK0tJJ991ISomc5ajoO6A== + dependencies: + chalk "^2.4.2" + did-you-mean "^0.0.1" + glob-parent@^5.0.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + 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" @@ -1872,9 +1871,9 @@ hash-stream-validation@^0.2.2: through2 "^2.0.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== http-cache-semantics@^4.0.0: version "4.1.0" @@ -2340,6 +2339,11 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" +levenshtein@*: + version "1.0.5" + resolved "https://registry.yarnpkg.com/levenshtein/-/levenshtein-1.0.5.tgz#3911737a9cb56da345d008f55782c6f138979ba3" + integrity sha1-ORFzepy1baNF0Aj1V4LG8TiXm6M= + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -2416,10 +2420,10 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long-timeout@0.1.1: version "0.1.1" @@ -2520,11 +2524,11 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, 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== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -2532,14 +2536,14 @@ minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.2.1: +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: minipass "^2.9.0" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -2634,7 +2638,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.2.1: +needle@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== @@ -2653,15 +2657,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.1.tgz#4fd0931bf6d7e48b219ff3e6abc73cbb0252b7a3" - integrity sha512-2WVfwRfIr1AVn3dRq4yRc2Hn35ND+mPJH6inC6bjpYCZVrpXPB4j3T6i//OGVfqVsR1t/X/axRulDsheq4F0LQ== +node-addon-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.0.tgz#812446a1001a54f71663bed188314bba07e09247" + integrity sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg== node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" node-forge@0.7.4: version "0.7.4" @@ -2673,14 +2679,14 @@ node-forge@^0.9.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== -node-pre-gyp@0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== +node-pre-gyp@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087" + integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA== dependencies: detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" + mkdirp "^0.5.3" + needle "^2.5.0" nopt "^4.0.1" npm-packlist "^1.1.6" npmlog "^4.0.2" @@ -2745,9 +2751,9 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: 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== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-bundled@^1.0.1: version "1.1.1" @@ -2790,11 +2796,6 @@ object-assign@^4, object-assign@^4.1.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - object-inspect@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -2953,19 +2954,15 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" +parseqs@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" + integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" +parseuri@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" + integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== parseurl@~1.3.3: version "1.3.3" @@ -2988,9 +2985,9 @@ path-key@^2.0.1: integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 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== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" @@ -3047,9 +3044,9 @@ promise-limit@^2.7.0: integrity sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw== protobufjs@^6.8.6, protobufjs@^6.8.9: - version "6.9.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.9.0.tgz#c08b2bf636682598e6fabbf0edb0b1256ff090bd" - integrity sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg== + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -3062,7 +3059,7 @@ protobufjs@^6.8.6, protobufjs@^6.8.9: "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" "@types/long" "^4.0.1" - "@types/node" "^13.7.0" + "@types/node" ">=13.7.0" long "^4.0.0" proxy-addr@~2.0.5: @@ -3294,7 +3291,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.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== @@ -3426,32 +3423,29 @@ socket.io-adapter@~1.1.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== -socket.io-client@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" - integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== +socket.io-client@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35" + integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ== dependencies: backo2 "1.0.2" - base64-arraybuffer "0.1.5" component-bind "1.0.0" - component-emitter "1.2.1" - debug "~4.1.0" - engine.io-client "~3.4.0" + component-emitter "~1.3.0" + debug "~3.1.0" + engine.io-client "~3.5.0" has-binary2 "~1.0.2" - has-cors "1.1.0" indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" + parseqs "0.0.6" + parseuri "0.0.6" socket.io-parser "~3.3.0" to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" - integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng== + version "3.3.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: - component-emitter "1.2.1" + component-emitter "~1.3.0" debug "~3.1.0" isarray "2.0.1" @@ -3464,16 +3458,16 @@ socket.io-parser@~3.4.0: debug "~4.1.0" isarray "2.0.1" -socket.io@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb" - integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg== +socket.io@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.0.tgz#01030a2727bd8eb2e85ea96d69f03692ee53d47e" + integrity sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ== dependencies: debug "~4.1.0" - engine.io "~3.4.0" + engine.io "~3.5.0" has-binary2 "~1.0.2" socket.io-adapter "~1.1.0" - socket.io-client "2.3.0" + socket.io-client "2.4.0" socket.io-parser "~3.4.0" sorted-array-functions@^1.0.0: @@ -3691,17 +3685,17 @@ table@^5.2.3: string-width "^3.0.0" tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" teeny-request@^6.0.0: version "6.0.3" @@ -3780,6 +3774,11 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -3839,6 +3838,11 @@ undefsafe@^2.0.2: dependencies: debug "^2.2.0" +underscore@*: + version "1.13.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -3871,9 +3875,9 @@ update-notifier@^4.0.0: xdg-basedir "^4.0.0" uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -3922,6 +3926,11 @@ walkdir@^0.4.0: resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" @@ -3936,6 +3945,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" @@ -4017,17 +4034,10 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@^7.1.2: - version "7.3.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd" - integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@~7.4.2: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xdg-basedir@^4.0.0: version "4.0.0" @@ -4044,7 +4054,7 @@ xtend@^4.0.1, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==